package store import ( "context" "testing" ) func TestClaimLegacyOwner_BindsExistingSingletonRowsToOneNewUser(t *testing.T) { db := openTestDB(t) ctx := context.Background() // Simulate the pre-migration state: a fresh DB already has one // migration-seeded profile row (id=1, user_id=NULL) and 8 workout_kinds // rows (user_id=NULL) -- exactly what a real upgraded deployment looks // like right after Task 1's migrations run, before any user exists. if _, err := db.ExecContext(ctx, `UPDATE profile SET name = 'Kriss', garmin_email = 'kriss@example.com' WHERE user_id IS NULL`); err != nil { t.Fatalf("seed legacy profile: %v", err) } if _, err := db.ExecContext(ctx, `INSERT INTO activities (user_id, garmin_activity_id, start_time_utc, duration_seconds, distance_meters, raw_json) VALUES (NULL, 1, '2026-01-01 00:00:00', 1800, 5000, '{}')`); err != nil { t.Fatalf("seed legacy activity: %v", err) } if err := db.ClaimLegacyOwner(ctx, "kriss-sub"); err != nil { t.Fatalf("ClaimLegacyOwner: %v", err) } u, found, err := db.GetUserBySub(ctx, "kriss-sub") if err != nil || !found { t.Fatalf("GetUserBySub: found=%v err=%v", found, err) } if u.DisplayName != "Kriss" { t.Errorf("DisplayName = %q, want %q (from the legacy profile's name column)", u.DisplayName, "Kriss") } profile, err := db.GetProfile(ctx, u.ID) if err != nil { t.Fatalf("GetProfile(claimed user): %v", err) } if profile.GarminEmail != "kriss@example.com" { t.Errorf("claimed profile GarminEmail = %q, want kriss@example.com", profile.GarminEmail) } kinds, err := db.ListWorkoutKinds(ctx, u.ID, false) if err != nil { t.Fatalf("ListWorkoutKinds(claimed user): %v", err) } if len(kinds) != 8 { t.Fatalf("expected the 8 legacy workout_kinds rows to be claimed, got %d", len(kinds)) } activities, err := db.ListActivities(ctx, u.ID, ActivityFilter{}) if err != nil { t.Fatalf("ListActivities(claimed user): %v", err) } if len(activities) != 1 { t.Fatalf("expected the 1 legacy activity to be claimed, got %d", len(activities)) } var remainingNullUserIDRows int for _, table := range []string{"profile", "workout_kinds", "activities", "sync_state"} { var n int if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM `+table+` WHERE user_id IS NULL`).Scan(&n); err != nil { t.Fatalf("count NULL user_id in %s: %v", table, err) } remainingNullUserIDRows += n } if remainingNullUserIDRows != 0 { t.Errorf("expected every legacy row to be claimed, %d rows still have NULL user_id", remainingNullUserIDRows) } } func TestClaimLegacyOwner_NoOpOnceAUserAlreadyExists(t *testing.T) { db := openTestDB(t) ctx := context.Background() firstUserID, err := db.ProvisionUser(ctx, "already-here", "Someone") if err != nil { t.Fatalf("ProvisionUser: %v", err) } // A second call (simulating a later restart with the env var still set) // must not create a second user or touch anything. if err := db.ClaimLegacyOwner(ctx, "kriss-sub"); err != nil { t.Fatalf("ClaimLegacyOwner (should be a no-op): %v", err) } if _, found, err := db.GetUserBySub(ctx, "kriss-sub"); err != nil || found { t.Fatalf("expected no user created for kriss-sub, found=%v err=%v", found, err) } users, err := db.ListUsers(ctx) if err != nil { t.Fatalf("ListUsers: %v", err) } if len(users) != 1 || users[0].ID != firstUserID { t.Fatalf("expected exactly the 1 pre-existing user to remain, got %+v", users) } }