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:
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user