test(store): adversarially cover ActivitiesMissingWorkout isolation

Follow this file's existing pattern (two real provisioned users,
identical-shaped rows, assert on empty-result/unaffected-by rather
than just two independently-created rows not colliding) for the two
methods touched by the details_fetched_at gating fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 09:01:10 +02:00
parent 1046e9f7a0
commit 0a7aa3a5e0

View File

@@ -205,6 +205,83 @@ func TestIsolation_DeleteUserLeavesOtherUsersDataIntact(t *testing.T) {
} }
} }
// TestIsolation_ActivitiesMissingWorkoutNeverLeaksAcrossUsers confirms
// ActivitiesMissingWorkout/CountActivitiesMissingWorkout only ever surface
// userID's own pending-workout activities, never another user's, even
// though both users have activities in the exact shape (workout_id set,
// details fetched, workout_raw_json still NULL) the query selects for.
func TestIsolation_ActivitiesMissingWorkoutNeverLeaksAcrossUsers(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)
}
workoutID := int64(999)
idA, err := db.UpsertActivity(ctx, userA, Activity{
GarminActivityID: 1, WorkoutID: &workoutID,
StartTimeUTC: "2026-07-01 06:00:00", RawJSON: "{}",
})
if err != nil {
t.Fatalf("UpsertActivity(a): %v", err)
}
if err := db.SetActivityDetails(ctx, userA, idA, "{}"); err != nil {
t.Fatalf("SetActivityDetails(a): %v", err)
}
idB, err := db.UpsertActivity(ctx, userB, Activity{
GarminActivityID: 1, WorkoutID: &workoutID,
StartTimeUTC: "2026-07-01 06:00:00", RawJSON: "{}",
})
if err != nil {
t.Fatalf("UpsertActivity(b): %v", err)
}
if err := db.SetActivityDetails(ctx, userB, idB, "{}"); err != nil {
t.Fatalf("SetActivityDetails(b): %v", err)
}
pendingA, err := db.ActivitiesMissingWorkout(ctx, userA, 10)
if err != nil {
t.Fatalf("ActivitiesMissingWorkout(a): %v", err)
}
if len(pendingA) != 1 || pendingA[0].ID != idA {
t.Fatalf("ActivitiesMissingWorkout(a) = %+v, want only userA's own activity (id %d)", pendingA, idA)
}
countA, err := db.CountActivitiesMissingWorkout(ctx, userA)
if err != nil {
t.Fatalf("CountActivitiesMissingWorkout(a): %v", err)
}
if countA != 1 {
t.Fatalf("CountActivitiesMissingWorkout(a) = %d, want 1 (userB's identical-looking activity must not be counted)", countA)
}
// Resolving userA's workout must not affect userB's pending activity.
if err := db.SetActivityWorkout(ctx, userA, idA, `{"segments":[]}`); err != nil {
t.Fatalf("SetActivityWorkout(a): %v", err)
}
pendingB, err := db.ActivitiesMissingWorkout(ctx, userB, 10)
if err != nil {
t.Fatalf("ActivitiesMissingWorkout(b): %v", err)
}
if len(pendingB) != 1 || pendingB[0].ID != idB {
t.Fatalf("ActivitiesMissingWorkout(b) = %+v, want userB's activity unaffected by userA's SetActivityWorkout", pendingB)
}
countB, err := db.CountActivitiesMissingWorkout(ctx, userB)
if err != nil {
t.Fatalf("CountActivitiesMissingWorkout(b): %v", err)
}
if countB != 1 {
t.Fatalf("CountActivitiesMissingWorkout(b) = %d, want 1 (unaffected by userA's SetActivityWorkout call)", countB)
}
}
// TestIsolation_MarkGarminConnectedNeverLeaksAcrossUsers confirms marking // TestIsolation_MarkGarminConnectedNeverLeaksAcrossUsers confirms marking
// one user's Garmin connection never sets another user's flag. // one user's Garmin connection never sets another user's flag.
func TestIsolation_MarkGarminConnectedNeverLeaksAcrossUsers(t *testing.T) { func TestIsolation_MarkGarminConnectedNeverLeaksAcrossUsers(t *testing.T) {