Files
Maven/internal/store/wipe_test.go
T
claude 82d9a3324e wipe: one command empties the box and leaves it standing (V-494)
There was no documented way to repair a poisoned box. Two invented facts
written during QA disabled world answering for every later turn (V-470), and
revert voids the SQL row while leaving the vector behind (V-493). This is the
operation that undoes both.

Store.Wipe drops every table sqlite_master reports and rebuilds from schema.sql
plus the migrations, rather than deleting from a hand-written list. A list has
to be edited whenever a table is added, and the once it is not, the wipe leaves
personal data behind while reporting success. It vacuums afterwards, because
free pages still hold readable text.

mavend -wipe prints every table and its row count and exits. That alone is a
dry run and answers what a QA session actually asks: what is on this box. It
deletes only with -confirm-wipe. Two flags, because the destructive reading of
one flag is the reading a mistyped command gets.

Nothing outside the database moves. Config, models, passkeys.json and the
encryption key are files.

QA isolation and onboarding are the other two thirds of V-494 and are not here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SoL7EBdYC5Mhz3DJd49GJy
2026-08-05 20:51:46 +04:00

100 lines
2.8 KiB
Go

package store
import (
"context"
"database/sql"
"path/filepath"
"testing"
"time"
)
// A wipe has to leave a working, empty install: every table still there, every
// migration still applied, and not one row of his anywhere (Vikunja #494).
func TestWipeEmptiesEveryTableAndLeavesTheSchemaUsable(t *testing.T) {
ctx := context.Background()
path := filepath.Join(t.TempDir(), "wipe.db")
s, err := Open(ctx, path)
if err != nil {
t.Fatal(err)
}
defer s.Close()
now := time.Now()
if _, err := s.WriteFact(ctx, now, KindSelf, "monitor", "новый", "tap:test", 1, sql.NullInt64{}); err != nil {
t.Fatal(err)
}
if _, err := s.WriteNote(ctx, now, "купил монитор", []float32{0.1, 0.2}, "tap:test"); err != nil {
t.Fatal(err)
}
if _, err := s.CaptureTask(ctx, Task{Text: "вернуть монитор", Source: "tap:test", Status: TaskOpen, CreatedTs: now}); err != nil {
t.Fatal(err)
}
before, err := s.WipeCounts(ctx)
if err != nil {
t.Fatal(err)
}
populated := 0
for _, c := range before {
if c.Rows > 0 {
populated++
}
}
if populated == 0 {
t.Fatal("nothing was written, so the wipe proves nothing")
}
if err := s.Wipe(ctx); err != nil {
t.Fatal(err)
}
after, err := s.WipeCounts(ctx)
if err != nil {
t.Fatal(err)
}
if len(after) != len(before) {
t.Errorf("table count changed across the wipe: %d before, %d after", len(before), len(after))
}
for _, c := range after {
if c.Rows != 0 {
t.Errorf("table %s still holds %d rows after the wipe", c.Table, c.Rows)
}
}
// The migrations are what make the schema usable, so the check that matters
// is a write through the newest columns, not a version number.
if _, err := s.CaptureTask(ctx, Task{Text: "новая задача", Source: "tap:test", Status: TaskOpen, CreatedTs: now}); err != nil {
t.Errorf("the store is not usable after a wipe: %v", err)
}
if _, err := s.WriteFact(ctx, now, KindSelf, "monitor", "другой", "tap:test", 1, sql.NullInt64{}); err != nil {
t.Errorf("the store is not usable after a wipe: %v", err)
}
}
// A wipe that reports success while leaving a table behind is the failure the
// drop-everything approach exists to prevent, so the count has to see the
// tables migrations added, not only the ones schema.sql declares.
func TestWipeCountsSeeMigratedTables(t *testing.T) {
ctx := context.Background()
s, err := Open(ctx, filepath.Join(t.TempDir(), "counts.db"))
if err != nil {
t.Fatal(err)
}
defer s.Close()
counts, err := s.WipeCounts(ctx)
if err != nil {
t.Fatal(err)
}
seen := map[string]bool{}
for _, c := range counts {
seen[c.Table] = true
}
// One from schema.sql, three from migrations, one from a table rebuild.
for _, want := range []string{"facts", "memory_vectors", "tasks", "ecosystem_traces", "nudges"} {
if !seen[want] {
t.Errorf("wipe does not see table %s", want)
}
}
}