Files
geniusrun/backend/internal/store/users_test.go
Christophe Vila 042579a396 refactor(store): single account name on users; rename profile/laps tables
users.display_name becomes users.name and is now the only human-facing
name -- profiles.name is dropped (it duplicated the account name; the
Profile page's Name field now edits users.name through PUT /api/profile,
which carries Name alongside the profiles columns). The profile table
becomes profiles and laps becomes activity_laps, homogeneous with
activity_samples. Onboarding pre-fills the display name from the OIDC
claim's name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 16:11:23 +02:00

162 lines
5.0 KiB
Go

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.Name != "Lucie" {
t.Fatalf("got %+v, want ID=%d Name=Lucie", u, userID)
}
profile, err := db.GetProfile(ctx, userID)
if err != nil {
t.Fatalf("GetProfile: %v", err)
}
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 profiles 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 activity_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)
}
}
}