From ef410863c600ccb13c4e5adc156483f8ec2927bc Mon Sep 17 00:00:00 2001 From: Christophe Vila Date: Sat, 25 Jul 2026 13:17:17 +0200 Subject: [PATCH] store: scope GetProfile/UpdateProfile to a user_id Part of per-user profile isolation: profile rows are no longer a global singleton, so every read/write requires the caller's userID. This also requires scoping ListActivities, ListWorkoutKinds, UpsertActivity, CreateWorkoutKind, GetSyncState, UpdateSyncState, and ResetAllSyncedData to userID, plus updating all related tests in the store package. --- backend/internal/store/activities.go | 22 ++++++------- backend/internal/store/profile.go | 17 +++++----- backend/internal/store/profile_test.go | 10 ++++-- backend/internal/store/reset.go | 12 +++---- backend/internal/store/reset_test.go | 16 ++++++---- backend/internal/store/store_test.go | 31 ++++++++++++++----- backend/internal/store/syncstate.go | 19 ++++++------ backend/internal/store/syncstate_test.go | 10 ++++-- backend/internal/store/workoutkinds.go | 22 ++++++------- .../store/workoutkinds_taxonomy_test.go | 12 +++++-- 10 files changed, 104 insertions(+), 67 deletions(-) diff --git a/backend/internal/store/activities.go b/backend/internal/store/activities.go index 6c107c5..ca326e3 100644 --- a/backend/internal/store/activities.go +++ b/backend/internal/store/activities.go @@ -52,15 +52,15 @@ type Activity struct { // UpsertActivity inserts a new activity or updates the existing row for the // same garmin_activity_id (idempotent, safe to call on every sync pass), and // returns its internal id. -func (db *DB) UpsertActivity(ctx context.Context, a Activity) (int64, error) { +func (db *DB) UpsertActivity(ctx context.Context, userID int64, a Activity) (int64, error) { _, err := db.ExecContext(ctx, ` INSERT INTO activities ( - garmin_activity_id, event_type_key, workout_id, start_time_utc, + user_id, garmin_activity_id, event_type_key, workout_id, start_time_utc, duration_seconds, distance_meters, avg_hr, max_hr, avg_speed_mps, elevation_gain_m, aerobic_training_effect, anaerobic_training_effect, vo2max_value, raw_json, updated_at - ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?, datetime('now')) + ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, datetime('now')) ON CONFLICT(garmin_activity_id) DO UPDATE SET event_type_key=excluded.event_type_key, workout_id=excluded.workout_id, @@ -77,18 +77,18 @@ func (db *DB) UpsertActivity(ctx context.Context, a Activity) (int64, error) { raw_json=excluded.raw_json, updated_at=datetime('now') `, - a.GarminActivityID, a.EventTypeKey, a.WorkoutID, a.StartTimeUTC, + userID, a.GarminActivityID, a.EventTypeKey, a.WorkoutID, a.StartTimeUTC, a.DurationSeconds, a.DistanceMeters, a.AvgHR, a.MaxHR, a.AvgSpeedMps, a.ElevationGainM, a.AerobicTrainingEffect, a.AnaerobicTrainingEffect, a.VO2MaxValue, a.RawJSON, ) if err != nil { - return 0, fmt.Errorf("upsert activity %d: %w", a.GarminActivityID, err) + return 0, fmt.Errorf("upsert activity %d for user %d: %w", a.GarminActivityID, userID, err) } var id int64 - if err := db.QueryRowContext(ctx, `SELECT id FROM activities WHERE garmin_activity_id = ?`, a.GarminActivityID).Scan(&id); err != nil { + if err := db.QueryRowContext(ctx, `SELECT id FROM activities WHERE garmin_activity_id = ? AND user_id = ?`, a.GarminActivityID, userID).Scan(&id); err != nil { return 0, fmt.Errorf("fetch id for activity %d: %w", a.GarminActivityID, err) } return id, nil @@ -137,10 +137,10 @@ type ActivityFilter struct { Offset int } -// ListActivities returns activities newest-first, optionally filtered by date range. -func (db *DB) ListActivities(ctx context.Context, f ActivityFilter) ([]Activity, error) { - query := `SELECT ` + activityColumns + ` FROM activities WHERE 1=1` - var args []any +// ListActivities returns activities for userID newest-first, optionally filtered by date range. +func (db *DB) ListActivities(ctx context.Context, userID int64, f ActivityFilter) ([]Activity, error) { + query := `SELECT ` + activityColumns + ` FROM activities WHERE user_id = ?` + args := []any{userID} if f.FromDate != "" { query += ` AND start_time_utc >= ?` args = append(args, f.FromDate) @@ -157,7 +157,7 @@ func (db *DB) ListActivities(ctx context.Context, f ActivityFilter) ([]Activity, rows, err := db.QueryContext(ctx, query, args...) if err != nil { - return nil, fmt.Errorf("list activities: %w", err) + return nil, fmt.Errorf("list activities for user %d: %w", userID, err) } defer rows.Close() diff --git a/backend/internal/store/profile.go b/backend/internal/store/profile.go index c7dad75..b7e8bcb 100644 --- a/backend/internal/store/profile.go +++ b/backend/internal/store/profile.go @@ -81,10 +81,10 @@ const profileColumns = ` created_at, updated_at ` -// GetProfile returns the single profile row. -func (db *DB) GetProfile(ctx context.Context) (Profile, error) { +// GetProfile returns the profile row for userID. +func (db *DB) GetProfile(ctx context.Context, userID int64) (Profile, error) { var p Profile - err := db.QueryRowContext(ctx, `SELECT `+profileColumns+` FROM profile WHERE id = 1`).Scan( + err := db.QueryRowContext(ctx, `SELECT `+profileColumns+` FROM profile WHERE user_id = ?`, userID).Scan( &p.Name, &p.GarminEmail, &p.GarminPassword, &p.RollingWindowDays, &p.BackfillHorizonDays, &p.MaxHeartRate, &p.RestingHeartRate, &p.HRZone1MinPct, &p.HRZone1MaxPct, &p.HRZone2MinPct, &p.HRZone2MaxPct, &p.HRZone3MinPct, &p.HRZone3MaxPct, &p.HRZone4MinPct, &p.HRZone4MaxPct, @@ -96,15 +96,15 @@ func (db *DB) GetProfile(ctx context.Context) (Profile, error) { &p.CreatedAt, &p.UpdatedAt, ) if err != nil { - return Profile{}, fmt.Errorf("get profile: %w", err) + return Profile{}, fmt.Errorf("get profile for user %d: %w", userID, err) } return p, nil } -// UpdateProfile overwrites the single profile row. Callers should read via +// UpdateProfile overwrites userID's profile row. Callers should read via // GetProfile first and modify the fields they intend to change, since this // replaces every column. -func (db *DB) UpdateProfile(ctx context.Context, p Profile) error { +func (db *DB) UpdateProfile(ctx context.Context, userID int64, p Profile) error { _, err := db.ExecContext(ctx, ` UPDATE profile SET name=?, garmin_email=?, garmin_password=?, rolling_window_days=?, backfill_horizon_days=?, max_heart_rate=?, resting_heart_rate=?, @@ -116,7 +116,7 @@ func (db *DB) UpdateProfile(ctx context.Context, p Profile) error { pace_color=?, heart_rate_color=?, warmup_color=?, effort_color=?, recovery_color=?, cooldown_color=?, main_line_tint_pct=?, background_darken_pct=?, target_brighten_pct=?, updated_at=datetime('now') - WHERE id = 1`, + WHERE user_id = ?`, p.Name, p.GarminEmail, p.GarminPassword, p.RollingWindowDays, p.BackfillHorizonDays, p.MaxHeartRate, p.RestingHeartRate, p.HRZone1MinPct, p.HRZone1MaxPct, p.HRZone2MinPct, p.HRZone2MaxPct, p.HRZone3MinPct, p.HRZone3MaxPct, p.HRZone4MinPct, p.HRZone4MaxPct, @@ -125,9 +125,10 @@ func (db *DB) UpdateProfile(ctx context.Context, p Profile) error { p.MinRepresentativePaceSecPerKm, p.MinRepresentativeTimeSeconds, p.PaceColor, p.HeartRateColor, p.WarmupColor, p.EffortColor, p.RecoveryColor, p.CooldownColor, p.MainLineTintPct, p.BackgroundDarkenPct, p.TargetBrightenPct, + userID, ) if err != nil { - return fmt.Errorf("update profile: %w", err) + return fmt.Errorf("update profile for user %d: %w", userID, err) } return nil } diff --git a/backend/internal/store/profile_test.go b/backend/internal/store/profile_test.go index f779319..34eb22e 100644 --- a/backend/internal/store/profile_test.go +++ b/backend/internal/store/profile_test.go @@ -8,8 +8,12 @@ import ( func TestProfile_DefaultsThenUpdate(t *testing.T) { db := openTestDB(t) ctx := context.Background() + userID, err := db.ProvisionUser(ctx, "test-sub", "Default") + if err != nil { + t.Fatalf("ProvisionUser: %v", err) + } - p, err := db.GetProfile(ctx) + p, err := db.GetProfile(ctx, userID) if err != nil { t.Fatalf("GetProfile: %v", err) } @@ -64,11 +68,11 @@ func TestProfile_DefaultsThenUpdate(t *testing.T) { p.MinRepresentativePaceSecPerKm = 600 p.MinRepresentativeTimeSeconds = 5 - if err := db.UpdateProfile(ctx, p); err != nil { + if err := db.UpdateProfile(ctx, userID, p); err != nil { t.Fatalf("UpdateProfile: %v", err) } - got, err := db.GetProfile(ctx) + got, err := db.GetProfile(ctx, userID) if err != nil { t.Fatalf("GetProfile after update: %v", err) } diff --git a/backend/internal/store/reset.go b/backend/internal/store/reset.go index 521cde6..2e7347c 100644 --- a/backend/internal/store/reset.go +++ b/backend/internal/store/reset.go @@ -5,23 +5,23 @@ import ( "fmt" ) -// ResetAllSyncedData deletes every synced activity (cascading to its laps, +// ResetAllSyncedData deletes every synced activity for userID (cascading to its laps, // activity_samples, and kind_assignments via ON DELETE CASCADE) and rewinds // the backfill watermark to its initial state, so a subsequent Backfill // starts a genuinely fresh pull instead of thinking history is already // covered. Workout kinds (the user's taxonomy) are left untouched. -func (db *DB) ResetAllSyncedData(ctx context.Context) error { +func (db *DB) ResetAllSyncedData(ctx context.Context, userID int64) error { tx, err := db.BeginTx(ctx, nil) if err != nil { return fmt.Errorf("begin reset tx: %w", err) } defer tx.Rollback() - if _, err := tx.ExecContext(ctx, `DELETE FROM activities`); err != nil { - return fmt.Errorf("delete activities: %w", err) + if _, err := tx.ExecContext(ctx, `DELETE FROM activities WHERE user_id = ?`, userID); err != nil { + return fmt.Errorf("delete activities for user %d: %w", userID, err) } - if _, err := tx.ExecContext(ctx, `UPDATE sync_state SET earliest_synced_date = NULL, backfill_complete = 0 WHERE id = 1`); err != nil { - return fmt.Errorf("reset sync state: %w", err) + if _, err := tx.ExecContext(ctx, `UPDATE sync_state SET earliest_synced_date = NULL, backfill_complete = 0 WHERE user_id = ?`, userID); err != nil { + return fmt.Errorf("reset sync state for user %d: %w", userID, err) } return tx.Commit() } diff --git a/backend/internal/store/reset_test.go b/backend/internal/store/reset_test.go index f53488a..1257459 100644 --- a/backend/internal/store/reset_test.go +++ b/backend/internal/store/reset_test.go @@ -8,12 +8,16 @@ import ( func TestResetAllSyncedData_DeletesActivitiesCascadesAndRewindsWatermark(t *testing.T) { db := openTestDB(t) ctx := context.Background() + userID, err := db.ProvisionUser(ctx, "test-sub", "Test User") + if err != nil { + t.Fatalf("ProvisionUser: %v", err) + } - activityID, err := db.UpsertActivity(ctx, Activity{GarminActivityID: 1, StartTimeUTC: "2026-07-11 06:00:00", RawJSON: "{}"}) + 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, WorkoutKind{Name: "Test Reset Kind", RuleJSON: `{"match":"all","conditions":[]}`, IsActive: true}) + kindID, err := db.CreateWorkoutKind(ctx, userID, WorkoutKind{Name: "Test Reset Kind", RuleJSON: `{"match":"all","conditions":[]}`, IsActive: true}) if err != nil { t.Fatalf("CreateWorkoutKind: %v", err) } @@ -26,15 +30,15 @@ func TestResetAllSyncedData_DeletesActivitiesCascadesAndRewindsWatermark(t *test }); err != nil { t.Fatalf("InsertKindAssignment: %v", err) } - if err := db.UpdateSyncState(ctx, "2020-01-01", true); err != nil { + if err := db.UpdateSyncState(ctx, userID, "2020-01-01", true); err != nil { t.Fatalf("UpdateSyncState: %v", err) } - if err := db.ResetAllSyncedData(ctx); err != nil { + if err := db.ResetAllSyncedData(ctx, userID); err != nil { t.Fatalf("ResetAllSyncedData: %v", err) } - activities, err := db.ListActivities(ctx, ActivityFilter{}) + activities, err := db.ListActivities(ctx, userID, ActivityFilter{}) if err != nil { t.Fatalf("ListActivities: %v", err) } @@ -52,7 +56,7 @@ func TestResetAllSyncedData_DeletesActivitiesCascadesAndRewindsWatermark(t *test t.Errorf("expected kind assignment to cascade-delete, ok=%v err=%v", ok, err) } - state, err := db.GetSyncState(ctx) + state, err := db.GetSyncState(ctx, userID) if err != nil { t.Fatalf("GetSyncState: %v", err) } diff --git a/backend/internal/store/store_test.go b/backend/internal/store/store_test.go index 33e1fcd..dbf45e6 100644 --- a/backend/internal/store/store_test.go +++ b/backend/internal/store/store_test.go @@ -41,6 +41,10 @@ func TestMigrateIsIdempotent(t *testing.T) { func TestUpsertActivity_InsertThenUpdateIsIdempotent(t *testing.T) { db := openTestDB(t) ctx := context.Background() + userID, err := db.ProvisionUser(ctx, "test-sub", "Test User") + if err != nil { + t.Fatalf("ProvisionUser: %v", err) + } a := Activity{ GarminActivityID: 23554066504, @@ -51,13 +55,13 @@ func TestUpsertActivity_InsertThenUpdateIsIdempotent(t *testing.T) { RawJSON: `{"activityId":23554066504,"activityName":"Auriol - W2-5-Base Endurance","activityType":{"typeKey":"running"}}`, } - id, err := db.UpsertActivity(ctx, a) + id, err := db.UpsertActivity(ctx, userID, a) if err != nil { t.Fatalf("UpsertActivity (insert): %v", err) } a.AvgHR = f(150) // simulate a re-sync with a corrected value - id2, err := db.UpsertActivity(ctx, a) + id2, err := db.UpsertActivity(ctx, userID, a) if err != nil { t.Fatalf("UpsertActivity (update): %v", err) } @@ -73,7 +77,7 @@ func TestUpsertActivity_InsertThenUpdateIsIdempotent(t *testing.T) { t.Errorf("AvgHR = %v, want 150 (update should have applied)", *got.AvgHR) } - all, err := db.ListActivities(ctx, ActivityFilter{}) + all, err := db.ListActivities(ctx, userID, ActivityFilter{}) if err != nil { t.Fatalf("ListActivities: %v", err) } @@ -85,8 +89,12 @@ func TestUpsertActivity_InsertThenUpdateIsIdempotent(t *testing.T) { func TestKindAssignment_AppendOnlyHistoryAndCurrentView(t *testing.T) { db := openTestDB(t) ctx := context.Background() + userID, err := db.ProvisionUser(ctx, "test-sub", "Test User") + if err != nil { + t.Fatalf("ProvisionUser: %v", err) + } - activityID, err := db.UpsertActivity(ctx, Activity{ + activityID, err := db.UpsertActivity(ctx, userID, Activity{ GarminActivityID: 1, StartTimeUTC: "2026-07-11 05:00:00", RawJSON: "{}", @@ -95,7 +103,7 @@ func TestKindAssignment_AppendOnlyHistoryAndCurrentView(t *testing.T) { t.Fatalf("UpsertActivity: %v", err) } - kindID, err := db.CreateWorkoutKind(ctx, WorkoutKind{ + kindID, err := db.CreateWorkoutKind(ctx, userID, WorkoutKind{ Name: "Test Assignment Custom", RuleJSON: `{"match":"all","conditions":[]}`, IsActive: true, @@ -169,8 +177,12 @@ func TestKindAssignment_AppendOnlyHistoryAndCurrentView(t *testing.T) { func TestReplaceLapsIsIdempotent(t *testing.T) { db := openTestDB(t) ctx := context.Background() + userID, err := db.ProvisionUser(ctx, "test-sub", "Test User") + if err != nil { + t.Fatalf("ProvisionUser: %v", err) + } - activityID, err := db.UpsertActivity(ctx, Activity{ + activityID, err := db.UpsertActivity(ctx, userID, Activity{ GarminActivityID: 2, StartTimeUTC: "2026-07-11 05:00:00", RawJSON: "{}", @@ -245,8 +257,13 @@ func TestForeignKeyEnforcementPostMigration(t *testing.T) { // migrations 0023/0024/0025) is properly scoped and re-enabled after each // table rebuild. + userID, err := db.ProvisionUser(ctx, "test-sub", "Test User") + if err != nil { + t.Fatalf("ProvisionUser: %v", err) + } + // Insert an activity that we can reference. - activityID, err := db.UpsertActivity(ctx, Activity{ + activityID, err := db.UpsertActivity(ctx, userID, Activity{ GarminActivityID: 1001, StartTimeUTC: "2026-07-11 05:00:00", RawJSON: "{}", diff --git a/backend/internal/store/syncstate.go b/backend/internal/store/syncstate.go index c6aba91..7310211 100644 --- a/backend/internal/store/syncstate.go +++ b/backend/internal/store/syncstate.go @@ -13,25 +13,24 @@ type SyncState struct { BackfillComplete bool // true once backfill has reached the configured horizon (or Garmin's own history start) } -// GetSyncState returns the current backfill watermark (the singleton row, -// created by migration 0002). -func (db *DB) GetSyncState(ctx context.Context) (SyncState, error) { +// GetSyncState returns the current backfill watermark for userID. +func (db *DB) GetSyncState(ctx context.Context, userID int64) (SyncState, error) { var s SyncState - err := db.QueryRowContext(ctx, `SELECT earliest_synced_date, backfill_complete FROM sync_state WHERE id = 1`). + err := db.QueryRowContext(ctx, `SELECT earliest_synced_date, backfill_complete FROM sync_state WHERE user_id = ?`, userID). Scan(&s.EarliestSyncedDate, &s.BackfillComplete) if err != nil { - return SyncState{}, fmt.Errorf("get sync state: %w", err) + return SyncState{}, fmt.Errorf("get sync state for user %d: %w", userID, err) } return s, nil } -// UpdateSyncState records progress of a backfill run. -func (db *DB) UpdateSyncState(ctx context.Context, earliestSyncedDate string, complete bool) error { +// UpdateSyncState records progress of a backfill run for userID. +func (db *DB) UpdateSyncState(ctx context.Context, userID int64, earliestSyncedDate string, complete bool) error { _, err := db.ExecContext(ctx, ` - UPDATE sync_state SET earliest_synced_date = ?, backfill_complete = ? WHERE id = 1`, - earliestSyncedDate, complete) + UPDATE sync_state SET earliest_synced_date = ?, backfill_complete = ? WHERE user_id = ?`, + earliestSyncedDate, complete, userID) if err != nil { - return fmt.Errorf("update sync state: %w", err) + return fmt.Errorf("update sync state for user %d: %w", userID, err) } return nil } diff --git a/backend/internal/store/syncstate_test.go b/backend/internal/store/syncstate_test.go index d7b9108..cf20f87 100644 --- a/backend/internal/store/syncstate_test.go +++ b/backend/internal/store/syncstate_test.go @@ -8,8 +8,12 @@ import ( func TestSyncState_DefaultsAndUpdate(t *testing.T) { db := openTestDB(t) ctx := context.Background() + userID, err := db.ProvisionUser(ctx, "test-sub", "Test User") + if err != nil { + t.Fatalf("ProvisionUser: %v", err) + } - state, err := db.GetSyncState(ctx) + state, err := db.GetSyncState(ctx, userID) if err != nil { t.Fatalf("GetSyncState: %v", err) } @@ -17,11 +21,11 @@ func TestSyncState_DefaultsAndUpdate(t *testing.T) { t.Fatalf("expected fresh DB to have no watermark, got %+v", state) } - if err := db.UpdateSyncState(ctx, "2023-01-01", true); err != nil { + if err := db.UpdateSyncState(ctx, userID, "2023-01-01", true); err != nil { t.Fatalf("UpdateSyncState: %v", err) } - state, err = db.GetSyncState(ctx) + state, err = db.GetSyncState(ctx, userID) if err != nil { t.Fatalf("GetSyncState after update: %v", err) } diff --git a/backend/internal/store/workoutkinds.go b/backend/internal/store/workoutkinds.go index 5d062b7..4883da8 100644 --- a/backend/internal/store/workoutkinds.go +++ b/backend/internal/store/workoutkinds.go @@ -29,13 +29,13 @@ func scanWorkoutKind(row interface{ Scan(...any) error }) (WorkoutKind, error) { const workoutKindColumns = `id, name, description, color, rule_json, priority, is_active, created_at, updated_at` // CreateWorkoutKind inserts a new workout kind and returns its id. -func (db *DB) CreateWorkoutKind(ctx context.Context, k WorkoutKind) (int64, error) { +func (db *DB) CreateWorkoutKind(ctx context.Context, userID int64, k WorkoutKind) (int64, error) { res, err := db.ExecContext(ctx, ` - INSERT INTO workout_kinds (name, description, color, rule_json, priority, is_active) - VALUES (?,?,?,?,?,?)`, - k.Name, k.Description, k.Color, k.RuleJSON, k.Priority, k.IsActive) + INSERT INTO workout_kinds (user_id, name, description, color, rule_json, priority, is_active) + VALUES (?,?,?,?,?,?,?)`, + userID, k.Name, k.Description, k.Color, k.RuleJSON, k.Priority, k.IsActive) if err != nil { - return 0, fmt.Errorf("create workout kind %q: %w", k.Name, err) + return 0, fmt.Errorf("create workout kind %q for user %d: %w", k.Name, userID, err) } return res.LastInsertId() } @@ -78,18 +78,18 @@ func (db *DB) GetWorkoutKindByName(ctx context.Context, name string) (WorkoutKin return k, true, nil } -// ListWorkoutKinds returns workout kinds. If activeOnly, soft-deleted +// ListWorkoutKinds returns workout kinds for userID. If activeOnly, soft-deleted // (is_active=0) kinds are excluded. -func (db *DB) ListWorkoutKinds(ctx context.Context, activeOnly bool) ([]WorkoutKind, error) { - query := `SELECT ` + workoutKindColumns + ` FROM workout_kinds` +func (db *DB) ListWorkoutKinds(ctx context.Context, userID int64, activeOnly bool) ([]WorkoutKind, error) { + query := `SELECT ` + workoutKindColumns + ` FROM workout_kinds WHERE user_id = ?` if activeOnly { - query += ` WHERE is_active = 1` + query += ` AND is_active = 1` } query += ` ORDER BY priority DESC, name` - rows, err := db.QueryContext(ctx, query) + rows, err := db.QueryContext(ctx, query, userID) if err != nil { - return nil, fmt.Errorf("list workout kinds: %w", err) + return nil, fmt.Errorf("list workout kinds for user %d: %w", userID, err) } defer rows.Close() diff --git a/backend/internal/store/workoutkinds_taxonomy_test.go b/backend/internal/store/workoutkinds_taxonomy_test.go index bb50cb4..cc92ab5 100644 --- a/backend/internal/store/workoutkinds_taxonomy_test.go +++ b/backend/internal/store/workoutkinds_taxonomy_test.go @@ -8,8 +8,12 @@ import ( func TestWorkoutTaxonomy_SeededWithEightFixedTypes(t *testing.T) { db := openTestDB(t) ctx := context.Background() + userID, err := db.ProvisionUser(ctx, "test-sub", "Test User") + if err != nil { + t.Fatalf("ProvisionUser: %v", err) + } - kinds, err := db.ListWorkoutKinds(ctx, true) + kinds, err := db.ListWorkoutKinds(ctx, userID, true) if err != nil { t.Fatalf("ListWorkoutKinds: %v", err) } @@ -42,8 +46,12 @@ func TestWorkoutTaxonomy_SeededWithEightFixedTypes(t *testing.T) { func TestWorkoutTaxonomy_ListedInFixedDisplayOrder(t *testing.T) { db := openTestDB(t) ctx := context.Background() + userID, err := db.ProvisionUser(ctx, "test-sub", "Test User") + if err != nil { + t.Fatalf("ProvisionUser: %v", err) + } - kinds, err := db.ListWorkoutKinds(ctx, true) + kinds, err := db.ListWorkoutKinds(ctx, userID, true) if err != nil { t.Fatalf("ListWorkoutKinds: %v", err) }