package store import ( "context" "encoding/json" "path/filepath" "testing" "time" ) // Внутренний тест, потому что проверяемое наружу не отдаётся: `updated_at` — // колонка, а не поле модели. Обещание «метка означает изменение содержимого, а // не касание строки» держится только этой проверкой, и внешний тест для неё // потребовал бы публичного метода ради теста. func TestMergeПовторНеДвигаетМеткуИзменения(t *testing.T) { t.Parallel() ctx := context.Background() st, err := Open(filepath.Join(t.TempDir(), "healthlog.db")) if err != nil { t.Fatalf("открытие базы: %v", err) } t.Cleanup(func() { _ = st.Close() }) raw := json.RawMessage(`{"id":"w1","route":[{"lat":1},{"lat":2}],"totalEnergy":{"qty":20}}`) wo := IncomingEntity{ ID: "w1", Kind: "workouts", Start: time.Date(2025, 6, 5, 7, 0, 0, 0, time.UTC), End: time.Date(2025, 6, 5, 7, 10, 0, 0, time.UTC), Raw: raw, } merge := func(id string, at time.Time) MergeStats { t.Helper() s, err := st.Merge(ctx, Incoming{Workouts: []IncomingEntity{wo}}, DeliveryRef{ID: id, ReceivedAt: at}) if err != nil { t.Fatalf("слияние: %v", err) } return s } updatedAt := func() string { t.Helper() var v string if err := st.db.QueryRowContext(ctx, `SELECT updated_at FROM workout WHERE id = 'w1'`).Scan(&v); err != nil { t.Fatalf("чтение updated_at: %v", err) } return v } base := time.Date(2025, 6, 5, 8, 0, 0, 0, time.UTC) merge("d1", base) before := updatedAt() // Тренировка приезжает до 26 раз, пока источник её досчитывает. Провенанс // при этом обязан подняться, а метка изменения — нет: иначе она становится // меткой касания строки, и запрос «что изменилось с момента X» получает 26 // ложных изменений, неотличимых от настоящего досчёта. merge("d2", base.Add(5*time.Minute)) if after := updatedAt(); after != before { t.Errorf("метка изменения двинулась без изменения содержимого: %s → %s", before, after) } w, err := st.Workout(ctx, "w1") if err != nil { t.Fatalf("чтение тренировки: %v", err) } if w.Delivery != "d2" { t.Fatal("провенанс не обновился — тест проверяет не то") } }