feat(garmin): implement data-fetch methods via generic call dispatch

GetActivities/GetActivitySplits/GetActivityDetails/GetWorkoutByID now send
{"cmd":"call","params":{"method":...,"args":...}} instead of named MCP
tools. subprocessClient fully implements Client.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 21:02:49 +02:00
parent 9cbcf62aed
commit 8c94c94c3f
2 changed files with 243 additions and 0 deletions

View File

@@ -276,3 +276,121 @@ func TestSubprocessClient_CompleteMFA_Failed(t *testing.T) {
t.Errorf("CompleteMFA status = %v, want AuthFailed", res.Status)
}
}
func TestSubprocessClient_GetActivities_UsesGarminconnectKwargNames(t *testing.T) {
var gotMethod string
var gotArgs map[string]any
c := newFakeWrapperClient(t, func(cmd string, params json.RawMessage) wireResponsePayload {
var p callParams
if err := json.Unmarshal(params, &p); err != nil {
t.Fatalf("unmarshal params: %v", err)
}
gotMethod, gotArgs = p.Method, p.Args
return fakeResult([]map[string]any{
{"activityId": float64(123), "activityType": map[string]any{"typeKey": "running"}},
})
})
activities, err := c.GetActivities(context.Background(), "2026-07-01", "2026-07-25", 0)
if err != nil {
t.Fatalf("GetActivities: %v", err)
}
if gotMethod != "get_activities_by_date" {
t.Errorf("method = %q, want get_activities_by_date", gotMethod)
}
if gotArgs["startdate"] != "2026-07-01" || gotArgs["enddate"] != "2026-07-25" {
t.Errorf("args = %+v, want startdate/enddate matching garminconnect's real kwargs", gotArgs)
}
if len(activities) != 1 || activities[0].ActivityID != 123 {
t.Fatalf("activities = %+v", activities)
}
}
func TestSubprocessClient_GetActivities_AppliesLimitClientSide(t *testing.T) {
c := newFakeWrapperClient(t, func(cmd string, params json.RawMessage) wireResponsePayload {
raw := make([]map[string]any, 5)
for i := range raw {
raw[i] = map[string]any{"activityId": float64(i)}
}
return fakeResult(raw)
})
activities, err := c.GetActivities(context.Background(), "2026-07-01", "2026-07-25", 2)
if err != nil {
t.Fatalf("GetActivities: %v", err)
}
if len(activities) != 2 {
t.Fatalf("len(activities) = %d, want 2", len(activities))
}
}
func TestSubprocessClient_GetActivitySplits_ParsesLapDTOsEnvelope(t *testing.T) {
var gotMethod string
var gotArgs map[string]any
c := newFakeWrapperClient(t, func(cmd string, params json.RawMessage) wireResponsePayload {
var p callParams
json.Unmarshal(params, &p)
gotMethod, gotArgs = p.Method, p.Args
return fakeResult(map[string]any{
"activityId": float64(111),
"lapDTOs": []map[string]any{{"lapIndex": float64(1), "distance": float64(1000)}},
})
})
splits, err := c.GetActivitySplits(context.Background(), 111)
if err != nil {
t.Fatalf("GetActivitySplits: %v", err)
}
if gotMethod != "get_activity_splits" || gotArgs["activity_id"] != "111" {
t.Errorf("method/args = %q/%+v, want get_activity_splits with activity_id=\"111\"", gotMethod, gotArgs)
}
if splits.ActivityID != 111 || len(splits.Laps) != 1 || splits.Laps[0].LapIndex != 1 {
t.Fatalf("splits = %+v", splits)
}
}
func TestSubprocessClient_GetActivityDetails_ParsesRawTelemetry(t *testing.T) {
c := newFakeWrapperClient(t, func(cmd string, params json.RawMessage) wireResponsePayload {
var p callParams
json.Unmarshal(params, &p)
if p.Method != "get_activity_details" || p.Args["activity_id"] != "222" {
t.Fatalf("unexpected call: %+v", p)
}
return fakeResult(map[string]any{
"activityId": float64(222),
"metricDescriptors": []map[string]any{{"key": "directHeartRate", "metricsIndex": float64(0)}},
"activityDetailMetrics": []map[string]any{{"metrics": []any{float64(101)}}},
})
})
details, err := c.GetActivityDetails(context.Background(), 222)
if err != nil {
t.Fatalf("GetActivityDetails: %v", err)
}
if details.ActivityID != 222 || len(details.MetricDescriptors) != 1 {
t.Fatalf("details = %+v", details)
}
}
func TestSubprocessClient_GetWorkoutByID_ParsesSegments(t *testing.T) {
c := newFakeWrapperClient(t, func(cmd string, params json.RawMessage) wireResponsePayload {
var p callParams
json.Unmarshal(params, &p)
if p.Method != "get_workout_by_id" || p.Args["workout_id"] != "333" {
t.Fatalf("unexpected call: %+v", p)
}
return fakeResult(map[string]any{
"workoutId": float64(333),
"workoutName": "Tempo",
"workoutSegments": []map[string]any{{"segmentOrder": float64(1), "workoutSteps": []any{}}},
})
})
workout, err := c.GetWorkoutByID(context.Background(), 333)
if err != nil {
t.Fatalf("GetWorkoutByID: %v", err)
}
if workout.WorkoutID != 333 || workout.WorkoutName != "Tempo" || len(workout.Segments) != 1 {
t.Fatalf("workout = %+v", workout)
}
}