mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 11:13:43 +08:00
The PRIVATE_ITEMS list contained the bare 'test' entry, which rsync interprets as 'any directory named test at any depth' — so it caught the root-level /test/ scratch directory (intended) AND every src/test/ under each module (not intended). Pattern is already anchored to /test (root-only). This commit rsyncs the accumulated src/test/ tree forward so opensource has the unit tests that have been written / updated against existing src/main/ code since the pattern regression. Going forward each per-commit sync will carry src/test/ files along with the main change.
124 lines
5.7 KiB
Java
124 lines
5.7 KiB
Java
package vip.mate.agent;
|
|
|
|
import org.junit.jupiter.api.DisplayName;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.springframework.ai.support.ToolCallbacks;
|
|
import org.springframework.ai.tool.ToolCallback;
|
|
import org.springframework.ai.tool.annotation.Tool;
|
|
import org.springframework.ai.tool.annotation.ToolParam;
|
|
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
|
/**
|
|
* Regression test for <a href="https://github.com/matevip/mateclaw/issues/24">issue #24</a>.
|
|
* <p>
|
|
* Symptom: agent tool bindings persisted under the Java class name
|
|
* (e.g. {@code BrowserUseTool}, which is what {@code mate_tool.name} stores) or the
|
|
* Spring bean name (e.g. {@code browserUseTool}) had no effect at runtime, because the
|
|
* graph runtime matches against the {@code @Tool} function name (e.g. {@code browser_use}).
|
|
* <p>
|
|
* Fix: {@link AgentToolSet} builds an alias index so any of these three identifiers
|
|
* resolves to the same callback. This test pins that contract.
|
|
*/
|
|
class AgentToolSetTest {
|
|
|
|
/** Fixture: a bean exposing two {@code @Tool} methods, mirroring real tools like
|
|
* {@code BrowserUseTool} ({@code browser_use}, {@code browser_screenshot}, ...). */
|
|
static class FakeBrowserTool {
|
|
@Tool(description = "Open a URL in the browser")
|
|
public String browser_use(@ToolParam(description = "url to open") String url) {
|
|
return "opened " + url;
|
|
}
|
|
|
|
@Tool(description = "Take a screenshot")
|
|
public String browser_screenshot() {
|
|
return "shot.png";
|
|
}
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("Issue #24: class name and bean name resolve to the same callbacks as the @Tool function name")
|
|
void aliasIndex_resolvesClassNameAndBeanNameToFunctionCallbacks() {
|
|
FakeBrowserTool bean = new FakeBrowserTool();
|
|
List<ToolCallback> callbacks = List.of(ToolCallbacks.from(bean));
|
|
assertEquals(2, callbacks.size(), "fixture should expose 2 @Tool methods");
|
|
|
|
AgentToolSet base = AgentToolSet.fromCallbacks(
|
|
List.of(bean),
|
|
callbacks,
|
|
b -> "fakeBrowserTool" // simulate Spring bean-name lookup
|
|
);
|
|
assertEquals(2, base.size());
|
|
|
|
// (A) Function name — the historically-correct form
|
|
AgentToolSet byFn = base.withAllowedToolsOnly(Set.of("browser_use"));
|
|
assertEquals(1, byFn.size());
|
|
assertEquals("browser_use", byFn.callbacks().get(0).getToolDefinition().name());
|
|
|
|
// (B) Spring bean name → expands to ALL @Tool methods on that bean
|
|
AgentToolSet byBean = base.withAllowedToolsOnly(Set.of("fakeBrowserTool"));
|
|
assertEquals(2, byBean.size(),
|
|
"bean name should pull in every @Tool method on the class");
|
|
|
|
// (C) Java class simple name (this is what mate_tool.name actually stores —
|
|
// e.g. 'BrowserUseTool' — and what the legacy bug saved into mate_agent_tool.tool_name)
|
|
AgentToolSet byClass = base.withAllowedToolsOnly(Set.of("FakeBrowserTool"));
|
|
assertEquals(2, byClass.size(),
|
|
"class simple name should expand to all bean methods (this is the issue #24 fix)");
|
|
|
|
// (D) Mixed: known + unknown aliases. Unknowns are silently dropped — callers persist
|
|
// stale data and we'd rather degrade gracefully than throw.
|
|
AgentToolSet mixed = base.withAllowedToolsOnly(Set.of("FakeBrowserTool", "nonexistent_tool"));
|
|
assertEquals(2, mixed.size());
|
|
|
|
// (E) Empty allow-list yields empty tool set (NOT global default — only null does that)
|
|
AgentToolSet none = base.withAllowedToolsOnly(Set.of());
|
|
assertEquals(0, none.size());
|
|
|
|
// (F) null = no per-agent binding → fall back to global default (every tool visible)
|
|
AgentToolSet allDefault = base.withAllowedToolsOnly(null);
|
|
assertEquals(2, allDefault.size());
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("withDeniedToolsFiltered accepts function / bean / class names interchangeably")
|
|
void deniedAliases_areToleranceOfNamingConvention() {
|
|
FakeBrowserTool bean = new FakeBrowserTool();
|
|
List<ToolCallback> callbacks = List.of(ToolCallbacks.from(bean));
|
|
|
|
AgentToolSet base = AgentToolSet.fromCallbacks(
|
|
List.of(bean),
|
|
callbacks,
|
|
b -> "fakeBrowserTool"
|
|
);
|
|
|
|
// Deny by class name: removes both @Tool methods on that class
|
|
AgentToolSet none = base.withDeniedToolsFiltered(Set.of("FakeBrowserTool"));
|
|
assertEquals(0, none.size());
|
|
|
|
// Deny by single function name: only that method drops
|
|
AgentToolSet justOne = base.withDeniedToolsFiltered(Set.of("browser_screenshot"));
|
|
assertEquals(1, justOne.size());
|
|
assertEquals("browser_use", justOne.callbacks().get(0).getToolDefinition().name());
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("Two-arg fromCallbacks (no bean-name resolver): function name + class simple name still indexed (Spring bean name is not)")
|
|
void twoArgFactory_indexesByFunctionNameAndClassName() {
|
|
FakeBrowserTool bean = new FakeBrowserTool();
|
|
List<ToolCallback> callbacks = List.of(ToolCallbacks.from(bean));
|
|
|
|
AgentToolSet noResolver = AgentToolSet.fromCallbacks(List.of(bean), callbacks);
|
|
|
|
// Function name resolves
|
|
assertEquals(1, noResolver.withAllowedToolsOnly(Set.of("browser_use")).size());
|
|
// Class simple name resolves too — derived from bean.getClass() reflection, no resolver needed
|
|
assertEquals(2, noResolver.withAllowedToolsOnly(Set.of("FakeBrowserTool")).size());
|
|
// Spring bean name does NOT resolve without a resolver (no source for it)
|
|
assertEquals(0, noResolver.withAllowedToolsOnly(Set.of("fakeBrowserTool")).size());
|
|
}
|
|
}
|