package core import ( "errors" "strings" "testing" ) func TestValidatePattern(t *testing.T) { tests := []struct { name string in string ok bool }{ {"subtree", "notes/**", true}, {"literal", "specs/0007.md", true}, {"star in component", "specs/*.md", true}, {"question mark", "specs/000?.md", true}, {"leading globstar", "**/*.md", true}, {"interior globstar", "notes/**/drafts/*.md", true}, {"bare globstar", "**", true}, {"bare star", "*", true}, {"cyrillic", "спеки/**", true}, {"bracket is literal", "specs/[draft].md", true}, {"empty", "", false}, {"too long", strings.Repeat("a", MaxPathLen+1), false}, {"absolute", "/notes/**", false}, {"trailing slash", "notes/", false}, {"empty component", "notes//**", false}, {"traversal", "../notes/**", false}, {"interior traversal", "notes/../specs/**", false}, {"dot component", "./notes/**", false}, {"backslash", `notes\**`, false}, // "a**b" has no obvious meaning; guessing one is how a policy comes to // mean something its author did not intend. {"globstar not whole component", "notes**", false}, {"globstar prefixed", "**notes/x", false}, {"globstar suffixed", "notes/**x", false}, {"triple star", "notes/***", false}, {"nul byte", "notes/\x00**", false}, {"newline", "notes/**\n", false}, {"rtl override", "notes/\u202e**", false}, {"invalid utf-8", "notes/\xff\xfe", false}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { err := ValidatePattern(tc.in) if (err == nil) != tc.ok { t.Fatalf("ValidatePattern(%q) = %v, want ok=%v", tc.in, err, tc.ok) } if err != nil && !errors.Is(err, ErrInvalidPattern) { t.Fatalf("error %v is not ErrInvalidPattern", err) } }) } } func TestMatchPattern(t *testing.T) { tests := []struct { pat string path string want bool }{ // Subtree matching, the shape the design's example uses. {"notes/**", "notes/a.md", true}, {"notes/**", "notes/2026/07/a.md", true}, {"notes/**", "notes", true}, // "**" matches zero components; documented {"notes/**", "notesx/a.md", false}, {"notes/**", "specs/a.md", false}, {"notes/**", "a/notes/b.md", false}, {"reports/**", "reports/weekly/2026-07.md", true}, // "*" never crosses a component boundary. {"specs/*.md", "specs/0007.md", true}, {"specs/*.md", "specs/sub/0007.md", false}, {"specs/*", "specs/0007.md", true}, {"specs/*", "specs", false}, {"*", "a.md", true}, {"*", "a/b.md", false}, {"*.md", "a.md", true}, {"*.md", "a.markdown", false}, // "?" is exactly one character, counted in runes. {"specs/000?.md", "specs/0007.md", true}, {"specs/000?.md", "specs/00007.md", false}, {"?.md", "я.md", true}, {"??.md", "я.md", false}, // "**" in the middle and at the front. {"**/*.md", "a.md", true}, {"**/*.md", "notes/a.md", true}, {"**/*.md", "a/b/c.md", true}, {"**/*.md", "a/b/c.png", false}, {"notes/**/*.md", "notes/a.md", true}, {"notes/**/*.md", "notes/2026/a.md", true}, {"notes/**/drafts/*.md", "notes/x/y/drafts/a.md", true}, {"notes/**/drafts/*.md", "notes/drafts/a.md", true}, {"notes/**/drafts/*.md", "notes/drafts/sub/a.md", false}, {"**", "anything/at/all.md", true}, // Backtracking within a component. {"a*b*c", "abc", true}, {"a*b*c", "axxbxxc", true}, {"a*b*c", "axxbxx", false}, {"*a*a*a*a*b", strings.Repeat("a", 40), false}, // Literals, including the deliberately unsupported bracket syntax. {"specs/0007.md", "specs/0007.md", true}, {"specs/0007.md", "specs/0008.md", false}, {"specs/[ab].md", "specs/a.md", false}, {"specs/[ab].md", "specs/[ab].md", true}, // An invalid pattern matches nothing rather than everything. {"", "a.md", false}, {"/notes/**", "notes/a.md", false}, {"notes**", "notes/a.md", false}, {"../**", "a.md", false}, } for _, tc := range tests { t.Run(tc.pat+" ~ "+tc.path, func(t *testing.T) { if got := MatchPattern(tc.pat, tc.path); got != tc.want { t.Fatalf("MatchPattern(%q, %q) = %v, want %v", tc.pat, tc.path, got, tc.want) } }) } } func TestParsePolicy(t *testing.T) { const full = `review: auto_merge: [notes/**, reports/**] schema: required: [id, title, status] status: [draft, review, superseded] ` p, err := ParsePolicy([]byte(full)) if err != nil { t.Fatalf("ParsePolicy: %v", err) } if got, want := strings.Join(p.Review.AutoMerge, ","), "notes/**,reports/**"; got != want { t.Fatalf("auto_merge = %q, want %q", got, want) } if got, want := joinStatuses(p.Schema.Status), "draft|review|superseded"; got != want { t.Fatalf("schema.status = %q, want %q", got, want) } if got, want := strings.Join(p.Schema.Required, ","), "id,title,status"; got != want { t.Fatalf("schema.required = %q, want %q", got, want) } } func TestParsePolicyDefaults(t *testing.T) { tests := []struct { name string in string }{ {"empty file", ""}, {"comment only", "# nothing configured yet\n"}, {"review section only", "review:\n auto_merge: [notes/**]\n"}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { p, err := ParsePolicy([]byte(tc.in)) if err != nil { t.Fatalf("ParsePolicy: %v", err) } // An omitted schema means the house contract, not "no status is // allowed and therefore no document can ever validate". if got, want := joinStatuses(p.Schema.Status), "draft|review|superseded"; got != want { t.Fatalf("schema.status = %q, want the default %q", got, want) } if got, want := strings.Join(p.Schema.Required, ","), "id,title,status"; got != want { t.Fatalf("schema.required = %q, want the default %q", got, want) } }) } } func TestParsePolicyEmptyListIsHonoured(t *testing.T) { // `required: []` is an explicit "no required keys", which YAML tells apart // from an absent key by nil-ness. Overwriting it with the default would // make the setting unexpressible. p, err := ParsePolicy([]byte("schema:\n required: []\n")) if err != nil { t.Fatalf("ParsePolicy: %v", err) } if len(p.Schema.Required) != 0 { t.Fatalf("schema.required = %v, want an empty list", p.Schema.Required) } if len(p.Schema.Status) != 3 { t.Fatalf("schema.status = %v, want the default enum", p.Schema.Status) } } func TestParsePolicyErrors(t *testing.T) { tests := []struct { name string in string }{ // A typo'd key would parse fine and silently enforce nothing. {"typo'd auto_merge", "review:\n auto-merge: [notes/**]\n"}, {"unknown top-level key", "reviews:\n auto_merge: [notes/**]\n"}, {"unknown schema key", "schema:\n requires: [id]\n"}, {"approvers reintroduced", "review:\n approvers: [~bigbes]\n"}, {"not a mapping", "- review\n"}, {"scalar", "just a string\n"}, {"broken yaml", "review:\n auto_merge: [notes/**\n"}, {"tab indentation", "review:\n\tauto_merge: [a]\n"}, {"two documents", "review:\n auto_merge: [notes/**]\n---\nschema:\n required: [id]\n"}, {"auto_merge as scalar", "review:\n auto_merge: notes/**\n"}, {"invalid pattern", "review:\n auto_merge: [../etc/**]\n"}, {"absolute pattern", "review:\n auto_merge: [/notes/**]\n"}, {"malformed globstar", "review:\n auto_merge: [notes**]\n"}, // The critical one: a per-space file must not be able to make // "approved" an authored status. {"approved status", "schema:\n status: [draft, approved]\n"}, {"unknown status", "schema:\n status: [wip]\n"}, {"empty status list", "schema:\n status: []\n"}, {"duplicate required key", "schema:\n required: [id, id]\n"}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { p, err := ParsePolicy([]byte(tc.in)) if err == nil { t.Fatalf("ParsePolicy(%q) = %+v, want an error", tc.in, p) } if !errors.Is(err, ErrInvalidPolicy) { t.Fatalf("error %v is not ErrInvalidPolicy", err) } }) } } func TestPolicyAutoMerges(t *testing.T) { p, err := ParsePolicy([]byte("review:\n auto_merge: [notes/**, reports/**, drafts/*.md]\n")) if err != nil { t.Fatalf("ParsePolicy: %v", err) } tests := []struct { path string want bool }{ {"notes/a.md", true}, {"notes/2026/07/standup.md", true}, {"reports/weekly.md", true}, {"drafts/x.md", true}, {"specs/0007-storage.md", false}, {"drafts/sub/x.md", false}, {"README.md", false}, {".spec.yml", false}, {"notesx/a.md", false}, // Fail-closed: a path that is not a valid path cannot auto-merge, so a // traversal attempt goes to a human rather than straight to approved. {"notes/../specs/0007.md", false}, {"../notes/a.md", false}, {"/notes/a.md", false}, {"notes/a\x00.md", false}, {"", false}, } for _, tc := range tests { t.Run(tc.path, func(t *testing.T) { if got := p.AutoMerges(tc.path); got != tc.want { t.Fatalf("AutoMerges(%q) = %v, want %v", tc.path, got, tc.want) } }) } } func TestDefaultPolicyAutoMergesNothing(t *testing.T) { // A space that has said nothing about review must not be laundering // unreviewed agent output onto the approved branch. p := DefaultPolicy() if err := p.Validate(); err != nil { t.Fatalf("DefaultPolicy().Validate() = %v, want nil", err) } for _, path := range []string{"notes/a.md", "specs/a.md", "anything.md"} { if p.AutoMerges(path) { t.Errorf("DefaultPolicy().AutoMerges(%q) = true, want false", path) } } } func TestPolicyValidate(t *testing.T) { tests := []struct { name string in Policy ok bool }{ {"default", DefaultPolicy(), true}, { "good patterns", Policy{Review: ReviewPolicy{AutoMerge: []string{"notes/**", "*.md"}}, Schema: DefaultSchema()}, true, }, { "bad pattern", Policy{Review: ReviewPolicy{AutoMerge: []string{"notes/**", "../x"}}, Schema: DefaultSchema()}, false, }, { "bad schema", Policy{Schema: Schema{Status: []Status{"approved"}}}, false, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { err := tc.in.Validate() if (err == nil) != tc.ok { t.Fatalf("Policy.Validate() = %v, want ok=%v", err, tc.ok) } if err != nil && !errors.Is(err, ErrInvalidPolicy) { t.Fatalf("error %v is not ErrInvalidPolicy", err) } }) } }