Files
Agent-Squad/adapters/intent/catalog_test.go
T
weichenandCursor a182d7662b Move cross-library squad code into adapter modules
Why: the squad core has to compile without agent-native or agent-intent. Ports stay in the core; adapters and the CLI own the concrete clients.
Evidence: GOWORK=off go test -count=1 ./squad/ ./store/ ./textextract/ ./schema/ passed, and the intent, sqlite, native, and cmd modules passed their own go test.
Impact: core go.mod keeps error-code-system only. Nested gitlinks stay. Callers register the native backend through agent-squad/adapters/native.
Refs: D1
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-03 13:40:58 +00:00

145 lines
4.9 KiB
Go
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 squadintent
import (
"context"
"strings"
"testing"
"testing/fstest"
"agent-intent/intent"
"agent-squad/squad"
)
// catalogFS:带槽位(label/question/enum)的可调用能力 + 守卫。
func catalogFS() fstest.MapFS {
return fstest.MapFS{"intents.json": &fstest.MapFile{Data: []byte(`{
"intents": [
{"id": "guard.config_change", "domain": "guard", "version": 1,
"matcher": {"includes_any": ["系统配置"]}},
{"id": "demo.run.status_query", "domain": "demo", "version": 1,
"description": "查询运行进度",
"matcher": {"includes_any": ["进度"]},
"handler": "taskrun.query_status",
"slots": [
{"key": "run_id", "required": true, "pattern": "(run-\\d+)",
"label": "运行编号", "question": "要查询哪个运行?请提供 run 编号"}
]}
]
}`)}}
}
func newCatalogGate(t *testing.T) *Gate {
t.Helper()
g, err := New(context.Background(), Options{
IntentOptions: intent.Options{GuardDomains: []string{"guard"}, ProtectedDomains: []string{"guard"}},
FS: catalogFS(),
Roots: []string{"."},
})
if err != nil {
t.Fatal(err)
}
return g
}
func TestGateCatalogMapsSpecsAndSlots(t *testing.T) {
g := newCatalogGate(t)
c := g.Catalog()
if c == nil || len(c.Specs) != 1 {
t.Fatalf("catalog specs: %+v", c)
}
view, ok := c.Get("demo.run.status_query")
if !ok || view.Handler != "taskrun.query_status" || len(view.RequiredSlots) != 1 || view.RequiredSlots[0] != "run_id" {
t.Fatalf("view: %+v", view)
}
if len(view.Slots) != 1 || view.Slots[0].Question == "" || view.Slots[0].Label != "运行编号" {
t.Fatalf("slot view: %+v", view.Slots)
}
if _, ok := c.Get("guard.config_change"); ok {
t.Fatal("guard spec must not appear in catalog")
}
if !strings.Contains(c.Markdown, "提问建议:要查询哪个运行") {
t.Fatalf("markdown missing question: %s", c.Markdown)
}
}
func TestRunnerDispatchesToHandlerAndFailsClosed(t *testing.T) {
g := newCatalogGate(t)
var got squad.IntentStep
runner := g.Runner(map[string]IntentHandler{
"taskrun.query_status": func(ctx context.Context, step squad.IntentStep) (string, error) {
got = step
return "# 进度\n\nrun-42 已完成,正文满足最小长度要求用于质检演示。\n", nil
},
})
res, err := runner.Run(context.Background(), squad.IntentStep{
IntentID: "demo.run.status_query", Slots: map[string]string{"run_id": "run-42"},
Targets: []string{"output.md"},
})
if err != nil || res == nil || !res.Success {
t.Fatalf("run: %+v %v", res, err)
}
if got.Slots["run_id"] != "run-42" || string(res.Patches["output.md"]) == "" {
t.Fatalf("handler dispatch: %+v", got)
}
// 未知意图与未注册 handler:fail-closed(无 handler 的 runner 拒绝执行)。
if _, err := runner.Run(context.Background(), squad.IntentStep{IntentID: "demo.nope"}); err == nil {
t.Fatal("expected unknown intent error")
}
bare := g.Runner(nil)
if _, err := bare.Run(context.Background(), squad.IntentStep{IntentID: "demo.run.status_query", Slots: map[string]string{"run_id": "run-1"}}); err == nil {
t.Fatal("expected unregistered handler error")
} else if !strings.Contains(err.Error(), "fail-closed") {
t.Fatalf("error: %v", err)
}
}
func TestEchoRunnerMaterializesCall(t *testing.T) {
g := newCatalogGate(t)
res, err := g.EchoRunner().Run(context.Background(), squad.IntentStep{
IntentID: "demo.run.status_query", Slots: map[string]string{"run_id": "run-7"},
Targets: []string{"output.md"}, Title: "查询进度",
})
if err != nil || !res.Success {
t.Fatalf("echo run: %+v %v", res, err)
}
text := string(res.Patches["output.md"])
for _, want := range []string{"EchoRunner", "demo.run.status_query", "run-7", "未真实执行"} {
if !strings.Contains(text, want) {
t.Fatalf("echo text missing %q: %s", want, text)
}
}
}
func TestNilGateCatalogAndRunner(t *testing.T) {
var g *Gate
if g.Catalog() != nil {
t.Fatal("nil gate catalog must be nil")
}
if _, err := g.EchoRunner().Run(context.Background(), squad.IntentStep{IntentID: "x"}); err == nil {
t.Fatal("nil gate runner must fail-closed")
}
}
func TestR2GateReloadSeesNewIntent(t *testing.T) {
fsys := fstest.MapFS{"intents.json": &fstest.MapFile{Data: []byte(`{"intents":[{"id":"demo.old","domain":"demo","version":1,"matcher":{"includes_any":["旧"]}}]}`)}}
g, err := New(context.Background(), Options{
IntentOptions: intent.Options{},
FS: fsys,
Roots: []string{"."},
})
if err != nil {
t.Fatal(err)
}
if _, ok := g.Snapshot().SpecByID("demo.new"); ok {
t.Fatal("new spec exists before reload")
}
fsys["intents.json"].Data = []byte(`{"intents":[{"id":"demo.old","domain":"demo","version":1,"matcher":{"includes_any":["旧"]}},{"id":"demo.new","domain":"demo","version":1,"matcher":{"includes_any":["新"]}}]}`)
if _, err := g.Reload(context.Background()); err != nil {
t.Fatal(err)
}
if _, ok := g.Snapshot().SpecByID("demo.new"); !ok {
t.Fatal("reload did not publish demo.new")
}
}