Files
Agent-Squad/squadintent/catalog_test.go
T
weichen b6ff0ab55f
ci / vet-and-test (push) Failing after 2s
ci / docker-checkout-hostgateway (push) Failing after 2s
Add catalog-driven orchestration: intent catalog injection
Details (original message):
Add catalog-driven orchestration: intent catalog injection, deterministic plan validation, and system-function steps.
2026-09-29 16:37:46 +00:00

123 lines
4.1 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")
}
}