fix(store): gate ActivitiesMissingWorkout on details already fetched
fillPendingDetails and fillPendingWorkouts are each independently LIMIT-bounded over different candidate sets, so on a large first backfill a structured-workout activity could fall inside the workouts pass's window while still outside the details pass's window. fillActivityWorkout would then align workout targets against zero laps (details/laps never written yet) and unconditionally mark workout_raw_json non-NULL, permanently losing that activity's target pace/HR bands once its real laps arrived later -- silently, with no error. Add details_fetched_at IS NOT NULL to ActivitiesMissingWorkout's and CountActivitiesMissingWorkout's WHERE clause: an activity is only eligible for a workout fetch once its laps actually exist to align against. This still covers the intended retry case (an activity whose workout fetch previously failed always has details_fetched_at already set) while excluding never-yet-processed activities. Rename/rewrite the store test to assert the corrected exclusion (count=1, not 2) as an explicit regression test, and fix the TestSyncStatus_ReportsPhaseAwareProgressAndWorkoutsPending API fixture, which exercised the same buggy shape. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -669,12 +669,20 @@ func TestSyncStatus_ReportsPhaseAwareProgressAndWorkoutsPending(t *testing.T) {
|
||||
ctx := newCtx()
|
||||
|
||||
workoutID := int64(555)
|
||||
if _, err := db.UpsertActivity(ctx, userID, store.Activity{
|
||||
activityID, err := db.UpsertActivity(ctx, userID, store.Activity{
|
||||
GarminActivityID: 1, WorkoutID: &workoutID,
|
||||
StartTimeUTC: "2026-07-11 06:00:00", RawJSON: "{}",
|
||||
}); err != nil {
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("UpsertActivity: %v", err)
|
||||
}
|
||||
// ActivitiesMissingWorkout only surfaces activities whose details have
|
||||
// already been fetched (see store.ActivitiesMissingWorkout) -- an
|
||||
// activity is only eligible for a workout fetch once fillActivityDetails
|
||||
// has given it laps to align target pace/HR bands against.
|
||||
if err := db.SetActivityDetails(ctx, userID, activityID, "{}"); err != nil {
|
||||
t.Fatalf("SetActivityDetails: %v", err)
|
||||
}
|
||||
|
||||
rec := doJSON(t, s.Router(), http.MethodGet, "/api/sync/status", nil)
|
||||
if rec.Code != http.StatusOK {
|
||||
|
||||
Reference in New Issue
Block a user