package identity import ( "strings" "sync" "testing" "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd" ) func TestComparisonKeyNormalizesSupportedEquivalences(t *testing.T) { tests := []struct { name string left string right string }{ {name: "case", left: "Captain Vale", right: "cAPtAiN vALE"}, {name: "compatibility", left: "Ally", right: "Ally"}, {name: "whitespace", left: " Mira\u2003Thorn ", right: "Mira Thorn"}, {name: "apostrophe", left: "O’Rin", right: "o'Rin"}, {name: "modifier apostrophe", left: "OʼRin", right: "o'Rin"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if ComparisonKey(test.left) != ComparisonKey(test.right) { t.Fatalf("ComparisonKey(%q) = %q, ComparisonKey(%q) = %q", test.left, ComparisonKey(test.left), test.right, ComparisonKey(test.right)) } }) } if ComparisonKey("Mira Thorn") == ComparisonKey("Mira Thorne") { t.Fatal("different names received the same comparison key") } } func TestNormalizeDisplayOnlyChangesWhitespace(t *testing.T) { if got := NormalizeDisplay(" O’Rin\u2003Thorn "); got != "O’Rin Thorn" { t.Fatalf("NormalizeDisplay() = %q", got) } } func TestDeriveIDAndIDSyntax(t *testing.T) { got := DeriveID(" Mira\u2003Thorn ") if len(got) != len("npc:sha256:")+64 || !strings.HasPrefix(got, "npc:sha256:") || !IsValidID(got) { t.Fatalf("DeriveID() = %q, want exact NPC ID syntax", got) } if got != DeriveID("Mira Thorn") || got != IDFor("Mira Thorn") { t.Fatal("DeriveID() is not deterministic across equivalent names") } if DeriveID(" \u2003 ") != "" { t.Fatal("empty identity produced an ID") } for _, invalid := range []string{"", "npc:sha256:", "npc:sha256:ABC", "npc:sha256:" + strings.Repeat("0", 63), "npc:sha256:" + strings.Repeat("0", 65)} { if IsValidID(invalid) { t.Fatalf("IsValidID(%q) = true, want false", invalid) } } } func TestIdentityFunctionsAreSafeForConcurrentUse(t *testing.T) { const workers = 32 var group sync.WaitGroup for i := 0; i < workers; i++ { group.Add(1) go func() { defer group.Done() for j := 0; j < 100; j++ { if !IsValidID(DeriveID("Mira Thorn")) { t.Errorf("derived ID failed syntax check") return } } }() } group.Wait() } func TestValidateRegistryReportsIdentityCollisionCategories(t *testing.T) { validID := DeriveID("Mira Thorn") npcs := []dnd.NPC{ {ID: validID, Name: "Mira Thorn", Aliases: []string{"The Greencloak", "the greencloak", "Mira Thorn"}}, {ID: validID, Name: "Mira Thorn", Aliases: []string{"The Greencloak"}}, {ID: DeriveID("Captain Vale"), Name: "Captain Vale", Aliases: []string{"Mira Thorn"}}, } issues := ValidateRegistry(npcs) want := map[IssueCode]bool{ IssueDuplicateAlias: false, IssueOwnCanonicalAlias: false, IssueDuplicateCanonical: false, IssueDuplicateID: false, IssueAliasOwnershipCollision: false, IssueAliasCanonicalCollision: false, } for _, issue := range issues { if _, ok := want[issue.Code]; ok { want[issue.Code] = true } } for code, found := range want { if !found { t.Errorf("ValidateRegistry() did not report %s", code) } } } func TestValidateRegistryReportsIDProblems(t *testing.T) { issues := ValidateRegistry([]dnd.NPC{{ID: "bad", Name: "Mira Thorn"}, {ID: DeriveID("Mira Thorn"), Name: "Other Name"}}) seen := map[IssueCode]bool{} for _, issue := range issues { seen[issue.Code] = true } if !seen[IssueInvalidID] || !seen[IssueIDMismatch] { t.Fatalf("ValidateRegistry() issues = %#v, want invalid and mismatched ID issues", issues) } }