Files
rands/uuid/uuid_test.go
Jim Myhrberg fe4308607c feat(strings/uuidv7): add UUIDv7 generation (#10)
The UUID v7 format is a time-ordered random UUID. It uses a timestamp
with millisecond precision in the most significant bits, followed by
random data. This provides both uniqueness and chronological ordering,
making it ideal for database primary keys and situations where sorting
by creation time is desired.

References:
- https://uuid7.com/
- https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#name-uuid-version-7
2025-02-28 02:16:32 +00:00

476 lines
8.9 KiB
Go

package uuid
import (
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUUID_String(t *testing.T) {
t.Parallel()
tests := []struct {
name string
uuid UUID
expected string
}{
{
name: "Zero UUID",
uuid: UUID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
expected: "00000000-0000-0000-0000-000000000000",
},
{
name: "Random UUID",
uuid: UUID{
0x12, 0x34, 0x56, 0x78,
0x9a, 0xbc,
0xde, 0xf0,
0x12, 0x34,
0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
},
expected: "12345678-9abc-def0-1234-56789abcdef0",
},
{
name: "UUID with max values",
uuid: UUID{
0xff, 0xff, 0xff, 0xff,
0xff, 0xff,
0xff, 0xff,
0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
},
expected: "ffffffff-ffff-ffff-ffff-ffffffffffff",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.uuid.String()
assert.Equal(t, tt.expected, got)
})
}
}
func BenchmarkUUID_String(b *testing.B) {
u := UUID{
0x12, 0x34, 0x56, 0x78,
0x9a, 0xbc,
0xde, 0xf0,
0x12, 0x34,
0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = u.String()
}
}
func TestFromBytes(t *testing.T) {
t.Parallel()
tests := []struct {
name string
bytes []byte
want UUID
wantErr error
}{
{
name: "Valid 16 bytes",
bytes: []byte{
0x12,
0x34,
0x56,
0x78,
0x9a,
0xbc,
0xde,
0xf0,
0x12,
0x34,
0x56,
0x78,
0x9a,
0xbc,
0xde,
0xf0,
},
want: UUID{
0x12, 0x34, 0x56, 0x78,
0x9a, 0xbc,
0xde, 0xf0,
0x12, 0x34,
0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
},
wantErr: nil,
},
{
name: "Empty bytes",
bytes: []byte{},
want: UUID{},
wantErr: ErrInvalidLength,
},
{
name: "Too few bytes",
bytes: []byte{0x12, 0x34, 0x56, 0x78, 0x9a},
want: UUID{},
wantErr: ErrInvalidLength,
},
{
name: "Too many bytes",
bytes: []byte{
0x12,
0x34,
0x56,
0x78,
0x9a,
0xbc,
0xde,
0xf0,
0x12,
0x34,
0x56,
0x78,
0x9a,
0xbc,
0xde,
0xf0,
0xab,
},
want: UUID{},
wantErr: ErrInvalidLength,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := FromBytes(tt.bytes)
if tt.wantErr != nil {
assert.EqualError(t, err, tt.wantErr.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.want, got)
})
}
}
func BenchmarkFromBytes(b *testing.B) {
bytes := []byte{
0x12, 0x34, 0x56, 0x78,
0x9a, 0xbc,
0xde, 0xf0,
0x12, 0x34,
0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = FromBytes(bytes)
}
}
func TestFromString(t *testing.T) {
t.Parallel()
tests := []struct {
name string
str string
want UUID
wantErr error
}{
{
name: "Valid UUID string",
str: "12345678-9abc-def0-1234-56789abcdef0",
want: UUID{
0x12, 0x34, 0x56, 0x78,
0x9a, 0xbc,
0xde, 0xf0,
0x12, 0x34,
0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
},
wantErr: nil,
},
{
name: "Empty string",
str: "",
want: UUID{},
wantErr: ErrInvalidLength,
},
{
name: "Too short string",
str: "12345678-9abc-def0-1234-56789abcde",
want: UUID{},
wantErr: ErrInvalidLength,
},
{
name: "Too long string",
str: "12345678-9abc-def0-1234-56789abcdef0a",
want: UUID{},
wantErr: ErrInvalidLength,
},
{
name: "Invalid characters",
str: "12345678-9abc-defg-1234-56789abcdef0",
want: UUID{},
wantErr: errors.New("encoding/hex: invalid byte: U+0067 'g'"),
},
{
name: "Zero UUID",
str: "00000000-0000-0000-0000-000000000000",
want: UUID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
wantErr: nil,
},
{
name: "Max value UUID",
str: "ffffffff-ffff-ffff-ffff-ffffffffffff",
want: UUID{
0xff, 0xff, 0xff, 0xff,
0xff, 0xff,
0xff, 0xff,
0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
},
wantErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := FromString(tt.str)
if tt.wantErr != nil {
assert.EqualError(t, err, tt.wantErr.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.want, got)
})
}
}
func BenchmarkFromString(b *testing.B) {
uuidStr := "12345678-9abc-def0-1234-56789abcdef0"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = FromString(uuidStr)
}
}
func TestUUID_Time(t *testing.T) {
t.Parallel()
// Define a reference time for testing.
refTime := time.Date(2023, 5, 15, 12, 0, 0, 0, time.UTC)
// The timestamp in milliseconds (Unix timestamp * 1000 in 6 bytes).
timestampMillis := refTime.UnixMilli()
// Create bytes for the timestamp (first 6 bytes of UUID).
timestampBytes := []byte{
byte(timestampMillis >> 40),
byte(timestampMillis >> 32),
byte(timestampMillis >> 24),
byte(timestampMillis >> 16),
byte(timestampMillis >> 8),
byte(timestampMillis),
}
tests := []struct {
name string
uuid UUID
wantTime time.Time
wantOk bool
}{
{
name: "Version 7 UUID",
uuid: func() UUID {
var u UUID
// Set first 6 bytes to timestamp.
copy(u[:6], timestampBytes)
// Set version to 7 (0111 as the high nibble of byte 6).
u[6] = (u[6] & 0x0F) | 0x70
// Set variant to RFC 4122 (10xx as the high bits of byte 8).
u[8] = (u[8] & 0x3F) | 0x80
return u
}(),
wantTime: refTime,
wantOk: true,
},
{
name: "Version 4 UUID (not time-based)",
uuid: func() UUID {
var u UUID
// Set first 6 bytes to same timestamp to verify it's ignored.
copy(u[:6], timestampBytes)
// Set version to 4 (0100 as the high nibble of byte 6).
u[6] = (u[6] & 0x0F) | 0x40
return u
}(),
wantTime: time.Time{}, // Zero time for non-V7 UUIDs
wantOk: false,
},
{
name: "Zero UUID",
uuid: UUID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
wantTime: time.Time{}, // Zero time for version 0
wantOk: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotTime, gotOk := tt.uuid.Time()
assert.Equal(t, tt.wantOk, gotOk)
if tt.wantTime.IsZero() {
assert.True(t, gotTime.IsZero())
} else {
// Compare time at millisecond precision.
assert.Equal(t, tt.wantTime.UnixMilli(), gotTime.UnixMilli())
}
})
}
}
func BenchmarkUUID_Time(b *testing.B) {
uuid, err := NewV7()
require.NoError(b, err)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = uuid.Time()
}
}
func TestUUID_Version(t *testing.T) {
t.Parallel()
tests := []struct {
name string
uuid UUID
want int
}{
{
name: "Version 0 (invalid/nil UUID)",
uuid: UUID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
want: 0,
},
{
name: "Version 1 (time-based)",
uuid: UUID{
0x12, 0x34, 0x56, 0x78,
0x9a, 0xbc,
0x10, 0xf0, // 0x10 = version 1 in top nibble
0x12, 0x34,
0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
},
want: 1,
},
{
name: "Version 2 (DCE Security)",
uuid: UUID{
0x12, 0x34, 0x56, 0x78,
0x9a, 0xbc,
0x20, 0xf0, // 0x20 = version 2 in top nibble
0x12, 0x34,
0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
},
want: 2,
},
{
name: "Version 3 (name-based, MD5)",
uuid: UUID{
0x12, 0x34, 0x56, 0x78,
0x9a, 0xbc,
0x30, 0xf0, // 0x30 = version 3 in top nibble
0x12, 0x34,
0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
},
want: 3,
},
{
name: "Version 4 (random)",
uuid: UUID{
0x12, 0x34, 0x56, 0x78,
0x9a, 0xbc,
0x40, 0xf0, // 0x40 = version 4 in top nibble
0x12, 0x34,
0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
},
want: 4,
},
{
name: "Version 5 (name-based, SHA-1)",
uuid: UUID{
0x12, 0x34, 0x56, 0x78,
0x9a, 0xbc,
0x50, 0xf0, // 0x50 = version 5 in top nibble
0x12, 0x34,
0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
},
want: 5,
},
{
name: "Version 7 (time-ordered)",
uuid: UUID{
0x12, 0x34, 0x56, 0x78,
0x9a, 0xbc,
0x70, 0xf0, // 0x70 = version 7 in top nibble
0x12, 0x34,
0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
},
want: 7,
},
{
name: "Version 8 (custom)",
uuid: UUID{
0x12, 0x34, 0x56, 0x78,
0x9a, 0xbc,
0x80, 0xf0, // 0x80 = version 8 in top nibble
0x12, 0x34,
0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
},
want: 8,
},
{
name: "Version 15 (theoretical max)",
uuid: UUID{
0x12, 0x34, 0x56, 0x78,
0x9a, 0xbc,
0xf0, 0xf0, // 0xf0 = version 15 in top nibble
0x12, 0x34,
0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
},
want: 15,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.uuid.Version()
assert.Equal(t, tt.want, got)
})
}
}
func BenchmarkUUID_Version(b *testing.B) {
uuid, err := NewV7()
require.NoError(b, err)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = uuid.Version()
}
}