feat(store): add DeleteUser with cascading account deletion
This commit is contained in:
@@ -89,3 +89,76 @@ func TestProvisionUser_TwoUsersGetIndependentTaxonomies(t *testing.T) {
|
||||
t.Fatal("expected each user's seeded kinds to be distinct rows")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteUser_RemovesUserAndCascadesEverything(t *testing.T) {
|
||||
db := openTestDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
userID, err := db.ProvisionUser(ctx, "delete-me", "Delete Me")
|
||||
if err != nil {
|
||||
t.Fatalf("ProvisionUser: %v", err)
|
||||
}
|
||||
|
||||
activityID, err := db.UpsertActivity(ctx, userID, Activity{GarminActivityID: 1, StartTimeUTC: "2026-07-11 06:00:00", RawJSON: "{}"})
|
||||
if err != nil {
|
||||
t.Fatalf("UpsertActivity: %v", err)
|
||||
}
|
||||
kindID, err := db.CreateWorkoutKind(ctx, userID, WorkoutKind{Name: "Test Delete Kind", RuleJSON: `{"match":"all","conditions":[]}`, IsActive: true})
|
||||
if err != nil {
|
||||
t.Fatalf("CreateWorkoutKind: %v", err)
|
||||
}
|
||||
if err := db.ReplaceLaps(ctx, userID, activityID, []Lap{{LapIndex: 1}}); err != nil {
|
||||
t.Fatalf("ReplaceLaps: %v", err)
|
||||
}
|
||||
if err := db.ReplaceActivitySamples(ctx, userID, activityID, []Sample{{ElapsedSeconds: 0}}); err != nil {
|
||||
t.Fatalf("ReplaceActivitySamples: %v", err)
|
||||
}
|
||||
if _, err := db.InsertKindAssignment(ctx, userID, KindAssignment{
|
||||
ActivityID: activityID, WorkoutKindID: &kindID, AssignmentSource: AssignmentSourceRuleEngine,
|
||||
Status: AssignmentStatusAssigned, CandidateKindsJSON: "[]",
|
||||
}); err != nil {
|
||||
t.Fatalf("InsertKindAssignment: %v", err)
|
||||
}
|
||||
minPace := 300.0
|
||||
if err := db.UpdateWorkoutTypePace(ctx, userID, WorkoutTypePace{WorkoutKindID: kindID, PaceMinSecPerKm: &minPace}); err != nil {
|
||||
t.Fatalf("UpdateWorkoutTypePace: %v", err)
|
||||
}
|
||||
if err := db.UpdateSyncState(ctx, userID, "2020-01-01", true); err != nil {
|
||||
t.Fatalf("UpdateSyncState: %v", err)
|
||||
}
|
||||
if _, err := db.StartSyncRun(ctx, userID, SyncKindBackfill); err != nil {
|
||||
t.Fatalf("StartSyncRun: %v", err)
|
||||
}
|
||||
|
||||
if err := db.DeleteUser(ctx, userID); err != nil {
|
||||
t.Fatalf("DeleteUser: %v", err)
|
||||
}
|
||||
|
||||
if _, found, err := db.GetUserBySub(ctx, "delete-me"); err != nil || found {
|
||||
t.Fatalf("expected user gone after DeleteUser, found=%v err=%v", found, err)
|
||||
}
|
||||
|
||||
checks := []struct {
|
||||
query string
|
||||
arg int64
|
||||
}{
|
||||
{`SELECT COUNT(*) FROM profile WHERE user_id = ?`, userID},
|
||||
{`SELECT COUNT(*) FROM workout_kinds WHERE user_id = ?`, userID},
|
||||
{`SELECT COUNT(*) FROM workout_type_paces WHERE workout_kind_id = ?`, kindID},
|
||||
{`SELECT COUNT(*) FROM activities WHERE user_id = ?`, userID},
|
||||
{`SELECT COUNT(*) FROM laps WHERE activity_id = ?`, activityID},
|
||||
{`SELECT COUNT(*) FROM activity_samples WHERE activity_id = ?`, activityID},
|
||||
{`SELECT COUNT(*) FROM kind_assignments WHERE activity_id = ?`, activityID},
|
||||
{`SELECT COUNT(*) FROM sync_state WHERE user_id = ?`, userID},
|
||||
{`SELECT COUNT(*) FROM sync_runs WHERE user_id = ?`, userID},
|
||||
}
|
||||
for _, c := range checks {
|
||||
var count int
|
||||
if err := db.QueryRowContext(ctx, c.query, c.arg).Scan(&count); err != nil {
|
||||
t.Fatalf("count query %q: %v", c.query, err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Errorf("query %q: got %d rows, want 0 after DeleteUser", c.query, count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user