Add D&D NPC artifact contract and identity codec
This commit is contained in:
114
internal/modules/dnd/npcs/identity/identity_test.go
Normal file
114
internal/modules/dnd/npcs/identity/identity_test.go
Normal file
@@ -0,0 +1,114 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user