Files
anchorage/internal/pkg/ids/ids_test.go
William Gill 90ac4b169c
Some checks failed
Security / Vulnerability Check (push) Successful in 1m47s
Test / Build & Unit Tests (push) Successful in 5m4s
Test / Lint (push) Successful in 27s
Test / Integration Tests (push) Failing after 1m58s
ci: fix first CI run — tidy, gofmt, ordering test, govulncheck pin
- ids: TestIDsAreTimeOrdered asserted strict lexicographic ordering of
  back-to-back UUIDv7s, but the sub-ms tail is random and not required
  to be monotonic. Sleep between samples so each ID lands in a distinct
  millisecond — the property that actually gives Postgres index
  locality on (org_id, id desc).
- go.mod/go.sum: run go mod tidy. keyfunc/v3, prometheus/client_golang
  and testcontainers-go/modules/postgres are imported directly and
  should not be marked // indirect; also drops stale sum entries.
- gofmt -w across 12 files flagged by the lint job.
- security.yml: pin govulncheck to v1.2.0. @latest triggers a proxy
  lookup every run, which is the step that hung for 16m on the Gitea
  runner.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 09:18:49 -05:00

150 lines
4.0 KiB
Go

package ids_test
import (
"strings"
"testing"
"time"
"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 at millisecond granularity; the sub-ms
// tail is random and not required to be monotonic, so back-to-back
// calls in the same millisecond can sort in either direction. The
// property we actually rely on for Postgres index locality on
// (org_id, id desc) is that IDs separated by >=1ms sort in
// generation order — so sleep between samples.
prev := ids.MustNewPin().String()
for i := 0; i < 8; i++ {
time.Sleep(2 * time.Millisecond)
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)
}
})
}
}