a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
143 lines
4.6 KiB
Go
143 lines
4.6 KiB
Go
package search
|
|
|
|
import (
|
|
"slices"
|
|
"testing"
|
|
)
|
|
|
|
func TestEquivalenceTable_CuratedLookup(t *testing.T) {
|
|
tbl := NewEquivalenceTable(nil)
|
|
|
|
// A word resolves to its class siblings, never itself.
|
|
got := tbl.Expand("login")
|
|
if len(got) == 0 {
|
|
t.Fatal("Expand(login) returned no siblings")
|
|
}
|
|
if slices.Contains(got, "login") {
|
|
t.Error("Expand must not include the query token itself")
|
|
}
|
|
for _, want := range []string{"auth", "authentication", "signin", "credential"} {
|
|
if !slices.Contains(got, want) {
|
|
t.Errorf("Expand(login) missing curated sibling %q; got %v", want, got)
|
|
}
|
|
}
|
|
|
|
// Case-insensitive.
|
|
if len(tbl.Expand("LOGIN")) != len(got) {
|
|
t.Error("Expand should be case-insensitive")
|
|
}
|
|
|
|
// A word in no class returns nil.
|
|
if tbl.Expand("zzzznotaword") != nil {
|
|
t.Error("Expand of an unknown token should return nil")
|
|
}
|
|
|
|
// delete/remove are siblings.
|
|
if !slices.Contains(tbl.Expand("delete"), "remove") {
|
|
t.Error("delete should expand to remove")
|
|
}
|
|
if !slices.Contains(tbl.Expand("remove"), "delete") {
|
|
t.Error("remove should expand to delete (symmetric)")
|
|
}
|
|
}
|
|
|
|
func TestEquivalenceTable_RepoExtra(t *testing.T) {
|
|
// A repo-custom class is added and its label joins the class.
|
|
tbl := NewEquivalenceTable(map[string][]string{
|
|
"widget": {"gadget", "gizmo"},
|
|
})
|
|
got := tbl.Expand("gadget")
|
|
if !slices.Contains(got, "gizmo") || !slices.Contains(got, "widget") {
|
|
t.Errorf("repo-extra class not wired: Expand(gadget) = %v", got)
|
|
}
|
|
|
|
// A repo extra whose label is a curated word extends that class.
|
|
tbl2 := NewEquivalenceTable(map[string][]string{
|
|
"auth": {"oauth", "sso"},
|
|
})
|
|
authSibs := tbl2.Expand("oauth")
|
|
if !slices.Contains(authSibs, "login") {
|
|
t.Errorf("repo extra keyed on a curated word should extend the curated class; got %v", authSibs)
|
|
}
|
|
}
|
|
|
|
func TestEquivalenceTable_NilSafe(t *testing.T) {
|
|
var tbl *EquivalenceTable
|
|
if tbl.Expand("auth") != nil {
|
|
t.Error("nil EquivalenceTable.Expand should return nil")
|
|
}
|
|
if tbl.ExpandRelated("auth") != nil {
|
|
t.Error("nil EquivalenceTable.ExpandRelated should return nil")
|
|
}
|
|
if tbl.ClassCount() != 0 {
|
|
t.Error("nil EquivalenceTable.ClassCount should be 0")
|
|
}
|
|
if tbl.RelatedClassCount() != 0 {
|
|
t.Error("nil EquivalenceTable.RelatedClassCount should be 0")
|
|
}
|
|
}
|
|
|
|
// TestEquivalenceTable_ExpandRelated confirms the concept-relatedness
|
|
// thesaurus bridges adjacent concepts (auth -> token / session)
|
|
// WITHOUT folding them into the synonym class.
|
|
func TestEquivalenceTable_ExpandRelated(t *testing.T) {
|
|
tbl := NewEquivalenceTable(nil)
|
|
|
|
// "auth" relates to the token and session classes.
|
|
rel := tbl.ExpandRelated("auth")
|
|
if len(rel) == 0 {
|
|
t.Fatal("ExpandRelated(auth) returned no related concepts")
|
|
}
|
|
for _, want := range []string{"token", "jwt", "session"} {
|
|
if !slices.Contains(rel, want) {
|
|
t.Errorf("ExpandRelated(auth) missing related concept %q; got %v", want, rel)
|
|
}
|
|
}
|
|
|
|
// Critical precision guard: the related concepts must NOT be
|
|
// synonyms of "auth" -- Expand (the union-find synonym channel)
|
|
// must still treat them as separate classes.
|
|
authSyn := tbl.Expand("auth")
|
|
for _, related := range []string{"token", "session", "jwt"} {
|
|
if slices.Contains(authSyn, related) {
|
|
t.Errorf("synonym Expand(auth) must NOT contain related concept %q -- "+
|
|
"the thesaurus must stay separate from the union-find classes", related)
|
|
}
|
|
}
|
|
|
|
// The relation is symmetric: "token" relates back to "auth".
|
|
if !slices.Contains(tbl.ExpandRelated("token"), "auth") {
|
|
t.Errorf("ExpandRelated(token) should relate back to auth; got %v", tbl.ExpandRelated("token"))
|
|
}
|
|
|
|
// The query token itself is never returned.
|
|
if slices.Contains(tbl.ExpandRelated("auth"), "auth") {
|
|
t.Error("ExpandRelated must not include the query token itself")
|
|
}
|
|
|
|
// A word in no class has no related concepts.
|
|
if tbl.ExpandRelated("zzznotaword") != nil {
|
|
t.Error("ExpandRelated of an unknown word should be nil")
|
|
}
|
|
}
|
|
|
|
// TestEquivalenceTable_RelatedSeparateFromClasses is the structural
|
|
// invariant: adding the thesaurus must not have merged any concepts
|
|
// into one giant class. The class count stays at the curated baseline
|
|
// and the token / auth classes remain distinct.
|
|
func TestEquivalenceTable_RelatedSeparateFromClasses(t *testing.T) {
|
|
tbl := NewEquivalenceTable(nil)
|
|
if tbl.RelatedClassCount() == 0 {
|
|
t.Fatal("the curated thesaurus should produce related-class edges")
|
|
}
|
|
// "auth" and "token" must be in DIFFERENT union-find classes.
|
|
authIdx, ok1 := tbl.member["auth"]
|
|
tokenIdx, ok2 := tbl.member["token"]
|
|
if !ok1 || !ok2 {
|
|
t.Fatal("auth and token must both be curated class members")
|
|
}
|
|
if authIdx == tokenIdx {
|
|
t.Error("auth and token must stay in distinct classes -- the thesaurus must not merge them")
|
|
}
|
|
}
|