From 66d8cc6c3b3887b8986ee6a51f815daf3ed77367 Mon Sep 17 00:00:00 2001 From: mateaix <7333791@qq.com> Date: Mon, 14 Sep 2026 23:36:41 +0800 Subject: [PATCH] test(goal): cover automatic managed JSON completion in real graphs --- .../goal/GoalJsonGraphIntegrationTest.java | 134 +++++++++++++----- 1 file changed, 96 insertions(+), 38 deletions(-) diff --git a/mateclaw-server/src/test/java/vip/mate/goal/GoalJsonGraphIntegrationTest.java b/mateclaw-server/src/test/java/vip/mate/goal/GoalJsonGraphIntegrationTest.java index 8839f6a7..d2c135ce 100644 --- a/mateclaw-server/src/test/java/vip/mate/goal/GoalJsonGraphIntegrationTest.java +++ b/mateclaw-server/src/test/java/vip/mate/goal/GoalJsonGraphIntegrationTest.java @@ -68,31 +68,16 @@ class GoalJsonGraphIntegrationTest { @Autowired private vip.mate.agent.progress.ProgressLedgerService progress; @org.junit.jupiter.params.ParameterizedTest - @org.junit.jupiter.params.provider.CsvSource({"false,false", "false,true", "true,false", "true,true"}) - void realGraphsPreserveAuthenticatedOriginThroughReadPublishCheckAndComplete(boolean plan, boolean scheduled) throws Exception { - String username = "graph-" + UUID.randomUUID(); - long userId = IdWorker.getId(); - String conversation = UUID.randomUUID().toString(); - jdbc.update("INSERT INTO mate_user(id,username,password,enabled,role,create_time,update_time,deleted) VALUES (?,?,?,TRUE,'user',CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,0)", userId, username, "unused"); - jdbc.update("INSERT INTO mate_conversation(id,conversation_id,username,workspace_id,agent_id,create_time,update_time,deleted) VALUES (?,?,?,1,1,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,0)", IdWorker.getId(), conversation, username); - var request = new GoalCreateRequest(); request.setConversationId(conversation); request.setAgentId(1L); request.setWorkspaceId(1L); - request.setTitle("Managed graph fixture"); request.setDescription("Produce JSON"); request.setPersistentExecution(scheduled); request.setAutoFollowupEnabled(false); - GoalEntity goal = goals.create(request, username); - goals.appendCriterion(goal.getId(), "Produce the report", username); - 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); - requirements.configure(goal.getId(), "r", new GoalJsonAcceptanceService.ConfigureRequest(0L, "report", List.of("summary")), username); - GoalRunCoordinator.ClaimedRun run = null; - ChatOrigin origin = ChatOrigin.web(conversation, username, 1L, null, null, userId).withAgent(1L); - if (scheduled) { - jdbc.update("UPDATE mate_agent_goal SET auto_followup_enabled=TRUE WHERE id=?", goal.getId()); - var now = java.time.LocalDateTime.now(); continuations.discover(now); - run = coordinator.claim(continuations.get(goal.getId()), goals.getById(goal.getId()), now); - assertNotNull(run); assertTrue(coordinator.markRunning(run, now)); - origin = ChatOrigin.web(conversation, username, 1L, null).withAgent(1L) - .withExecutionAttribution(new vip.mate.agent.context.ExecutionAttribution(goal.getId(), run.attempt().id(), null, null, run.attempt().leaseToken())); - } - when(evaluator.evaluate(any(), anyList(), anyString())).thenReturn(GoalEvaluationResult.fallback("offline_graph_fixture")); + @org.junit.jupiter.params.provider.CsvSource({"false,false,false", "false,true,false", "true,false,false", "true,true,false", + "false,false,true", "false,true,true", "true,false,true", "true,true,true"}) + void realGraphsPreserveAuthenticatedOriginThroughReadPublishCheckAndComplete(boolean plan, boolean scheduled, boolean automatic) throws Exception { + Fixture fixture = configuredGoal(scheduled); + String username = fixture.username(); String conversation = fixture.conversation(); + GoalEntity goal = fixture.goal(); GoalRunCoordinator.ClaimedRun run = fixture.run(); ChatOrigin origin = fixture.origin(); + when(evaluator.evaluate(any(), anyList(), anyString())).thenReturn(automatic + ? new GoalEvaluationResult(1, "offline graph semantic verdict", "completed", true, "fixture", 1, 0, + List.of(new GoalChecklistVerdict.CriterionVerdict("C1", true, "fixture only")), null) + : GoalEvaluationResult.fallback("offline_graph_fixture")); var toolSet = tools.getEnabledToolSet().withAllowedToolsOnly(Set.of("getManagedGoalJsonSlots", "publishManagedGoalJson", "checkManagedGoalJson", "completeGoal")); assertEquals(4, toolSet.callbacks().size()); ChatModel model = mock(ChatModel.class); @@ -126,10 +111,16 @@ class GoalJsonGraphIntegrationTest { arguments = json.writeValueAsString(Map.of("criterionKey", "r", "expectedRequirementRevision", revision.get(), "artifactId", last.path("artifactId").asText(), "expectedGeneration", last.path("generation").asText())); } - case 4 -> { assertTrue(last.path("acceptanceEligible").asBoolean(), String.valueOf(last)); name = "completeGoal"; } + case 4 -> { + assertTrue(last.path("acceptanceEligible").asBoolean(), String.valueOf(last)); + if (automatic) return new ChatResponse(List.of(new Generation(new AssistantMessage("Managed JSON is ready for final validation.")))); + name = "completeGoal"; + } default -> { - if (last != null) assertEquals("completed", last.path("status").asText(), String.valueOf(last)); - assertEquals(GoalStatus.COMPLETED, goals.getById(goal.getId()).getStatus()); + if (!automatic) { + if (last != null) assertEquals("completed", last.path("status").asText(), String.valueOf(last)); + assertEquals(GoalStatus.COMPLETED, goals.getById(goal.getId()).getStatus()); + } return new ChatResponse(List.of(new Generation(new AssistantMessage("Managed JSON fixture completed.")))); } } @@ -138,15 +129,7 @@ class GoalJsonGraphIntegrationTest { }; when(model.call(any(Prompt.class))).thenAnswer(script); when(model.stream(any(Prompt.class))).thenAnswer(invocation -> Flux.just(script.answer(invocation))); - CompiledGraph graph = ReflectionTestUtils.invokeMethod(builder, plan ? "buildPlanExecuteGraph" : "buildReActGraph", toolSet, model, 12, null); - assertNotNull(graph); - vip.mate.agent.BaseAgent agent = plan - ? new vip.mate.agent.graph.plan.StateGraphPlanExecuteAgent(mock(ChatClient.class), conversations, graph, planning, model, window, toolSet) - : new StateGraphReActAgent(mock(ChatClient.class), conversations, graph, model, window, toolSet); - ReflectionTestUtils.setField(agent, "agentId", "1"); - ReflectionTestUtils.setField(agent, "agentName", "JSON graph fixture"); - ReflectionTestUtils.setField(agent, "systemPrompt", "Follow the user's managed JSON requirements."); - ReflectionTestUtils.setField(agent, "goalService", goals); + var agent = graphAgent(plan, toolSet, model); ChatOriginHolder.set(origin); try { assertNotNull(agent.chat("Produce and check the managed JSON report.", conversation)); } finally { ChatOriginHolder.clear(); } @@ -158,6 +141,81 @@ class GoalJsonGraphIntegrationTest { } assertFalse(progress.load(conversation).asMap().containsKey("auto_getManagedGoalJsonSlots"), "Current acceptance reads must not become a permanent done step that discourages reloading"); - assertTrue(calls.get() >= 6 && calls.get() <= 10, "Bounded scripted model calls: " + calls.get()); + assertTrue(calls.get() >= (automatic ? 5 : 6) && calls.get() <= 10, "Bounded scripted model calls: " + calls.get()); } + @org.junit.jupiter.params.ParameterizedTest + @org.junit.jupiter.params.provider.ValueSource(booleans = {false, true}) + void automaticGraphCannotPromoteAPassingSemanticVerdictWithoutManagedBytes(boolean plan) throws Exception { + Fixture fixture = configuredGoal(false); + when(evaluator.evaluate(any(), anyList(), anyString())).thenReturn(new GoalEvaluationResult( + 1, "PASS from offline semantic fixture", "completed", true, "fixture", 1, 0, + List.of(new GoalChecklistVerdict.CriterionVerdict("C1", true, "fixture only")), null)); + var toolSet = tools.getEnabledToolSet().withAllowedToolsOnly(Set.of("getManagedGoalJsonSlots", "publishManagedGoalJson", "checkManagedGoalJson", "completeGoal")); + ChatModel model = mock(ChatModel.class); + AtomicInteger calls = new AtomicInteger(); + org.mockito.stubbing.Answer script = invocation -> { + int step = calls.getAndIncrement(); + if (plan && step == 0) return new ChatResponse(List.of(new Generation(new AssistantMessage( + "{\"needs_planning\":true,\"steps\":[\"Read current managed JSON requirements and report status\"]}")))); + if (plan) step--; + if (step == 0) return new ChatResponse(List.of(new Generation(AssistantMessage.builder().content("") + .toolCalls(List.of(new AssistantMessage.ToolCall("read-current", "function", "getManagedGoalJsonSlots", "{}"))).build()))); + return new ChatResponse(List.of(new Generation(new AssistantMessage("PASS. All requirements are completed.")))); + }; + when(model.call(any(Prompt.class))).thenAnswer(script); + when(model.stream(any(Prompt.class))).thenAnswer(invocation -> Flux.just(script.answer(invocation))); + var agent = graphAgent(plan, toolSet, model); + ChatOriginHolder.set(fixture.origin()); + try { assertNotNull(agent.chat("Read the current managed JSON requirements and report status.", fixture.conversation())); } + finally { ChatOriginHolder.clear(); } + verify(evaluator, atLeastOnce()).evaluate(any(), anyList(), anyString()); + assertEquals(GoalStatus.ACTIVE, goals.getById(fixture.goal().getId()).getStatus()); + assertTrue(goals.getById(fixture.goal().getId()).isJsonAcceptanceRequired()); + assertEquals("NO_ARTIFACT", bindings.state(fixture.goal().getId(), fixture.username()).getFirst().status()); + assertTrue(goals.listEvents(fixture.goal().getId(), 30).stream().noneMatch(e -> "completed".equals(e.getEventType()))); + assertTrue(calls.get() < 10, "Bounded offline rejection flow: " + calls.get()); + } + + private record Fixture(String username, String conversation, GoalEntity goal, + GoalRunCoordinator.ClaimedRun run, ChatOrigin origin) { } + + private Fixture configuredGoal(boolean scheduled) { + String username = "graph-" + UUID.randomUUID(); + long userId = IdWorker.getId(); + String conversation = UUID.randomUUID().toString(); + jdbc.update("INSERT INTO mate_user(id,username,password,enabled,role,create_time,update_time,deleted) VALUES (?,?,?,TRUE,'user',CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,0)", userId, username, "unused"); + jdbc.update("INSERT INTO mate_conversation(id,conversation_id,username,workspace_id,agent_id,create_time,update_time,deleted) VALUES (?,?,?,1,1,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,0)", IdWorker.getId(), conversation, username); + var request = new GoalCreateRequest(); request.setConversationId(conversation); request.setAgentId(1L); request.setWorkspaceId(1L); + request.setTitle("Managed graph fixture"); request.setDescription("Produce JSON"); request.setPersistentExecution(scheduled); request.setAutoFollowupEnabled(false); + GoalEntity goal = goals.create(request, username); + goals.appendCriterion(goal.getId(), "Produce the report", username); + 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); + requirements.configure(goal.getId(), "r", new GoalJsonAcceptanceService.ConfigureRequest(0L, "report", List.of("summary")), username); + GoalRunCoordinator.ClaimedRun run = null; + ChatOrigin origin = ChatOrigin.web(conversation, username, 1L, null, null, userId).withAgent(1L); + if (scheduled) { + jdbc.update("UPDATE mate_agent_goal SET auto_followup_enabled=TRUE WHERE id=?", goal.getId()); + var now = java.time.LocalDateTime.now(); continuations.discover(now); + run = coordinator.claim(continuations.get(goal.getId()), goals.getById(goal.getId()), now); + assertNotNull(run); assertTrue(coordinator.markRunning(run, now)); + origin = ChatOrigin.web(conversation, username, 1L, null).withAgent(1L) + .withExecutionAttribution(new vip.mate.agent.context.ExecutionAttribution(goal.getId(), run.attempt().id(), null, null, run.attempt().leaseToken())); + } + return new Fixture(username, conversation, goal, run, origin); + } + + private vip.mate.agent.BaseAgent graphAgent(boolean plan, vip.mate.agent.AgentToolSet toolSet, ChatModel model) { + CompiledGraph graph = ReflectionTestUtils.invokeMethod(builder, plan ? "buildPlanExecuteGraph" : "buildReActGraph", toolSet, model, 12, null); + assertNotNull(graph); + vip.mate.agent.BaseAgent agent = plan + ? new vip.mate.agent.graph.plan.StateGraphPlanExecuteAgent(mock(ChatClient.class), conversations, graph, planning, model, window, toolSet) + : new StateGraphReActAgent(mock(ChatClient.class), conversations, graph, model, window, toolSet); + ReflectionTestUtils.setField(agent, "agentId", "1"); + ReflectionTestUtils.setField(agent, "agentName", "JSON graph fixture"); + ReflectionTestUtils.setField(agent, "systemPrompt", "Follow the user's managed JSON requirements."); + ReflectionTestUtils.setField(agent, "goalService", goals); + return agent; + } + }