Files
healthlog/internal/store/migration_test.go
T
av 34e5109b6d непокрытые секции доставки видны в статусе разбора
- половина потока (50 доставок из 104) не несёт metrics вовсе и до сих пор
  числилась parsed: ретеншен, поверив статусу, срезал бы тела stateOfMind,
  которых в экспорте Apple нет
- разбор перечисляет верхнеуровневые ключи data, непокрытые проглатываются
  декодированием: тело 40 МиБ из непокрытой секции удерживает 0 МиБ
- статус partial и колонка delivery.uncovered_sections; миграция переводит
  прежние parsed в pending — им верить нельзя
- витрина не изменилась: отпечаток совпал с прогоном до изменения
2026-08-01 21:25:59 +03:00

91 lines
3.2 KiB
Go

package store
import (
"context"
"io/fs"
"path/filepath"
"testing"
"github.com/jmoiron/sqlx"
"github.com/pressly/goose/v3"
)
// Миграция 00005 переводит существующие `parsed` в `pending`. Статус, который
// поставил код, не различавший частичного разбора, ничего не доказывает: под
// ним лежат и полностью разобранные доставки, и доставки из одних тренировок с
// нулём точек (48 из 99 на живом архиве). Ретеншен, ради которого признак и
// заводится, поверил бы им и срезал тела — а для stateOfMind это необратимо.
func TestMigrationПрежниеParsedСтановятсяPending(t *testing.T) {
db, err := sqlx.Connect("sqlite", dsn(filepath.Join(t.TempDir(), "healthlog.db")))
if err != nil {
t.Fatalf("открытие базы: %v", err)
}
t.Cleanup(func() { _ = db.Close() })
sub, err := fs.Sub(migrationsFS, "migrations")
if err != nil {
t.Fatalf("миграции: %v", err)
}
p, err := goose.NewProvider(goose.DialectSQLite3, db.DB, sub)
if err != nil {
t.Fatalf("провайдер: %v", err)
}
ctx := context.Background()
// Состояние ДО этой миграции: схема четвёртой версии.
if _, err := p.UpTo(ctx, 4); err != nil {
t.Fatalf("миграция до 4: %v", err)
}
const insert = `
INSERT INTO delivery (id, received_at, automation_name, automation_id,
aggregation, period, session_id, bytes, sha256, raw_path,
parse_status, points)
VALUES (?, '2025-06-05T10:00:00Z', '', '', '', '', '', 0, '-', '-', ?, ?)`
for _, c := range []struct {
id string
status string
points int
}{
{"d-parsed-points", "parsed", 12},
{"d-parsed-empty", "parsed", 0},
{"d-failed", "failed", 0},
{"d-pending", "pending", 0},
} {
if _, err := db.ExecContext(ctx, insert, c.id, c.status, c.points); err != nil {
t.Fatalf("вставка %s: %v", c.id, err)
}
}
if _, err := p.Up(ctx); err != nil {
t.Fatalf("миграция до последней: %v", err)
}
want := map[string]string{
// Переводятся ОБА варианта parsed, а не только пустой: наблюдение
// «секции не смешиваются» собрано за двое суток потока, и ставить на
// него необратимое удаление тел значит повторять ошибку, ради которой
// задача и заведена.
"d-parsed-points": "pending",
"d-parsed-empty": "pending",
"d-failed": "failed",
"d-pending": "pending",
}
for id, wantStatus := range want {
var status, sections string
err := db.QueryRowContext(ctx,
`SELECT parse_status, uncovered_sections FROM delivery WHERE id = ?`, id).
Scan(&status, &sections)
if err != nil {
t.Fatalf("чтение %s: %v", id, err)
}
if status != wantStatus {
t.Errorf("%s: статус %q, ожидался %q", id, status, wantStatus)
}
if sections != "[]" {
t.Errorf("%s: список %q, ожидался `[]`", id, sections)
}
}
}