Дозакрыты находки ревью по слиянию сущностей
- Правило покрытия получило второй разряд (условный, как у точек), запрет вырождения формы и счёт содержательных элементов ряда: скелет из скаляров и ряд из null больше не затирают маршрут. Победитель внутри доставки стал функцией множества версий — общим помощником с точками, — а провенанс поднимается и при совпавшем хеше, иначе отложенная доставка возвращала витрину к прежнему содержимому. - Одно поле не того типа больше не уносит сущность, а пропуски видны в учётной записи доставки (миграция 00008, NULL = «не измерялось»); каноническая форма считается один раз и вне транзакции; откат бинаря поверх новой схемы отказывает на старте; текст ошибки разбора не несёт значений из тела. - Ревью кода профилем deep (девять проходов) нашло две регрессии и обе закрыты: безусловный второй разряд запирал законный досчёт навсегда, а выбор победителя был квадратичен по числу присланных версий одного ключа.
This commit is contained in:
@@ -108,3 +108,88 @@ func seed(t *testing.T, path string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Откат бинаря поверх новой схемы обязан отказывать, а не стартовать молча:
|
||||
// старый бинарь незнакомые секции игнорирует и доставки за окно отката помечает
|
||||
// разобранными — ничто не намекает, что для этого окна нужна пересборка. Класс
|
||||
// «молчание», и после ретеншена тел окно становится невосстановимым.
|
||||
func TestOpenОтвергаетСхемуИзБудущего(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
path := filepath.Join(t.TempDir(), "healthlog.db")
|
||||
seed(t, path)
|
||||
|
||||
db, err := sql.Open("sqlite", "file:"+path)
|
||||
if err != nil {
|
||||
t.Fatalf("sql.Open: %v", err)
|
||||
}
|
||||
if _, err := db.Exec(
|
||||
`INSERT INTO goose_db_version (version_id, is_applied, tstamp)
|
||||
VALUES (99, 1, datetime('now'))`); err != nil {
|
||||
t.Fatalf("вставка версии из будущего: %v", err)
|
||||
}
|
||||
_ = db.Close()
|
||||
|
||||
st, err := store.Open(path)
|
||||
if err == nil {
|
||||
_ = st.Close()
|
||||
t.Fatal("Open молча открыл базу со схемой, которой бинарь не знает")
|
||||
}
|
||||
if !errors.Is(err, store.ErrSchemaMismatch) {
|
||||
t.Errorf("Open дал %v, ожидался ErrSchemaMismatch", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Версия базы НИЖЕ версии бинаря отказом быть не должна: ради этого случая
|
||||
// миграции и существуют. Асимметрия только у Open — OpenForRead строг.
|
||||
func TestOpenНоваяБазаМигрирует(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
path := filepath.Join(t.TempDir(), "fresh.db")
|
||||
st, err := store.Open(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Open новой базы: %v", err)
|
||||
}
|
||||
if err := st.Close(); err != nil {
|
||||
t.Fatalf("Close: %v", err)
|
||||
}
|
||||
// Повторное открытие уже мигрированной базы тоже проходит: current == target.
|
||||
st2, err := store.Open(path)
|
||||
if err != nil {
|
||||
t.Fatalf("повторный Open: %v", err)
|
||||
}
|
||||
_ = st2.Close()
|
||||
}
|
||||
|
||||
// База без журнала миграций нашей не является, и сказать это надо прямо и
|
||||
// сразу. Через goose такой вопрос стоил бы трёх секунд повторов и ответа
|
||||
// «attempt to write a readonly database» — то есть оператор, спросивший про
|
||||
// версию схемы, получил бы ответ про права на файл.
|
||||
func TestOpenForReadЧужаяБазаОтвергаетсяБыстро(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
path := filepath.Join(t.TempDir(), "alien.db")
|
||||
db, err := sql.Open("sqlite", "file:"+path)
|
||||
if err != nil {
|
||||
t.Fatalf("sql.Open: %v", err)
|
||||
}
|
||||
if _, err := db.Exec(`CREATE TABLE t (a INTEGER)`); err != nil {
|
||||
t.Fatalf("создание чужой таблицы: %v", err)
|
||||
}
|
||||
_ = db.Close()
|
||||
|
||||
start := time.Now()
|
||||
st, err := store.OpenForRead(path)
|
||||
if err == nil {
|
||||
_ = st.Close()
|
||||
t.Fatal("чужая база открылась на чтение")
|
||||
}
|
||||
if !errors.Is(err, store.ErrSchemaMismatch) {
|
||||
t.Errorf("OpenForRead дал %v, ожидался ErrSchemaMismatch", err)
|
||||
}
|
||||
// Проверяется свойство «отказ не идёт через повторы записи», а не
|
||||
// конкретная скорость: повторы у goose — три по секунде.
|
||||
if d := time.Since(start); d > time.Second {
|
||||
t.Errorf("отказ занял %v — путь идёт через попытки записи", d)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user