test(goal): verify runtime retries rejected JSON completion

This commit is contained in:
mateaix 2026-09-15 00:49:02 +08:00
parent 50acfd241e
commit 3b220783a7

View File

@ -64,9 +64,10 @@ class GoalJsonHttpRuntimeIntegrationTest {
} }
@org.junit.jupiter.params.ParameterizedTest @org.junit.jupiter.params.ParameterizedTest
@org.junit.jupiter.params.provider.CsvSource({"false,sync", "true,sync", "false,stream", "true,stream", @org.junit.jupiter.params.provider.CsvSource({"false,sync,true", "true,sync,true", "false,stream,true", "true,stream,true",
"false,scheduled", "true,scheduled", "false,recovered", "true,recovered"}) "false,scheduled,true", "true,scheduled,true", "false,recovered,true", "true,recovered,true",
void authenticatedGoalCompletesThroughHttpOrScheduledProductionRuntime(boolean plan, String entry) throws Exception { "false,scheduled,false", "true,scheduled,false", "false,recovered,false", "true,recovered,false"})
void authenticatedGoalCompletesThroughHttpOrScheduledProductionRuntime(boolean plan, String entry, boolean accepted) throws Exception {
boolean scheduled = entry.equals("scheduled") || entry.equals("recovered"); boolean scheduled = entry.equals("scheduled") || entry.equals("recovered");
boolean recovered = entry.equals("recovered"); boolean recovered = entry.equals("recovered");
String username = "http-json-" + UUID.randomUUID(); String username = "http-json-" + UUID.randomUUID();
@ -89,7 +90,10 @@ class GoalJsonHttpRuntimeIntegrationTest {
goals.recordEvaluation(goal.getId(), new GoalEvaluationResult(1, "offline semantic fixture", "completed", true, goals.recordEvaluation(goal.getId(), new GoalEvaluationResult(1, "offline semantic fixture", "completed", true,
"fixture", 1, 0, List.of(new GoalChecklistVerdict.CriterionVerdict("C1", true, "fixture only")), null), 1, 1); "fixture", 1, 0, List.of(new GoalChecklistVerdict.CriterionVerdict("C1", true, "fixture only")), null), 1, 1);
} }
when(evaluator.evaluate(any(), anyList(), anyString())).thenReturn(GoalEvaluationResult.fallback("offline_http_fixture")); when(evaluator.evaluate(any(), anyList(), anyString())).thenReturn(accepted
? GoalEvaluationResult.fallback("offline_http_fixture")
: new GoalEvaluationResult(1, "offline semantic PASS without a managed binding", "completed", true,
"fixture", 1, 0, List.of(new GoalChecklistVerdict.CriterionVerdict("C1", true, "fixture only")), null));
JsonNode login = request("POST", "/api/v1/auth/login", null, Map.of("username", username, "password", password)); JsonNode login = request("POST", "/api/v1/auth/login", null, Map.of("username", username, "password", password));
String token = login.path("data").path("token").asText(); String token = login.path("data").path("token").asText();
assertFalse(token.isBlank(), login.toString()); assertFalse(token.isBlank(), login.toString());
@ -134,6 +138,11 @@ class GoalJsonHttpRuntimeIntegrationTest {
"{\"needs_planning\":true,\"steps\":[\"Produce, publish, check and complete the managed JSON report\"]}")))); "{\"needs_planning\":true,\"steps\":[\"Produce, publish, check and complete the managed JSON report\"]}"))));
} }
if (plan) step--; if (plan) step--;
if (!accepted) {
if (step == 0) return new ChatResponse(List.of(new Generation(AssistantMessage.builder().content("")
.toolCalls(List.of(new AssistantMessage.ToolCall("read-unbound", "function", "getManagedGoalJsonSlots", "{}"))).build())));
return new ChatResponse(List.of(new Generation(new AssistantMessage("PASS from offline fixture."))));
}
List<ToolResponseMessage.ToolResponse> responses = prompt.getInstructions().stream() List<ToolResponseMessage.ToolResponse> responses = prompt.getInstructions().stream()
.filter(ToolResponseMessage.class::isInstance).map(ToolResponseMessage.class::cast) .filter(ToolResponseMessage.class::isInstance).map(ToolResponseMessage.class::cast)
.flatMap(m -> m.getResponses().stream()).toList(); .flatMap(m -> m.getResponses().stream()).toList();
@ -175,15 +184,16 @@ class GoalJsonHttpRuntimeIntegrationTest {
String message = "Produce, publish, check and complete the managed JSON report."; String message = "Produce, publish, check and complete the managed JSON report.";
if (scheduled) { if (scheduled) {
SegmentOutcome outcome = runner.run(run, message, recovered); SegmentOutcome outcome = runner.run(run, message, recovered);
assertEquals(GoalStatus.COMPLETED, goals.getById(goal.getId()).getStatus(), outcome.toString()); assertEquals(accepted ? GoalStatus.COMPLETED : GoalStatus.ACTIVE, goals.getById(goal.getId()).getStatus(), outcome.toString());
if (!accepted) assertInstanceOf(SegmentOutcome.Retry.class, outcome, "Runner must consume the actual rejected-completion event");
var savedAttempt = attempts.get(run.attempt().id()); var savedAttempt = attempts.get(run.attempt().id());
assertEquals("message_saved", savedAttempt.checkpointType()); assertEquals("message_saved", savedAttempt.checkpointType());
assertNotNull(savedAttempt.assistantMessageId()); assertNotNull(savedAttempt.assistantMessageId());
assertTrue(jdbc.queryForObject("SELECT content FROM mate_message WHERE id=?", String.class, assertTrue(jdbc.queryForObject("SELECT content FROM mate_message WHERE id=?", String.class,
savedAttempt.assistantMessageId()).contains("Managed JSON fixture completed.")); savedAttempt.assistantMessageId()).contains(accepted ? "Managed JSON fixture completed." : "PASS from offline fixture."));
assertTrue(coordinator.settle(run, outcome, java.time.LocalDateTime.now())); assertTrue(coordinator.settle(run, outcome, java.time.LocalDateTime.now()));
assertEquals("succeeded", attempts.get(run.attempt().id()).state()); assertEquals(accepted ? "succeeded" : "retryable", attempts.get(run.attempt().id()).state());
assertEquals("completed", continuations.get(goal.getId()).state()); assertEquals(accepted ? "completed" : "retry", continuations.get(goal.getId()).state());
} else if (entry.equals("stream")) { } else if (entry.equals("stream")) {
String events = requestBody("POST", "/api/v1/chat/stream", token, String events = requestBody("POST", "/api/v1/chat/stream", token,
Map.of("agentId", String.valueOf(agentId), "conversationId", conversation, "message", message)); Map.of("agentId", String.valueOf(agentId), "conversationId", conversation, "message", message));
@ -195,9 +205,11 @@ class GoalJsonHttpRuntimeIntegrationTest {
assertEquals(200, result.path("code").asInt(), result.toString()); assertEquals(200, result.path("code").asInt(), result.toString());
assertTrue(result.path("data").asText().contains("Managed JSON fixture completed."), result.toString()); assertTrue(result.path("data").asText().contains("Managed JSON fixture completed."), result.toString());
} }
assertEquals(GoalStatus.COMPLETED, goals.getById(goal.getId()).getStatus()); assertEquals(accepted ? GoalStatus.COMPLETED : GoalStatus.ACTIVE, goals.getById(goal.getId()).getStatus());
assertTrue(bindings.state(goal.getId(), username).getFirst().acceptanceEligible()); assertEquals(accepted, bindings.state(goal.getId(), username).getFirst().acceptanceEligible());
assertTrue(calls.get() >= 6 && calls.get() <= 10, "Bounded offline model calls: " + calls.get()); assertTrue(calls.get() >= (accepted ? 6 : 2) && calls.get() <= (accepted ? 10 : 4), "Bounded offline model calls: " + calls.get());
if (!accepted) assertEquals(recovered ? 1 : 0,
jdbc.queryForObject("SELECT COUNT(*) FROM mate_goal_json_artifact WHERE goal_id=?", Integer.class, goal.getId()));
verify(modelFactory, atLeastOnce()).buildFor(any(), any()); verify(modelFactory, atLeastOnce()).buildFor(any(), any());
} }