Files
Agent-Intent/intent/spec_test.go
T
weichenandCursor 3130190acb
ci / vet-and-test (pull_request) Failing after 2s
ci / docker-checkout-hostgateway (pull_request) Successful in 2s
Reject invalid classifier refinements and scope slot validators
Why: Refine could swap handlers or emit out-of-range scores, Ambiguous tops skipped slot extraction, and the process-global validator map coupled independent registries.
Evidence: GOWORK=off go vet ./... && go test -count=1 ./... && go test -race ./... passed, including TestRecognizeClassifierRejectsBadRefinement, TestRecognizeClassifierRebuildsTamperedHandler, TestRecognizeClassifierRefinedTopGetsSlots, and TestScopedSlotValidatorsDoNotInterfere. error-code-system and logging-system advanced to a3ee67b and 49b5d8b (docs-only).
Impact: illegal Refine results stay Ambiguous; a resolved Top is slotted; Options.SlotValidators wins over the deprecated global table.
Refs: I1, I2, I6
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-03 00:47:16 +00:00

117 lines
3.9 KiB
Go

package intent
import (
"errors"
"testing"
)
func validSpec() IntentSpec {
return IntentSpec{
ID: "demo.run.status_query",
Domain: "demo",
Version: 1,
Matcher: MatchSpec{IncludesAny: []string{"进度"}},
}
}
func TestValidateSpecAcceptsValid(t *testing.T) {
lim := Options{}.normalized().limits()
if err := validateSpec(validSpec(), lim, nil); err != nil {
t.Fatalf("valid spec rejected: %v", err)
}
}
func TestValidateSpecDefaultsNoLimits(t *testing.T) {
// 零值 limits 跳过数量上限(供意图较少的宿主直接校验)。
if err := validateSpec(validSpec(), limits{}, nil); err != nil {
t.Fatalf("valid spec rejected without limits: %v", err)
}
}
func TestValidateSpecRejections(t *testing.T) {
lim := Options{}.normalized().limits()
cases := []struct {
name string
mut func(*IntentSpec)
field string
}{
{"empty_domain", func(s *IntentSpec) { s.Domain = "" }, "domain"},
{"bad_domain_case", func(s *IntentSpec) { s.Domain = "Demo" }, "domain"},
{"id_prefix_mismatch", func(s *IntentSpec) { s.ID = "other.status_query" }, "id"},
{"bad_mnemonic", func(s *IntentSpec) { s.ID = "demo.Status-Query" }, "id"},
{"zero_version", func(s *IntentSpec) { s.Version = 0 }, "version"},
{"bad_risk", func(s *IntentSpec) { s.Risk = "extreme" }, "risk"},
{"empty_matcher", func(s *IntentSpec) { s.Matcher = MatchSpec{} }, "matcher"},
{"empty_term", func(s *IntentSpec) { s.Matcher = MatchSpec{IncludesAny: []string{" "}} }, "matcher.includes_any"},
{"bad_pattern", func(s *IntentSpec) { s.Matcher = MatchSpec{IncludesAny: []string{"a"}, Patterns: []string{"["}} }, "matcher.patterns[0]"},
{"too_many_patterns", func(s *IntentSpec) {
s.Matcher = MatchSpec{IncludesAny: []string{"a"}, Patterns: make([]string, lim.maxPatternsPerSpec+1)}
for i := range s.Matcher.Patterns {
s.Matcher.Patterns[i] = "x"
}
}, "matcher.patterns"},
{"bad_slot_key", func(s *IntentSpec) {
s.Slots = []SlotSpec{{Key: "RunID", Pattern: "(x)"}}
}, "slots[0].key"},
{"bad_slot_pattern", func(s *IntentSpec) {
s.Slots = []SlotSpec{{Key: "run_id", Pattern: "["}}
}, "slots[0].pattern"},
{"slot_group_out_of_range", func(s *IntentSpec) {
s.Slots = []SlotSpec{{Key: "run_id", Pattern: "(x)", Group: 2}}
}, "slots[0].group"},
{"base_out_of_range", func(s *IntentSpec) {
s.Confidence = ConfidenceSpec{Base: 1.5}
}, "confidence.base"},
{"bonus_out_of_range", func(s *IntentSpec) {
s.Confidence = ConfidenceSpec{PatternBonus: 1.0}
}, "confidence.pattern_bonus"},
{"threshold_out_of_range", func(s *IntentSpec) {
s.Confidence = ConfidenceSpec{Threshold: -0.1}
}, "confidence.threshold"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
spec := validSpec()
tc.mut(&spec)
err := validateSpec(spec, lim, nil)
if err == nil {
t.Fatalf("expected rejection")
}
var e *Error
if !errors.As(err, &e) || e.Code != InvalidSpec {
t.Fatalf("expected intent.invalid_spec, got %v", err)
}
if got, _ := e.Fields["field"].(string); got != tc.field && !hasPrefix(got, tc.field) {
t.Fatalf("expected field %q, got %q (fields=%v)", tc.field, got, e.Fields)
}
})
}
}
func hasPrefix(s, p string) bool {
return len(s) >= len(p) && s[:len(p)] == p
}
func TestValidateSpecIncludesAllOnly(t *testing.T) {
// 仅 includes_all 也是合法正向条件。
spec := validSpec()
spec.Matcher = MatchSpec{IncludesAll: []string{"任务", "进度"}}
if err := validateSpec(spec, Options{}.normalized().limits(), nil); err != nil {
t.Fatalf("includes_all-only spec rejected: %v", err)
}
}
func TestErrorCodeDuckInterface(t *testing.T) {
err := newError(InvalidSpec, "msg", "id", "demo.x")
if err.ErrorCode() != InvalidSpec {
t.Fatalf("code mismatch")
}
fields := err.ErrorFields()
if fields["id"] != "demo.x" {
t.Fatalf("fields mismatch: %v", fields)
}
if err.Error() == "" {
t.Fatal("empty error string")
}
}