package store import ( "context" "testing" ) func TestGetUserBySub_NotFoundReturnsFalseNotError(t *testing.T) { db := openTestDB(t) _, found, err := db.GetUserBySub(context.Background(), "no-such-sub") if err != nil { t.Fatalf("GetUserBySub: %v", err) } if found { t.Fatal("expected found=false for an unknown sub") } } func TestProvisionUser_SeedsProfileTaxonomyAndSyncState(t *testing.T) { db := openTestDB(t) ctx := context.Background() userID, err := db.ProvisionUser(ctx, "sub-123", "Lucie") if err != nil { t.Fatalf("ProvisionUser: %v", err) } u, found, err := db.GetUserBySub(ctx, "sub-123") if err != nil || !found { t.Fatalf("GetUserBySub: found=%v err=%v", found, err) } if u.ID != userID || u.DisplayName != "Lucie" { t.Fatalf("got %+v, want ID=%d DisplayName=Lucie", u, userID) } profile, err := db.GetProfile(ctx, userID) if err != nil { t.Fatalf("GetProfile: %v", err) } if profile.Name != "Lucie" { t.Errorf("profile.Name = %q, want %q", profile.Name, "Lucie") } if profile.RollingWindowDays != 90 { t.Errorf("profile.RollingWindowDays = %d, want 90 (default)", profile.RollingWindowDays) } kinds, err := db.ListWorkoutKinds(ctx, userID, false) if err != nil { t.Fatalf("ListWorkoutKinds: %v", err) } if len(kinds) != 8 { t.Fatalf("expected 8 seeded workout kinds, got %d", len(kinds)) } state, err := db.GetSyncState(ctx, userID) if err != nil { t.Fatalf("GetSyncState: %v", err) } if state.EarliestSyncedDate != nil || state.BackfillComplete { t.Errorf("expected fresh sync state, got %+v", state) } } func TestProvisionUser_TwoUsersGetIndependentTaxonomies(t *testing.T) { db := openTestDB(t) ctx := context.Background() userA, err := db.ProvisionUser(ctx, "sub-a", "A") if err != nil { t.Fatalf("ProvisionUser(a): %v", err) } userB, err := db.ProvisionUser(ctx, "sub-b", "B") if err != nil { t.Fatalf("ProvisionUser(b): %v", err) } kindsA, err := db.ListWorkoutKinds(ctx, userA, false) if err != nil { t.Fatalf("ListWorkoutKinds(a): %v", err) } kindsB, err := db.ListWorkoutKinds(ctx, userB, false) if err != nil { t.Fatalf("ListWorkoutKinds(b): %v", err) } if len(kindsA) != 8 || len(kindsB) != 8 { t.Fatalf("expected 8 kinds each, got a=%d b=%d", len(kindsA), len(kindsB)) } if kindsA[0].ID == kindsB[0].ID { 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, SyncKindFull); 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) } } }