Дозакрыты находки ревью по слиянию сущностей
- Правило покрытия получило второй разряд (условный, как у точек), запрет вырождения формы и счёт содержательных элементов ряда: скелет из скаляров и ряд из null больше не затирают маршрут. Победитель внутри доставки стал функцией множества версий — общим помощником с точками, — а провенанс поднимается и при совпавшем хеше, иначе отложенная доставка возвращала витрину к прежнему содержимому. - Одно поле не того типа больше не уносит сущность, а пропуски видны в учётной записи доставки (миграция 00008, NULL = «не измерялось»); каноническая форма считается один раз и вне транзакции; откат бинаря поверх новой схемы отказывает на старте; текст ошибки разбора не несёт значений из тела. - Ревью кода профилем deep (девять проходов) нашло две регрессии и обе закрыты: безусловный второй разряд запирал законный досчёт навсегда, а выбор победителя был квадратичен по числу присланных версий одного ключа.
This commit is contained in:
@@ -386,3 +386,131 @@ func TestFoldНепонятоеСодержимоеВыводитИзОчере
|
||||
t.Errorf("parse_status = %q, ожидался %q", status, store.ParseFailed)
|
||||
}
|
||||
}
|
||||
|
||||
// Пропущенная сущность обязана быть видна в БАЗЕ, а не только в логе. Ретеншен
|
||||
// сырого архива решает «что потеряется, если тело удалить», по учётной записи,
|
||||
// и до этого счётчика получал ответ «терять нечего» ровно там, где потеряна
|
||||
// тренировка с маршрутом: сущность в витрину не попала, список непокрытых
|
||||
// секций пуст, статус `parsed`.
|
||||
func TestFoldПропускиСущностейВидныВУчёте(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f, arch, st := newFold(t)
|
||||
ctx := context.Background()
|
||||
|
||||
deliver(t, arch, st, "d1", "Minutes", "a1", fixture(t, "handmade_entities.json"))
|
||||
stats, err := f.Fold(ctx, "d1")
|
||||
if err != nil {
|
||||
t.Fatalf("свёртка: %v", err)
|
||||
}
|
||||
skipped := stats.SkippedNoID + stats.SkippedEntityNoTime + stats.SkippedEntityMalformed
|
||||
if skipped == 0 {
|
||||
t.Fatal("фикстура перестала давать пропуски — тест проверяет не то")
|
||||
}
|
||||
|
||||
d, err := st.LastDelivery(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("чтение доставки: %v", err)
|
||||
}
|
||||
if d.SkippedEntities == nil {
|
||||
t.Fatal("счётчик пропусков пуст: доставка выглядит как «не измерялась»")
|
||||
}
|
||||
if *d.SkippedEntities != int64(skipped) {
|
||||
t.Errorf("в базе %d пропусков, разбор дал %d", *d.SkippedEntities, skipped)
|
||||
}
|
||||
}
|
||||
|
||||
// Число замещает прежнее значение целиком, включая замещение нулём: доставка,
|
||||
// пропуски которой исчезли вместе с поумневшим разбором, не должна остаться
|
||||
// помеченной навсегда.
|
||||
func TestFoldПересвёрткаБезПропусковОбнуляетСчётчик(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f, arch, st := newFold(t)
|
||||
ctx := context.Background()
|
||||
|
||||
deliver(t, arch, st, "d1", "Minutes", "a1", fixture(t, "handmade_entities.json"))
|
||||
if _, err := f.Fold(ctx, "d1"); err != nil {
|
||||
t.Fatalf("свёртка: %v", err)
|
||||
}
|
||||
before, err := st.LastDelivery(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("чтение доставки: %v", err)
|
||||
}
|
||||
if before.SkippedEntities == nil || *before.SkippedEntities == 0 {
|
||||
t.Fatal("фикстура перестала давать пропуски — тест проверяет не то")
|
||||
}
|
||||
|
||||
// Тело подменяется на такое же, но без кривых элементов: ровно то, что
|
||||
// произойдёт при пересвёртке поумневшим разбором.
|
||||
deliver(t, arch, st, "d2", "Minutes", "a1", fixture(t, "workout_indoor.json"))
|
||||
if _, err := f.Fold(ctx, "d2"); err != nil {
|
||||
t.Fatalf("повторная свёртка: %v", err)
|
||||
}
|
||||
after, err := st.LastDelivery(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("чтение доставки: %v", err)
|
||||
}
|
||||
if after.SkippedEntities == nil || *after.SkippedEntities != 0 {
|
||||
t.Errorf("счётчик %v, ожидался ноль", after.SkippedEntities)
|
||||
}
|
||||
}
|
||||
|
||||
// Доставка, свёрнутая разбором, который пропусков не считал, обязана быть
|
||||
// отличима от доставки с нулём: подстановка нуля объявила бы её проверенной, и
|
||||
// ретеншен получил бы ложное «терять нечего» с видом измерения.
|
||||
func TestFoldДоНачалаУчётаПропускиНеИзмерены(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, arch, st := newFold(t)
|
||||
ctx := context.Background()
|
||||
|
||||
deliver(t, arch, st, "d1", "Minutes", "a1", fixture(t, "minute.json"))
|
||||
d, err := st.LastDelivery(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("чтение доставки: %v", err)
|
||||
}
|
||||
if d.SkippedEntities != nil {
|
||||
t.Errorf("несвёрнутая доставка отдаёт %d вместо «не измерялось»", *d.SkippedEntities)
|
||||
}
|
||||
}
|
||||
|
||||
// Отказ, при котором разбор не досчитал, обязан оставить счётчик НЕТРОНУТЫМ.
|
||||
// Ноль здесь означал бы «проверено, терять нечего» — то самое ложное измерение,
|
||||
// ради отказа от которого колонка заведена без умолчания. Тело при этом может
|
||||
// нести сотни тренировок, ни одна из которых не сохранена.
|
||||
func TestFoldОтказРазбораНеПодделываетСчётчикПропусков(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f, arch, st := newFold(t)
|
||||
ctx := context.Background()
|
||||
|
||||
// Сперва успешная свёртка: счётчик измерен и ненулевой.
|
||||
deliver(t, arch, st, "d1", "Minutes", "a1", fixture(t, "handmade_entities.json"))
|
||||
if _, err := f.Fold(ctx, "d1"); err != nil {
|
||||
t.Fatalf("свёртка: %v", err)
|
||||
}
|
||||
before, err := st.LastDelivery(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("чтение доставки: %v", err)
|
||||
}
|
||||
if before.SkippedEntities == nil || *before.SkippedEntities == 0 {
|
||||
t.Fatal("фикстура перестала давать пропуски — тест проверяет не то")
|
||||
}
|
||||
|
||||
// Теперь тело, которое разбор не понимает: секция есть, но конверт оборван.
|
||||
deliver(t, arch, st, "d2", "Minutes", "a1", []byte(`{"data":{"workouts":[`))
|
||||
if _, err := f.Fold(ctx, "d2"); err == nil {
|
||||
t.Fatal("разбор оборванного тела не отказал")
|
||||
}
|
||||
after, err := st.LastDelivery(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("чтение доставки: %v", err)
|
||||
}
|
||||
if after.ID != "d2" {
|
||||
t.Fatalf("прочитана доставка %q, ожидалась d2", after.ID)
|
||||
}
|
||||
if after.SkippedEntities != nil {
|
||||
t.Errorf("отказ разбора записал %d пропусков как измерение", *after.SkippedEntities)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user