2026-07-21 02:08:46 +00:00
|
|
|
|
package identity
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2026-08-05 18:25:04 +00:00
|
|
|
|
"reflect"
|
2026-07-21 02:08:46 +00:00
|
|
|
|
"strings"
|
|
|
|
|
|
"sync"
|
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
|
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
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 ")
|
2026-08-05 18:25:04 +00:00
|
|
|
|
const want = "npc:sha256:35ba5f679aee69e07ae3bd65c44278f29539d5dc9bb5225db1c0060555b23221"
|
|
|
|
|
|
if Policy != "dnd.npc_registry.identity.v1" || IdentityPolicy != Policy {
|
|
|
|
|
|
t.Fatalf("identity policy = %q/%q", Policy, IdentityPolicy)
|
|
|
|
|
|
}
|
2026-07-21 02:08:46 +00:00
|
|
|
|
if len(got) != len("npc:sha256:")+64 || !strings.HasPrefix(got, "npc:sha256:") || !IsValidID(got) {
|
|
|
|
|
|
t.Fatalf("DeriveID() = %q, want exact NPC ID syntax", got)
|
|
|
|
|
|
}
|
2026-08-05 18:25:04 +00:00
|
|
|
|
if got != want {
|
|
|
|
|
|
t.Fatalf("DeriveID() = %q, want SHA-256 of compact JSON [policy, comparison_name]", got)
|
|
|
|
|
|
}
|
2026-07-21 02:08:46 +00:00
|
|
|
|
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{
|
2026-07-22 18:51:26 +00:00
|
|
|
|
{ID: validID, Name: "Mira Thorn"},
|
|
|
|
|
|
{ID: validID, Name: "Mira Thorn"},
|
2026-07-21 02:08:46 +00:00
|
|
|
|
}
|
2026-08-05 18:25:04 +00:00
|
|
|
|
before := append([]dnd.NPC(nil), npcs...)
|
2026-07-21 02:08:46 +00:00
|
|
|
|
issues := ValidateRegistry(npcs)
|
2026-08-05 18:25:04 +00:00
|
|
|
|
if !reflect.DeepEqual(npcs, before) {
|
|
|
|
|
|
t.Fatal("ValidateRegistry() mutated its input")
|
|
|
|
|
|
}
|
2026-07-21 02:08:46 +00:00
|
|
|
|
want := map[IssueCode]bool{
|
2026-07-22 18:51:26 +00:00
|
|
|
|
IssueDuplicateCanonical: false,
|
|
|
|
|
|
IssueDuplicateID: false,
|
2026-07-21 02:08:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|