Files
rvbox/internal/domain/id_test.go
T

136 lines
3.3 KiB
Go

package domain
import (
"bytes"
"errors"
"sort"
"sync"
"testing"
"time"
)
func TestUUIDv7CanonicalValidation_HP_IDEM_01(t *testing.T) {
t.Parallel()
valid := "01890a5d-ac96-7a11-b234-123456789abc"
parsed, err := ParseUUIDv7(valid)
if err != nil {
t.Fatalf("ParseUUIDv7(%q): %v", valid, err)
}
if parsed.String() != valid {
t.Fatalf("round trip = %q, want %q", parsed.String(), valid)
}
for _, value := range []string{
"",
"01890A5D-AC96-7A11-B234-123456789ABC",
"01890a5dac967a11b234123456789abc",
"{01890a5d-ac96-7a11-b234-123456789abc}",
"01890a5d-ac96-4a11-b234-123456789abc",
"01890a5d-ac96-7a11-7234-123456789abc",
} {
if _, err := ParseUUIDv7(value); !errors.Is(err, ErrInvalidUUIDv7) {
t.Errorf("ParseUUIDv7(%q) error = %v, want ErrInvalidUUIDv7", value, err)
}
}
}
func TestUUIDv7GeneratorMonotonicAcrossClockRollback_HP_IDEM_10(t *testing.T) {
t.Parallel()
times := []time.Time{
time.UnixMilli(1_700_000_000_000),
time.UnixMilli(1_700_000_000_000),
time.UnixMilli(1_699_999_999_000),
time.UnixMilli(1_700_000_000_001),
}
next := 0
generator := NewUUIDv7Generator(func() time.Time {
value := times[next]
next++
return value
}, bytes.NewReader(bytes.Repeat([]byte{0x11}, 40)))
var previous string
for range times {
id, err := generator.New()
if err != nil {
t.Fatalf("New(): %v", err)
}
if _, err := ParseUUIDv7(id.String()); err != nil {
t.Fatalf("generated invalid UUID %q: %v", id, err)
}
if previous != "" && id.String() <= previous {
t.Fatalf("UUID %q did not sort after %q", id, previous)
}
previous = id.String()
}
}
func TestUUIDv7GeneratorConcurrentOrderingAndUniqueness_HP_IDEM_10(t *testing.T) {
t.Parallel()
const count = 512
generator := NewUUIDv7Generator(
func() time.Time { return time.UnixMilli(1_700_000_000_000) },
bytes.NewReader(bytes.Repeat([]byte{0x42}, count*10)),
)
ids := make(chan string, count)
errCh := make(chan error, count)
var group sync.WaitGroup
for range count {
group.Add(1)
go func() {
defer group.Done()
id, err := generator.New()
if err != nil {
errCh <- err
return
}
ids <- id.String()
}()
}
group.Wait()
close(ids)
close(errCh)
for err := range errCh {
t.Errorf("New(): %v", err)
}
ordered := make([]string, 0, count)
seen := make(map[string]struct{}, count)
for id := range ids {
if _, duplicate := seen[id]; duplicate {
t.Fatalf("duplicate UUID %q", id)
}
seen[id] = struct{}{}
ordered = append(ordered, id)
}
if len(ordered) != count {
t.Fatalf("generated %d UUIDs, want %d", len(ordered), count)
}
sort.Strings(ordered)
for index := 1; index < len(ordered); index++ {
if ordered[index] <= ordered[index-1] {
t.Fatalf("UUID ordering failed at %d", index)
}
}
}
func TestUUIDv7GeneratorErrors_HP_IDEM_01(t *testing.T) {
t.Parallel()
t.Run("entropy", func(t *testing.T) {
generator := NewUUIDv7Generator(time.Now, bytes.NewReader(nil))
if _, err := generator.New(); err == nil {
t.Fatal("New() succeeded with empty entropy")
}
})
t.Run("before epoch", func(t *testing.T) {
generator := NewUUIDv7Generator(func() time.Time { return time.UnixMilli(-1) }, bytes.NewReader(make([]byte, 10)))
if _, err := generator.New(); !errors.Is(err, ErrUUIDTimeRange) {
t.Fatalf("New() error = %v, want ErrUUIDTimeRange", err)
}
})
}