mateclaw/mateclaw-server/src/test/java/vip/mate/agent/AgentAuthoringToolTest.java

135 lines
5.6 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package vip.mate.agent;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import vip.mate.MateClawApplication;
import vip.mate.agent.binding.service.AgentBindingService;
import vip.mate.agent.context.ChatOrigin;
import vip.mate.agent.model.AgentEntity;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Exercises the agent authoring tool against a real Spring context so the
* create-then-bind sequence runs through {@link AgentService} and
* {@link AgentBindingService} exactly as it would at chat time. Builtin
* skills are seeded on startup, so skill-name resolution hits real rows.
*/
@SpringBootTest(
classes = MateClawApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.NONE
)
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:h2:mem:agent_authoring_test_${random.uuid};MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;DB_CLOSE_DELAY=-1",
"spring.ai.dashscope.api-key=test-key",
"spring.main.web-application-type=none"
})
class AgentAuthoringToolTest {
private static final AtomicLong WS_SEQ = new AtomicLong(70_000L);
@Autowired
private AgentAuthoringTool tool;
@Autowired
private AgentService agentService;
@Autowired
private AgentBindingService bindingService;
private final ObjectMapper mapper = new ObjectMapper();
private long workspaceId;
@BeforeEach
void setUp() {
workspaceId = WS_SEQ.getAndIncrement();
}
private ToolContext ctxFor(long ws) {
return ChatOrigin.web("conv-" + ws, "123", ws, null).toToolContext();
}
@Test
@DisplayName("create_employee 在 ChatOrigin 的 workspace 内创建 Agent无能力名时继承全局默认")
void createsGeneralistInOriginWorkspace() throws Exception {
String json = tool.create_employee(
"generalist-helper", "general assistant", "You help with anything.",
null, null, null, null, ctxFor(workspaceId));
JsonNode node = mapper.readTree(json);
long agentId = Long.parseLong(node.get("agentId").asText());
AgentEntity created = agentService.getAgent(agentId);
assertEquals("generalist-helper", created.getName());
assertEquals(workspaceId, created.getWorkspaceId());
assertEquals("react", created.getAgentType());
// Creator attribution parsed from the numeric requesterId.
assertEquals(123L, created.getCreatorUserId());
// No bindings declared → inherits global defaults (null sentinel).
assertNull(bindingService.getBoundSkillIds(agentId));
assertNull(bindingService.getBoundToolNames(agentId));
}
@Test
@DisplayName("create_employee 绑定指定的内置技能(按名解析为 id")
void bindsRequestedBuiltinSkill() throws Exception {
String json = tool.create_employee(
"planner-employee", "planning specialist", "You break goals into plans.",
"react", null, "[\"make_plan\"]", null, ctxFor(workspaceId));
JsonNode node = mapper.readTree(json);
long agentId = Long.parseLong(node.get("agentId").asText());
Set<Long> boundSkills = bindingService.getBoundSkillIds(agentId);
assertNotNull(boundSkills, "declaring a skill must create a binding set");
assertEquals(1, boundSkills.size());
// The summary echoes the bound skill name.
assertTrue(node.get("skillsBound").toString().contains("make_plan"));
}
@Test
@DisplayName("create_employee 缺少 workspace 上下文时拒绝执行")
void rejectsWithoutWorkspace() {
String result = tool.create_employee(
"no-ws", "x", "y", null, null, null, null, ChatOrigin.EMPTY.toToolContext());
assertTrue(result.startsWith("[error]"));
}
@Test
@DisplayName("create_employee 重名返回友好错误而非抛出")
void duplicateNameReturnsFriendlyError() {
ToolContext ctx = ctxFor(workspaceId);
tool.create_employee("dup-employee", "first", "p", null, null, null, null, ctx);
String second = tool.create_employee("dup-employee", "second", "p", null, null, null, null, ctx);
assertTrue(second.startsWith("[error]"), "duplicate name should surface as a friendly error");
}
@Test
@DisplayName("list_capability_catalog 返回技能与工具清单")
void catalogReturnsSkillsAndTools() throws Exception {
String json = tool.list_capability_catalog(ctxFor(workspaceId));
JsonNode node = mapper.readTree(json);
assertTrue(node.has("skills"));
assertTrue(node.has("tools"));
assertTrue(node.get("skills").isArray());
// make_plan is a seeded builtin skill, so the catalog must surface it.
boolean hasMakePlan = false;
for (JsonNode s : node.get("skills")) {
if ("make_plan".equals(s.path("name").asText())) { hasMakePlan = true; break; }
}
assertTrue(hasMakePlan, "seeded builtin skill make_plan should appear in the catalog");
}
}