Explicit subfolder/frontmatter wins; otherwise a classifier and an independent scorer must agree above a threshold on a known, eligible wing. Anything else is an Exception for a human. Never creates a wing, never assigns an ineligible (client/untagged) wing. Mutation-checked: dropping the eligibility, known-wing, scorer, threshold or conflict check fails a test. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
113 lines
4.6 KiB
Go
113 lines
4.6 KiB
Go
package wing_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mathiasbq/hyperguild/ingestion/internal/wing"
|
|
)
|
|
|
|
type fakeClassifier struct {
|
|
s wing.Suggestion
|
|
err error
|
|
calls int
|
|
}
|
|
|
|
func (f *fakeClassifier) Classify(_ context.Context, _ string, _ []string) (wing.Suggestion, error) {
|
|
f.calls++
|
|
return f.s, f.err
|
|
}
|
|
|
|
type fakeScorer map[string]float64
|
|
|
|
func (f fakeScorer) Score(string) map[string]float64 { return f }
|
|
|
|
// eligibleExcept allows every name except the listed ones. Being permissive on
|
|
// names it has never heard of means only the "known wing" check can stop an
|
|
// invented wing, so a test of that check cannot pass by accident.
|
|
func eligibleExcept(names ...string) func(string) bool {
|
|
deny := map[string]bool{}
|
|
for _, n := range names {
|
|
deny[n] = true
|
|
}
|
|
return func(w string) bool { return !deny[w] }
|
|
}
|
|
|
|
func newResolver(c wing.Classifier, s wing.Scorer) wing.Resolver {
|
|
return wing.Resolver{
|
|
Known: []string{"homelab", "hyperguild", "tapir", "rikatillsammans"},
|
|
Eligible: eligibleExcept("rikatillsammans"), // a client wing: known, never auto
|
|
Classifier: c,
|
|
Scorer: s,
|
|
Threshold: 0.8,
|
|
}
|
|
}
|
|
|
|
func TestResolve_ExplicitSignal(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
note wing.Note
|
|
want wing.Source
|
|
wantWing string
|
|
}{
|
|
{"subfolder of a known eligible wing", wing.Note{Dir: "homelab"}, wing.Explicit, "homelab"},
|
|
{"frontmatter of a known eligible wing", wing.Note{FrontmatterWing: "tapir"}, wing.Explicit, "tapir"},
|
|
{"subfolder and frontmatter agree", wing.Note{Dir: "tapir", FrontmatterWing: "tapir"}, wing.Explicit, "tapir"},
|
|
{"subfolder and frontmatter disagree", wing.Note{Dir: "tapir", FrontmatterWing: "homelab"}, wing.Exception, ""},
|
|
{"unknown wing is never created", wing.Note{Dir: "hoemlab"}, wing.Exception, "hoemlab"},
|
|
{"known but not eligible (client) wing is never auto-assigned", wing.Note{Dir: "rikatillsammans"}, wing.Exception, "rikatillsammans"},
|
|
{"subfolder is case-insensitive", wing.Note{Dir: "HomeLab"}, wing.Explicit, "homelab"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
c := &fakeClassifier{}
|
|
d := newResolver(c, fakeScorer{}).Resolve(context.Background(), tt.note)
|
|
assert.Equal(t, tt.want, d.Source)
|
|
assert.Equal(t, tt.wantWing, d.Wing)
|
|
assert.Zero(t, c.calls, "an explicit signal must not spend an LLM call")
|
|
assert.NotEmpty(t, d.Reason)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResolve_Classified(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
sugg wing.Suggestion
|
|
err error
|
|
scores fakeScorer
|
|
want wing.Source
|
|
wantWing string
|
|
}{
|
|
{"confident and scorer agrees", wing.Suggestion{Wing: "tapir", Confidence: 0.9}, nil, fakeScorer{"tapir": 3, "homelab": 1}, wing.Classified, "tapir"},
|
|
{"confidence exactly at threshold passes", wing.Suggestion{Wing: "tapir", Confidence: 0.8}, nil, fakeScorer{"tapir": 3}, wing.Classified, "tapir"},
|
|
{"below threshold is an exception, proposal kept", wing.Suggestion{Wing: "tapir", Confidence: 0.79}, nil, fakeScorer{"tapir": 3}, wing.Exception, "tapir"},
|
|
{"scorer disagrees", wing.Suggestion{Wing: "tapir", Confidence: 0.95}, nil, fakeScorer{"homelab": 5, "tapir": 1}, wing.Exception, "tapir"},
|
|
{"scorer has no opinion also needs review", wing.Suggestion{Wing: "tapir", Confidence: 0.95}, nil, fakeScorer{}, wing.Exception, "tapir"},
|
|
{"classifier proposes a client wing", wing.Suggestion{Wing: "rikatillsammans", Confidence: 0.99}, nil, fakeScorer{"rikatillsammans": 9}, wing.Exception, "rikatillsammans"},
|
|
{"classifier invents a wing", wing.Suggestion{Wing: "brand-new-wing", Confidence: 0.99}, nil, fakeScorer{"brand-new-wing": 9}, wing.Exception, "brand-new-wing"},
|
|
{"classifier abstains", wing.Suggestion{Wing: "", Confidence: 0}, nil, fakeScorer{"tapir": 3}, wing.Exception, ""},
|
|
{"classifier error", wing.Suggestion{}, errors.New("llm down"), fakeScorer{"tapir": 3}, wing.Exception, ""},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
c := &fakeClassifier{s: tt.sugg, err: tt.err}
|
|
d := newResolver(c, tt.scores).Resolve(context.Background(), wing.Note{Text: "some note"})
|
|
assert.Equal(t, tt.want, d.Source)
|
|
assert.Equal(t, tt.wantWing, d.Wing)
|
|
assert.NotEmpty(t, d.Reason, "every decision says why, so a spot-check can judge it")
|
|
assert.Equal(t, 1, c.calls)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResolve_NilDependenciesFailClosed(t *testing.T) {
|
|
r := wing.Resolver{Known: []string{"tapir"}, Eligible: eligibleExcept(), Threshold: 0.8}
|
|
d := r.Resolve(context.Background(), wing.Note{Text: "x"})
|
|
require.Equal(t, wing.Exception, d.Source, "no classifier configured must route to review, not panic or guess")
|
|
}
|