package store import ( "context" "testing" ) func userVersion(t *testing.T, s *Store) int { t.Helper() var v int if err := s.db.QueryRowContext(context.Background(), "PRAGMA user_version").Scan(&v); err != nil { t.Fatalf("read user_version: %v", err) } return v } func TestMigrateAppliesOnceAndIsIdempotent(t *testing.T) { ctx := context.Background() s := newTestStore(t) // The 1 migration in the built-in slice (tools scope) was applied on Open. startVer := len(migrations) if v := userVersion(t, s); v != startVer { t.Fatalf("fresh DB user_version = %d, want %d", v, startVer) } // Append a fake migration and run it: creates a throwaway table, bumps by 1. migrations = append(migrations, `CREATE TABLE migrate_probe (id INTEGER PRIMARY KEY)`) t.Cleanup(func() { migrations = migrations[:len(migrations)-1] }) if err := migrate(ctx, s.db); err != nil { t.Fatalf("migrate: %v", err) } want := startVer + 1 if v := userVersion(t, s); v != want { t.Fatalf("after migrate user_version = %d, want %d", v, want) } if _, err := s.db.ExecContext(ctx, "INSERT INTO migrate_probe DEFAULT VALUES"); err != nil { t.Fatalf("probe table not created: %v", err) } // Second run is a no-op — re-running the CREATE would error (no IF NOT EXISTS). if err := migrate(ctx, s.db); err != nil { t.Fatalf("migrate second run not idempotent: %v", err) } if v := userVersion(t, s); v != want { t.Fatalf("after re-migrate user_version = %d, want %d", v, want) } } // Migration #18 clears the calendar keys written while safeKey dropped // Cyrillic. Those rows are indistinguishable from real events on read, so // leaving them would recite one meeting as several (Vikunja #443). func TestCollapsedCalendarKeysAreDropped(t *testing.T) { ctx := context.Background() s := newTestStore(t) rows := []string{ "calendar_event_20260804_--", // "Встреча с Аней" under the old rule "calendar_event_20260804_", // a one-word Russian summary "calendar_event_20260804_Встреча-с-Аней", // the new format "calendar_event_20260804_Standup", // an ASCII summary, always fine } for _, key := range rows { if _, err := s.db.ExecContext(ctx, `INSERT INTO facts (ts, kind, key, value, source, confidence) VALUES (0, 'env', ?, 'x', 'poll:caldav', 1.0)`, key); err != nil { t.Fatalf("seed %q: %v", key, err) } } if _, err := s.db.ExecContext(ctx, migrations[17]); err != nil { t.Fatalf("migration 18: %v", err) } var got int if err := s.db.QueryRowContext(ctx, `SELECT count(*) FROM facts WHERE key LIKE 'calendar_event_%'`).Scan(&got); err != nil { t.Fatal(err) } if got != 2 { t.Fatalf("%d calendar rows left, want the 2 that identify their event", got) } }