package ident import ( "strings" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // Идентификатор — ULID в нижнем регистре: постоянная ширина, алфавит Crockford, // и разбор своего же значения его не меняет. func TestNewIsCanonical(t *testing.T) { id := New() assert.Len(t, id, Len) assert.Equal(t, strings.ToLower(id), id, "идентификатор выдан не в нижнем регистре") parsed, ok := Parse(id) require.True(t, ok, "свой же идентификатор не разобрался") assert.Equal(t, id, parsed, "разбор изменил канонический вид") } // Порядок выдачи и порядок сравнения строк совпадают — в том числе внутри одной // миллисекунды: колонка времени несёт секунды, и «новые сверху» держит ключ. func TestNewIsMonotonic(t *testing.T) { previous := New() for range 10000 { next := New() require.Less(t, previous, next, "идентификатор выдан не по возрастанию") previous = next } } // Выдача идёт из нескольких потоков разом: два одинаковых ключа означали бы // отказ вставки на живой базе. func TestNewIsUniqueUnderConcurrency(t *testing.T) { const goroutines = 8 const each = 500 var ( mu sync.Mutex all = map[string]bool{} ) var wg sync.WaitGroup for range goroutines { wg.Add(1) go func() { defer wg.Done() own := make([]string, 0, each) for range each { own = append(own, New()) } mu.Lock() defer mu.Unlock() for _, id := range own { all[id] = true } }() } wg.Wait() assert.Len(t, all, goroutines*each, "выданы одинаковые идентификаторы") } // Разбор проверяет вид и приводит регистр: значение приходит от спрашивающего, а // сравнение в базе побайтово. func TestParse(t *testing.T) { id := New() upper, ok := Parse(strings.ToUpper(id)) require.True(t, ok, "верхний регистр не принят: алфавит к нему нечувствителен") assert.Equal(t, id, upper, "разбор не привёл регистр к каноническому") for name, value := range map[string]string{ "пусто": "", "короче": id[:Len-1], "длиннее": id + "0", "знак вне алфавита": strings.Repeat("u", Len), "время за пределом": "8" + id[1:], "не латиница": strings.Repeat("я", Len), } { t.Run(name, func(t *testing.T) { _, ok := Parse(value) assert.False(t, ok, "негодное значение принято разбором") }) } } // Время, зашитое в идентификатор, идёт в ту же сторону, что и колонка времени. func TestTimestampGrowsWithIssue(t *testing.T) { first, ok := Timestamp(New()) require.True(t, ok) second, ok := Timestamp(New()) require.True(t, ok) assert.False(t, second.Before(first), "время в ключе пошло назад") }