package ident_test import ( "errors" "strings" "testing" "time" "git.vakhrushev.me/av/healthlog/internal/ident" ) func TestNewIDIsLowercaseULID(t *testing.T) { id := ident.NewID() if len(id) != 26 { t.Errorf("длина = %d, ожидалось 26: %q", len(id), id) } if id != strings.ToLower(id) { t.Errorf("id не в нижнем регистре: %q", id) } if ident.NewID() == id { t.Error("два вызова вернули один и тот же id") } } // Идентификаторы сортируемы по времени создания — на этом держится // хронология выборок и корреляция в логах. func TestNewIDSortsByCreationTime(t *testing.T) { prev := ident.NewID() for range 10 { next := ident.NewID() if next <= prev { t.Fatalf("порядок нарушен: %q после %q", next, prev) } prev = next } } // Внешний id приводится к каноническому виду: сравнение строк в SQLite // побайтовое, а base32 ULID при декодировании нечувствителен к регистру. func TestParseNormalizesCase(t *testing.T) { id := ident.NewID() got, err := ident.Parse(strings.ToUpper(id)) if err != nil { t.Fatalf("Parse: %v", err) } if got != id { t.Errorf("Parse вернул %q, ожидалось %q", got, id) } } func TestParseRejectsGarbage(t *testing.T) { for _, s := range []string{"", "не-id", "01k1", strings.Repeat("z", 26)} { if _, err := ident.Parse(s); !errors.Is(err, ident.ErrInvalid) { t.Errorf("Parse(%q): ошибка = %v, ожидалась ErrInvalid", s, err) } } } // Время из ULID нужно телу, лежащему в архиве без учётной записи: другого // источника метки приёма у него нет. func TestTimeOfВUTCИСекундах(t *testing.T) { t.Parallel() id := ident.NewID() at, err := ident.TimeOf(id) if err != nil { t.Fatalf("TimeOf(%q): %v", id, err) } // UTC, а не локальная зона: ulid.Time собирает время через time.Unix, то // есть в зоне машины, и метка подобранного тела сравнивалась бы с меткой из // БД по-разному на разных машинах. if at.Location() != time.UTC { t.Errorf("зона %v, ожидался UTC", at.Location()) } // Секундная точность — та же, что у store.Now: метка уезжает в колонку // фиксированной ширины, где лексикографический порядок равен хронологии. if at.Nanosecond() != 0 { t.Errorf("метка %v несёт доли секунды", at) } if d := time.Since(at); d < 0 || d > time.Minute { t.Errorf("метка %v далека от настоящего времени (%v)", at, d) } } // Монотонность ULID — то, на чём стоит порядок проигрывания журнала для // подобранных тел. func TestTimeOfСохраняетПорядок(t *testing.T) { t.Parallel() first, err := ident.TimeOf(ident.NewID()) if err != nil { t.Fatalf("TimeOf: %v", err) } time.Sleep(1100 * time.Millisecond) second, err := ident.TimeOf(ident.NewID()) if err != nil { t.Fatalf("TimeOf: %v", err) } if !second.After(first) { t.Errorf("порядок меток не сохранился: %v, затем %v", first, second) } } func TestTimeOfОтвергаетНеИдентификатор(t *testing.T) { t.Parallel() for _, s := range []string{"", "не-ulid", "01kyzbb5zy4cbkbc0agb6ad07", "readme.txt"} { if _, err := ident.TimeOf(s); !errors.Is(err, ident.ErrInvalid) { t.Errorf("TimeOf(%q) дал %v, ожидался ErrInvalid", s, err) } } }