feat: add command domain invariants
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user