// Package mock provides a fake garmin.Client for tests and frontend/dev // work without a live Garmin account or the wrapper subprocess. package mock import ( "context" "time" "geniusrun/backend/internal/garmin" ) // Client is a fake garmin.Client returning data supplied by the test/caller. type Client struct { AuthResults []garmin.AuthResult // consumed in order by Authenticate/CompleteMFA calls Activities []garmin.Activity Splits map[int64]garmin.ActivitySplits Details map[int64]garmin.ActivityDetails Workouts map[int64]garmin.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 _ garmin.Client = (*Client)(nil) func (c *Client) nextAuthResult() garmin.AuthResult { if c.authResultCursor >= len(c.AuthResults) { return garmin.AuthResult{Status: garmin.AuthSuccess, Message: "Authenticated successfully."} } r := c.AuthResults[c.authResultCursor] c.authResultCursor++ return r } func (c *Client) Authenticate(ctx context.Context) (garmin.AuthResult, error) { c.AuthenticateCalls++ if c.Err != nil { return garmin.AuthResult{}, c.Err } return c.nextAuthResult(), nil } func (c *Client) CompleteMFA(ctx context.Context, code string) (garmin.AuthResult, error) { if c.Err != nil { return garmin.AuthResult{}, c.Err } return c.nextAuthResult(), nil } func (c *Client) GetActivities(ctx context.Context, startDate, endDate string, limit int) ([]garmin.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 *Client) GetActivitySplits(ctx context.Context, activityID int64) (garmin.ActivitySplits, error) { if c.Err != nil { return garmin.ActivitySplits{}, c.Err } return c.Splits[activityID], nil } func (c *Client) GetActivityDetails(ctx context.Context, activityID int64) (garmin.ActivityDetails, error) { if c.Err != nil { return garmin.ActivityDetails{}, c.Err } return c.Details[activityID], nil } func (c *Client) GetWorkoutByID(ctx context.Context, workoutID int64) (garmin.Workout, error) { if c.Err != nil { return garmin.Workout{}, c.Err } if err, ok := c.WorkoutErrByID[workoutID]; ok { return garmin.Workout{}, err } return c.Workouts[workoutID], nil } func (c *Client) UpdateCredentials(email, password string) { c.LastEmail = email c.LastPassword = password } func (c *Client) Close() error { c.ClosedCalled = true return nil }