// MockClient support: a fake Client for tests and frontend/dev // work without a live Garmin account or the wrapper subprocess. package garmin import ( "context" "time" ) // MockClient is a fake Client returning data supplied by the test/caller. type MockClient struct { AuthResults []AuthResult // consumed in order by Authenticate/CompleteMFA calls Activities []Activity Splits map[int64]ActivitySplits Details map[int64]ActivityDetails Workouts map[int64]Workout // WorkoutErrByID, if set for a given workout ID, makes GetWorkoutByID // return that error for that ID specifically -- independent of the // all-calls-fail Err field below -- so a test can simulate one // activity's workout fetch failing while others in the same batch // succeed. WorkoutErrByID map[int64]error // Delay, if set, is slept (ctx-cancellable) at the start of every // GetActivities call -- lets a test give sync.Service's discovering // phase (backfillCore/incrementalSyncCore) real wall-clock duration, so // a concurrent goroutine can observe Progress() mid-flight instead of // the call returning instantly. Delay time.Duration Err error // if set, every call returns this error authResultCursor int ClosedCalled bool GetActivitiesCalls int AuthenticateCalls int LastEmail string LastPassword string } var _ Client = (*MockClient)(nil) func (c *MockClient) nextAuthResult() AuthResult { if c.authResultCursor >= len(c.AuthResults) { return AuthResult{Status: AuthSuccess, Message: "Authenticated successfully."} } r := c.AuthResults[c.authResultCursor] c.authResultCursor++ return r } func (c *MockClient) Authenticate(ctx context.Context) (AuthResult, error) { c.AuthenticateCalls++ if c.Err != nil { return AuthResult{}, c.Err } return c.nextAuthResult(), nil } func (c *MockClient) CompleteMFA(ctx context.Context, code string) (AuthResult, error) { if c.Err != nil { return AuthResult{}, c.Err } return c.nextAuthResult(), nil } func (c *MockClient) GetActivities(ctx context.Context, startDate, endDate string, limit int) ([]Activity, error) { c.GetActivitiesCalls++ if c.Delay > 0 { select { case <-ctx.Done(): return nil, ctx.Err() case <-time.After(c.Delay): } } if c.Err != nil { return nil, c.Err } if limit > 0 && limit < len(c.Activities) { return c.Activities[:limit], nil } return c.Activities, nil } func (c *MockClient) GetActivitySplits(ctx context.Context, activityID int64) (ActivitySplits, error) { if c.Err != nil { return ActivitySplits{}, c.Err } return c.Splits[activityID], nil } func (c *MockClient) GetActivityDetails(ctx context.Context, activityID int64) (ActivityDetails, error) { if c.Err != nil { return ActivityDetails{}, c.Err } return c.Details[activityID], nil } func (c *MockClient) GetWorkoutByID(ctx context.Context, workoutID int64) (Workout, error) { if c.Err != nil { return Workout{}, c.Err } if err, ok := c.WorkoutErrByID[workoutID]; ok { return Workout{}, err } return c.Workouts[workoutID], nil } func (c *MockClient) UpdateCredentials(email, password string) { c.LastEmail = email c.LastPassword = password } func (c *MockClient) Close() error { c.ClosedCalled = true return nil }