Removes the LLM classifier, the substring 'BM25' scorer, the agreement gate, the backtest and the old migrate/wingmigrate tooling: measured at 2 of 55 pages placed (#208), and the migration was built for the per-wing facts layout this branch replaces. wing now holds the decision types, the Router and KnownWings. INGEST_WING_ROUTING=1 builds the Router (default wing INGEST_DEFAULT_WING=web-clip, client pattern from INGEST_CLIENT_BLOCK or CLAUDE_INGEST_CLIENT_BLOCK); a bad default or pattern is fatal at startup. Watcher tests rewritten for the Router, including a deliberate subfolder into any existing wing, typo'd folders parked, forged-key and edit-while-placing cases. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
115 lines
5.0 KiB
Go
115 lines
5.0 KiB
Go
package wing_test
|
|
|
|
import (
|
|
"context"
|
|
"regexp"
|
|
"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 plain 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")
|
|
})
|
|
}
|