- половина потока (50 доставок из 104) не несёт metrics вовсе и до сих пор числилась parsed: ретеншен, поверив статусу, срезал бы тела stateOfMind, которых в экспорте Apple нет - разбор перечисляет верхнеуровневые ключи data, непокрытые проглатываются декодированием: тело 40 МиБ из непокрытой секции удерживает 0 МиБ - статус partial и колонка delivery.uncovered_sections; миграция переводит прежние parsed в pending — им верить нельзя - витрина не изменилась: отпечаток совпал с прогоном до изменения
91 lines
3.2 KiB
Go
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, §ions)
|
|
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)
|
|
}
|
|
}
|
|
}
|