Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ A value without one of the type's prefixes fails with a `*WrongPrefixError`, whi

`FromUUID[T](u)` builds an ID from a `uuid.UUID`, and `id.UUID()` returns the bare value as a `uuid.UUID`. `Scan` and `DecodeSpanner` also accept `uuid.UUID` and `[16]byte`, for drivers and columns that carry native UUIDs. These need `StorageFormat: FormatDropPrefix`, because a native UUID has no prefix.

### Deterministic IDs in tests

Package `objectidtest` gives a test the same valid ID for the same seed on every run:

```go
buyer := objectidtest.FromSeed[fanType]("buyer") // fan_<UUIDv5 of "buyer">
```

`FromSeedIn` takes another UUIDv5 namespace, to match IDs that another service's test helpers derive.

## Emitted forms

| Method | Emits | Controlled by |
Expand Down
16 changes: 16 additions & 0 deletions objectidtest/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package objectidtest_test

import (
"fmt"

"github.com/TrogonStack/trogonobjectid/objectidtest"
)

func ExampleFromSeed() {
buyer := objectidtest.FromSeed[fanType]("buyer")
fmt.Println(buyer == objectidtest.FromSeed[fanType]("buyer"))
fmt.Println(buyer.Prefix())
// Output:
// true
// fan_
}
32 changes: 32 additions & 0 deletions objectidtest/objectidtest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Package objectidtest builds deterministic object IDs for tests, so a test can name
// "the buyer" or "the seller" and get the same valid ID on every run:
//
// buyer := objectidtest.FromSeed[fanType]("buyer")
//
// The bare value is a UUIDv5 of the seed, so it passes ValidateUUID.
package objectidtest

import (
"github.com/google/uuid"

"github.com/TrogonStack/trogonobjectid"
)

// Namespace is the UUIDv5 namespace that FromSeed uses.
var Namespace = uuid.NewSHA1(uuid.NameSpaceURL, []byte("https://github.com/TrogonStack/trogonobjectid/objectidtest"))

// FromSeed returns the ID whose bare value is the UUIDv5 of seed in Namespace. It
// panics if the type rejects the value, because a test fixture must be valid.
func FromSeed[T trogonobjectid.Definer](seed string) trogonobjectid.ID[T] {
return FromSeedIn[T](Namespace, seed)
}

// FromSeedIn is like FromSeed in another namespace, for matching IDs that another
// service's test helpers derive.
func FromSeedIn[T trogonobjectid.Definer](namespace uuid.UUID, seed string) trogonobjectid.ID[T] {
id, err := trogonobjectid.FromUUID[T](uuid.NewSHA1(namespace, []byte(seed)))
if err != nil {
panic(err)
}
return id
}
62 changes: 62 additions & 0 deletions objectidtest/objectidtest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package objectidtest_test

import (
"testing"

"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/TrogonStack/trogonobjectid"
"github.com/TrogonStack/trogonobjectid/objectidtest"
)

type fanType struct{}

func (fanType) Definition() trogonobjectid.Definition {
return trogonobjectid.Definition{ObjectType: "fan", Validate: trogonobjectid.ValidateUUID}
}

type shopType struct{}

func (shopType) Definition() trogonobjectid.Definition {
return trogonobjectid.Definition{ObjectType: "shop", Validate: trogonobjectid.ValidateUUID}
}

type seqType struct{}

func (seqType) Definition() trogonobjectid.Definition {
return trogonobjectid.Definition{ObjectType: "seq", Validate: trogonobjectid.ValidateInteger}
}

func TestFromSeed(t *testing.T) {
t.Run("the same seed gives the same ID", func(t *testing.T) {
assert.Equal(t, objectidtest.FromSeed[fanType]("buyer"), objectidtest.FromSeed[fanType]("buyer"))
})

t.Run("different seeds give different IDs", func(t *testing.T) {
assert.NotEqual(t, objectidtest.FromSeed[fanType]("buyer"), objectidtest.FromSeed[fanType]("seller"))
})

t.Run("the bare value is a UUIDv5 in Namespace", func(t *testing.T) {
got := objectidtest.FromSeed[fanType]("buyer")
assert.Equal(t, uuid.NewSHA1(objectidtest.Namespace, []byte("buyer")).String(), got.Bare())
u, err := got.UUID()
require.NoError(t, err)
assert.Equal(t, uuid.Version(5), u.Version())
})

t.Run("types share bare values for the same seed", func(t *testing.T) {
assert.Equal(t, objectidtest.FromSeed[fanType]("x").Bare(), objectidtest.FromSeed[shopType]("x").Bare())
})

t.Run("panics for a type that rejects UUIDs", func(t *testing.T) {
assert.Panics(t, func() { objectidtest.FromSeed[seqType]("x") })
})
}

func TestFromSeedIn(t *testing.T) {
namespace := uuid.MustParse("75211e1f-6d69-5d80-b91f-9e9c5c8aa697")
got := objectidtest.FromSeedIn[fanType](namespace, "buyer")
assert.Equal(t, uuid.NewSHA1(namespace, []byte("buyer")).String(), got.Bare())
}
Loading