feat: разбор и хранение тренировок и состояния разума
- секции `workouts` и `stateOfMind` покрыты разбором: тренировка лежит одной строкой вместе с маршрутом и внутренними рядами, запись — по ключу `род + id`; миграция 00007 заводит обе таблицы и возвращает в очередь `partial`-доставки с этими ключами - сущность заменяется целиком, но условно: приехавшая побеждает, если не теряет содержания сохранённой (множество ключей и длины верхнеуровневых массивов), а при равном содержании выигрывает версия из более поздней доставки ЖУРНАЛА — «побеждает приехавшая» было бы функцией порядка свёртки, и живая витрина расходилась бы с пересборкой молча - отпечаток витрины покрывает тренировки и записи и снимается одним снимком базы; отчёт `reindex` считает «было и стало» по каждой единице хранения
This commit is contained in:
@@ -88,3 +88,77 @@ func TestMigrationПрежниеParsedСтановятсяPending(t *testing.T)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Миграция 00007 исполняет правило «покрыли секцию — пересверните»: список
|
||||
// непокрытых ключей это снимок покрытия на момент свёртки, и доставки,
|
||||
// свёрнутые до того, как workouts и stateOfMind стали покрытыми, остались бы
|
||||
// `partial` со старым списком навсегда — а ретеншен вечно щадил бы их тела.
|
||||
//
|
||||
// Перевод ТОЧЕЧНЫЙ: доставка, у которой непокрыта только `ecg`, пересворачивать
|
||||
// нечего, и трогать её значило бы гонять весь архив на каждую новую секцию.
|
||||
func TestMigrationПокрытыеСекцииВозвращаютсяВОчередь(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, 6); err != nil {
|
||||
t.Fatalf("миграция до 6: %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, uncovered_sections)
|
||||
VALUES (?, '2025-06-05T10:00:00Z', '', '', '', '', '', 0, '-', '-', ?, 0, ?)`
|
||||
cases := []struct {
|
||||
id string
|
||||
status string
|
||||
uncovered string
|
||||
want string
|
||||
}{
|
||||
{"d-workouts", ParsePartial, `["workouts"]`, ParsePending},
|
||||
{"d-mind", ParsePartial, `["stateOfMind"]`, ParsePending},
|
||||
{"d-both", ParsePartial, `["stateOfMind","workouts"]`, ParsePending},
|
||||
{"d-mixed", ParsePartial, `["ecg","workouts"]`, ParsePending},
|
||||
// Ничего из ставшего покрытым: трогать нечего.
|
||||
{"d-ecg", ParsePartial, `["ecg"]`, ParsePartial},
|
||||
// Подстрока имени секции — не имя секции: отбор идёт по элементу
|
||||
// массива, иначе чужое тело управляло бы тем, что мы пересворачиваем.
|
||||
{"d-lookalike", ParsePartial, `["myworkoutsx"]`, ParsePartial},
|
||||
// Статус `failed` возвращает только пересборка, а `parsed` этой
|
||||
// миграцией не трогается: у него пустой список непокрытых.
|
||||
{"d-failed", ParseFailed, `["workouts"]`, ParseFailed},
|
||||
{"d-parsed", ParseDone, `[]`, ParseDone},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if _, err := db.ExecContext(ctx, insert, c.id, c.status, c.uncovered); err != nil {
|
||||
t.Fatalf("вставка %s: %v", c.id, err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := p.UpTo(ctx, 7); err != nil {
|
||||
t.Fatalf("миграция до 7: %v", err)
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
var got string
|
||||
if err := db.GetContext(ctx, &got, `SELECT parse_status FROM delivery WHERE id = ?`, c.id); err != nil {
|
||||
t.Fatalf("чтение %s: %v", c.id, err)
|
||||
}
|
||||
if got != c.want {
|
||||
t.Errorf("%s: статус %q, ожидался %q (непокрытые %s)", c.id, got, c.want, c.uncovered)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user