package ids_test import ( "strings" "testing" "anchorage/internal/pkg/ids" ) func TestNewConstructorsReturnCorrectPrefixes(t *testing.T) { tests := []struct { name string gen func(t *testing.T) (string, string) prefix string }{ {"org", func(t *testing.T) (string, string) { id, err := ids.NewOrg() if err != nil { t.Fatalf("NewOrg: %v", err) } return id.Prefix(), id.String() }, ids.OrgPrefixToken}, {"user", func(t *testing.T) (string, string) { id, err := ids.NewUser() if err != nil { t.Fatalf("NewUser: %v", err) } return id.Prefix(), id.String() }, ids.UserPrefixToken}, {"pin", func(t *testing.T) (string, string) { id, err := ids.NewPin() if err != nil { t.Fatalf("NewPin: %v", err) } return id.Prefix(), id.String() }, ids.PinPrefixToken}, {"token", func(t *testing.T) (string, string) { id, err := ids.NewToken() if err != nil { t.Fatalf("NewToken: %v", err) } return id.Prefix(), id.String() }, ids.TokenPrefixToken}, {"node", func(t *testing.T) (string, string) { id, err := ids.NewNode() if err != nil { t.Fatalf("NewNode: %v", err) } return id.Prefix(), id.String() }, ids.NodePrefixToken}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { prefix, str := tt.gen(t) if prefix != tt.prefix { t.Errorf("Prefix() = %q, want %q", prefix, tt.prefix) } if !strings.HasPrefix(str, tt.prefix+"_") { t.Errorf("String() = %q, want prefix %q", str, tt.prefix+"_") } }) } } func TestMustConstructorsDoNotPanicInHappyPath(t *testing.T) { // Smoke-test: they must return the same prefix as the error-returning // counterpart, and not panic under normal operation. if p := ids.MustNewOrg().Prefix(); p != ids.OrgPrefixToken { t.Errorf("MustNewOrg prefix = %q", p) } if p := ids.MustNewUser().Prefix(); p != ids.UserPrefixToken { t.Errorf("MustNewUser prefix = %q", p) } if p := ids.MustNewPin().Prefix(); p != ids.PinPrefixToken { t.Errorf("MustNewPin prefix = %q", p) } if p := ids.MustNewToken().Prefix(); p != ids.TokenPrefixToken { t.Errorf("MustNewToken prefix = %q", p) } if p := ids.MustNewNode().Prefix(); p != ids.NodePrefixToken { t.Errorf("MustNewNode prefix = %q", p) } } func TestIDsAreUniquePerCall(t *testing.T) { // UUIDv7 suffixes must differ between calls even back-to-back. a := ids.MustNewPin().String() b := ids.MustNewPin().String() if a == b { t.Fatalf("two consecutive NewPin() calls produced the same id: %s", a) } } func TestIDsAreTimeOrdered(t *testing.T) { // UUIDv7 is time-ordered, so later-generated IDs should sort after // earlier ones lexicographically. This is the property that gives // Postgres index locality on (org_id, id desc). prev := ids.MustNewPin().String() for i := 0; i < 32; i++ { next := ids.MustNewPin().String() if next <= prev { t.Fatalf("UUIDv7 ordering violated: %q followed %q", next, prev) } prev = next } } func TestParseRoundTrip(t *testing.T) { orig := ids.MustNewOrg() parsed, err := ids.ParseOrg(orig.String()) if err != nil { t.Fatalf("ParseOrg: %v", err) } if parsed.String() != orig.String() { t.Errorf("round-trip changed value: %q -> %q", orig, parsed) } } func TestParseRejectsWrongPrefix(t *testing.T) { // An org ID string must not parse as a pin ID. orgStr := ids.MustNewOrg().String() if _, err := ids.ParsePin(orgStr); err == nil { t.Errorf("ParsePin accepted an org id %q", orgStr) } } func TestParseRejectsGarbage(t *testing.T) { tests := []string{ "", "not-a-typeid", "org_", "_01h7rfxv6qefr4twn2jg5p4k3z", "org_too-short", "org_01h7rfxv6qefr4twn2jg5p4k3Z", // uppercase base32 char: invalid } for _, s := range tests { t.Run(s, func(t *testing.T) { if _, err := ids.ParseOrg(s); err == nil { t.Errorf("ParseOrg(%q) should have failed", s) } }) } }