- база своя: два пула, захват одним UPDATE ... RETURNING, шаги схемы на goose под файловым замком, одна миграция начальной схемы вместо семи прежних - транспорт переписан на net/http: свои слои, свой ограничитель частоты, отдача файла с проверкой владельца; панель /_/ и пространство /api/ исчезли - по находкам ревью: журнал не пишет путь под корнем приложения, ключ бюджета читается справа налево, узнавание известного идёт читающим пулом
102 lines
3.3 KiB
Go
102 lines
3.3 KiB
Go
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), "время в ключе пошло назад")
|
|
}
|