package wing_test import ( "context" "regexp" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/mathiasbq/hyperguild/ingestion/internal/wing" ) func routerBrain(t *testing.T) string { dir := t.TempDir() writePage(t, dir, "wiki/homelab/facts/a.md", "x") writePage(t, dir, "wiki/rikatillsammans/facts/b.md", "x") return dir } func TestRouter_Decisions(t *testing.T) { tests := []struct { name string note wing.Note want wing.Source wantWing string }{ {"no signal goes to the default wing", wing.Note{Text: "a talk about agents"}, wing.Default, "web-clip"}, {"a subfolder naming an existing wing places there", wing.Note{Dir: "homelab", Text: "x"}, wing.Explicit, "homelab"}, {"a deliberate subfolder into a client wing is the owner's call and is placed", wing.Note{Dir: "rikatillsammans", Text: "x"}, wing.Explicit, "rikatillsammans"}, {"frontmatter wing places like a subfolder", wing.Note{FrontmatterWing: "homelab", Text: "x"}, wing.Explicit, "homelab"}, {"subfolder is case-insensitive", wing.Note{Dir: "HomeLab", Text: "x"}, wing.Explicit, "homelab"}, {"a typo never creates a wing", wing.Note{Dir: "hoemlab", Text: "x"}, wing.Exception, "hoemlab"}, {"conflicting signals are parked", wing.Note{Dir: "homelab", FrontmatterWing: "rikatillsammans", Text: "x"}, wing.Exception, ""}, {"the default wing may be named explicitly even before it exists", wing.Note{Dir: "web-clip", Text: "x"}, wing.Explicit, "web-clip"}, {"client marker with no signal is parked, not defaulted", wing.Note{Text: "notes from the ACME-CLIENT kickoff"}, wing.Exception, ""}, {"client marker does not block a deliberate subfolder", wing.Note{Dir: "homelab", Text: "ACME-CLIENT"}, wing.Explicit, "homelab"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := wing.Router{BrainDir: routerBrain(t), Default: "web-clip", ClientBlock: regexp.MustCompile(`(?i)acme-client`)} d := r.Resolve(context.Background(), tt.note) assert.Equal(t, tt.want, d.Source) assert.Equal(t, tt.wantWing, d.Wing) assert.NotEmpty(t, d.Reason) }) } } func TestRouter_ReadsTheWingsFreshOnEveryDecision(t *testing.T) { dir := routerBrain(t) r := wing.Router{BrainDir: dir, Default: "web-clip"} assert.Equal(t, wing.Exception, r.Resolve(context.Background(), wing.Note{Dir: "newwing", Text: "x"}).Source) writePage(t, dir, "wiki/newwing/facts/c.md", "x") // a wing created after startup assert.Equal(t, wing.Explicit, r.Resolve(context.Background(), wing.Note{Dir: "newwing", Text: "x"}).Source, "a wing made while the server runs is usable without a restart") } func TestRouter_AnUnreadableBrainParksInsteadOfGuessing(t *testing.T) { r := wing.Router{BrainDir: t.TempDir() + "/missing", Default: "web-clip"} d := r.Resolve(context.Background(), wing.Note{Dir: "homelab", Text: "x"}) assert.Equal(t, wing.Exception, d.Source) } func TestRouter_WithoutADefaultAnUnsignalledNoteIsParked(t *testing.T) { r := wing.Router{BrainDir: routerBrain(t)} assert.Equal(t, wing.Exception, r.Resolve(context.Background(), wing.Note{Text: "x"}).Source) } func TestRouter_ADefaultThatIsNotAPlainNameIsRefused(t *testing.T) { r := wing.Router{BrainDir: routerBrain(t), Default: "../escape"} assert.Equal(t, wing.Exception, r.Resolve(context.Background(), wing.Note{Text: "x"}).Source) } func TestRouter_ANameThatIsNotPlainIsRefusedAsSuch(t *testing.T) { for _, bad := range []string{"../x", "a/b", "has space", "dot.ted"} { t.Run(bad, func(t *testing.T) { // Default equals the bad name: the "named explicitly" shortcut for the // default wing must not wave a malformed name through. r := wing.Router{BrainDir: routerBrain(t), Default: bad} d := r.Resolve(context.Background(), wing.Note{Dir: bad, Text: "x"}) assert.Equal(t, wing.Exception, d.Source) assert.Empty(t, d.Wing, "a malformed name is never echoed as a proposal") assert.Contains(t, d.Reason, "not a usable wing name") }) } } func TestNewRouter(t *testing.T) { dir := routerBrain(t) t.Run("valid", func(t *testing.T) { r, err := wing.NewRouter(dir, "web-clip", "acme-client|globex") assert.NoError(t, err) d := r.Resolve(context.Background(), wing.Note{Text: "kickoff with GLOBEX"}) assert.Equal(t, wing.Exception, d.Source, "the marker is matched case-insensitively") }) t.Run("no marker pattern means no content check", func(t *testing.T) { r, err := wing.NewRouter(dir, "web-clip", "") assert.NoError(t, err) assert.Equal(t, wing.Default, r.Resolve(context.Background(), wing.Note{Text: "anything"}).Source) }) t.Run("a default that is not a plain wing name is a startup error", func(t *testing.T) { _, err := wing.NewRouter(dir, "Web Clip", "") assert.Error(t, err) }) t.Run("an empty default is a startup error", func(t *testing.T) { _, err := wing.NewRouter(dir, "", "") assert.Error(t, err) }) t.Run("a pattern that does not compile is a startup error", func(t *testing.T) { _, err := wing.NewRouter(dir, "web-clip", "([unclosed") assert.Error(t, err, "a client filter that silently does nothing is worse than none") }) } // From the review of #211: the content filter is the only guard on the default // lane, so it must survive the cheap evasions. func TestRouter_ClientMarkerSurvivesCheapEvasions(t *testing.T) { r := wing.Router{BrainDir: routerBrain(t), Default: "web-clip", ClientBlock: regexp.MustCompile(`(?i)acme-client`)} tests := []struct{ name, text string }{ {"non-breaking hyphen", "notes on acme‑client"}, {"zero-width joiner inside", "notes on acme\u200d-client"}, {"zero-width space inside", "notes on ac\u200bme-client"}, {"split across lines", "notes on acme-\nclient"}, {"spaced hyphen", "notes on acme - client"}, {"fullwidth letters", "notes on acme-client"}, {"soft hyphen", "notes on acme\u00ad-client"}, {"en dash", "notes on acme–client"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { d := r.Resolve(context.Background(), wing.Note{Text: tt.text}) assert.Equal(t, wing.Exception, d.Source, tt.text) }) } t.Run("an unrelated text still files", func(t *testing.T) { d := r.Resolve(context.Background(), wing.Note{Text: "a talk about agents and a client-server model"}) assert.Equal(t, wing.Default, d.Source) }) } func TestRouter_FolderNamesAreAsciiAndNotReserved(t *testing.T) { dir := routerBrain(t) writePage(t, dir, "wiki/kubernetes/facts/a.md", "x") r := wing.Router{BrainDir: dir, Default: "web-clip"} tests := []struct { name string dir string }{ {"Kelvin sign lowercases to an ASCII k", "Kubernetes"}, {"cyrillic lookalike", "h\u043emelab"}, {"retired layout name: sources", "sources"}, {"retired layout name: concepts", "concepts"}, {"retired layout name: entities", "entities"}, {"wiki", "wiki"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { d := r.Resolve(context.Background(), wing.Note{Dir: tt.dir, Text: "x"}) assert.Equal(t, wing.Exception, d.Source) assert.Empty(t, d.Wing, "never proposed as a wing") }) } t.Run("plain ASCII case folding still works", func(t *testing.T) { d := r.Resolve(context.Background(), wing.Note{Dir: "KubernetEs", Text: "x"}) assert.Equal(t, wing.Explicit, d.Source) assert.Equal(t, "kubernetes", d.Wing) }) } func TestNewRouter_AReservedDefaultIsRefused(t *testing.T) { _, err := wing.NewRouter(routerBrain(t), "sources", "") assert.Error(t, err) } // From the second review of #211: the multi-word, \s and \b patterns an operator // would actually write, and the next tier of evasions. func TestRouter_ClientMarkerHandlesRealisticPatternsAndHarderEvasions(t *testing.T) { tests := []struct { name, pattern, text string }{ {"space in pattern vs NBSP", `acme corp`, "about acme\u00a0corp today"}, {"space in pattern vs two spaces", `acme corp`, "about acme corp today"}, {"space in pattern vs newline", `acme corp`, "about acme\ncorp today"}, {"backslash-s pattern vs NBSP", `acme\s+corp`, "about acme\u00a0corp today"}, {"word boundary vs a zero-width char inside", `\bacme\b`, "the ac\u200bme corp"}, {"dash pattern vs a soft hyphen as the dash", `acme-client`, "acme\u00adclient"}, {"cyrillic a", `acme-client`, "\u0430cme-client"}, {"cyrillic e", `acme-client`, "acm\u0435-client"}, {"cyrillic o and c", `coop`, "c\u043e\u043ep"}, {"combining acute", `acme-client`, "a\u0301cme-client"}, {"precomposed accent", `acme-client`, "\u00e1cme-client"}, {"hangul filler", `acme-client`, "ac\u3164me-client"}, {"halfwidth hangul filler", `acme-client`, "ac\uffa0me-client"}, {"braille blank", `acme-client`, "ac\u2800me-client"}, {"grapheme joiner", `acme-client`, "ac\u034fme-client"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r, err := wing.NewRouter(routerBrain(t), "web-clip", tt.pattern) assert.NoError(t, err) assert.Equal(t, wing.Exception, r.Resolve(context.Background(), wing.Note{Text: tt.text}).Source, "%q should be caught by %q", tt.text, tt.pattern) }) } t.Run("ordinary text is not caught by a multi-word pattern", func(t *testing.T) { r, _ := wing.NewRouter(routerBrain(t), "web-clip", `acme corp`) assert.Equal(t, wing.Default, r.Resolve(context.Background(), wing.Note{Text: "acme is not corp, and corp is not acme"}).Source) }) } // The folding allocates several times the text, so a large note is checked in // bounded chunks. A marker that straddles a chunk edge must still be found. func TestRouter_ClientMarkerIsFoundAcrossAChunkBoundary(t *testing.T) { r, err := wing.NewRouter(routerBrain(t), "web-clip", `acme-client`) assert.NoError(t, err) r.FilterChunk = 64 for _, at := range []int{0, 30, 55, 58, 62, 64, 70, 120, 190} { text := strings.Repeat("x", at) + "acme-client" + strings.Repeat("y", 200) assert.Equal(t, wing.Exception, r.Resolve(context.Background(), wing.Note{Text: text}).Source, "marker at offset %d was missed", at) } clean := strings.Repeat("lorem ipsum ", 100) assert.Equal(t, wing.Default, r.Resolve(context.Background(), wing.Note{Text: clean}).Source) } func TestRouter_ClientMarkerChunkingDoesNotSplitAMultiByteRune(t *testing.T) { r, err := wing.NewRouter(routerBrain(t), "web-clip", `acme-client`) assert.NoError(t, err) r.FilterChunk = 65 // odd, over two-byte runes: every boundary falls inside a rune unless it is moved for pad := 0; pad < 8; pad++ { text := strings.Repeat("\u00e9", 40+pad) + "acme-client" + strings.Repeat("\u00e9", 40) assert.Equal(t, wing.Exception, r.Resolve(context.Background(), wing.Note{Text: text}).Source, "pad %d", pad) } clean := strings.Repeat("\u00e9", 300) assert.Equal(t, wing.Default, r.Resolve(context.Background(), wing.Note{Text: clean}).Source, "no panic or false match on a text of 2-byte runes") }