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)
}
})
}
}