# Спецификация 01: GarminClient — SSO и API Garmin Connect Источники: tech-req разд. 5 (FitFetcher — «по контракту семьи библиотек garminconnect»), Ф-8.1, vision (SSO-последовательность). Прототип: против живого Garmin не автоматизируется; тесты — фейковый `HttpMessageHandler` с теми же маршрутами. ## 1. Сигнатуры ```csharp public class GarminClient { public GarminClient(string login, string password, string baseUrl, HttpMessageHandler? handler = null); public void Login(CancellationToken ct = default); // SSO → Bearer public List ListActivities(int limit, CancellationToken ct = default); public byte[] DownloadFit(long activityId, CancellationToken ct = default); } public sealed record GarminActivity( long ActivityId, DateTime StartTimeLocal, string ActivityType); ``` ## 2. Маршруты (по контракту garminconnect; `{base}` = `Garmin.BaseUrl`) | Шаг | Запрос | Что берём | |---|---|---| | 1 | `GET {base}/sso/embed` | cookies (SSO) | | 2 | `GET {base}/sso/signin` | csrf из html (`name="_csrf"`) | | 3 | `POST {base}/sso/signin` (form: username, password, embed, csrf) | ticket из редиректа (`?ticket=…`); неуспех → `GarminAuthException` | | 4 | `GET {base}/modern/?ticket=…` | cookies современного домена | | 5 | `POST {base}/sso/api/2.0/oauth/exchange/service/user/2.0` (OAuth1-подпись тестовыми consumer key/secret) | Bearer `access_token` | | 6 | `GET {base}/activitylist-service/activities/search/activities?limit=N&start=0` | JSON список: `activityId`, `startTimeLocal`, `activityType.typeKey` | | 7 | `GET {base}/download-service/files/activity/{id}` | байты .fit | Все запросы после шага 5 — заголовок `Authorization: Bearer …`. Таймаут HttpClient — 30 с. 🛡 пароль и токены в логи не попадают (исключения — без credential-текста). ## 3. Тестовый handler `FakeGarminHandler : HttpMessageHandler` — маршруты разд. 2 на `{base}`; список и байты .fit задаёт тест (`FitParser.WriteTestFile`); режим `FailLogin` — шаг 3 возвращает 401. ## 4. Ошибки - `GarminAuthException` — шаги 1–5 (логин/MFA/блокировка); текст содержит шаг и код ответа, **не содержит** пароль; - `HttpRequestException`/`TaskCanceledException` — сеть/таймаут (сеть сбоит в тестах подменой handler).