INITIAL COMMIT
This commit is contained in:
275
docs/testing.md
Normal file
275
docs/testing.md
Normal file
@ -0,0 +1,275 @@
|
||||
# Phase 2 Probe Testing
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```sh
|
||||
uv sync
|
||||
```
|
||||
|
||||
Optional environment setup:
|
||||
|
||||
```sh
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Fill `GARMIN_EMAIL` and `GARMIN_PASSWORD` in `.env`, or leave them blank and the scripts will prompt. Tokens are stored in `.garmin-tokens` by default.
|
||||
|
||||
Garmin Coach cycling detail currently comes from this Connect API endpoint:
|
||||
|
||||
```text
|
||||
/workout-service/fbt-adaptive/<workoutUuid>
|
||||
```
|
||||
|
||||
The browser network analyzer shows the same route under `/gc-api/...`, but the CLI should use the Connect API route with normal `python-garminconnect` tokens. If you are debugging the browser-only `/gc-api` route specifically, you can copy the `Cookie` request header into `.env`:
|
||||
|
||||
```text
|
||||
GARMIN_GC_API_COOKIE=JWT_WEB=...; ...
|
||||
```
|
||||
|
||||
Treat that cookie like a password. Do not commit `.env`, screenshots containing the cookie, or debug logs with request headers.
|
||||
|
||||
## Local Checkout Validation
|
||||
|
||||
```sh
|
||||
uv run python scripts/local_check.py
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS: ruff check .`
|
||||
- `PASS: ty check`
|
||||
- `PASS: pytest`
|
||||
- `PASS: ... scripts/probe_garmin.py dummy --date tomorrow --dry-run`
|
||||
- `debug/local_checks.json` is written.
|
||||
|
||||
This command does not log in to Garmin or upload anything.
|
||||
|
||||
## Login
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py login
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- If cached tokens work, it prints account/device summary.
|
||||
- If credentials are needed, it prompts for email/password.
|
||||
- If MFA is required, it prompts for the MFA code.
|
||||
- It must not print the Garmin password or MFA code.
|
||||
|
||||
## Discovery
|
||||
|
||||
Preferred one-pass read-only report:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py report --dump-json
|
||||
```
|
||||
|
||||
This writes `debug/probe_report.json`. It logs in once and collects account summary, training plans, workout type metadata, normal workouts, today/tomorrow calendar entries, and today/tomorrow Coach/adaptive match summaries. It continues through partial API failures and records errors in the report.
|
||||
|
||||
Individual discovery commands:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py plans
|
||||
uv run python scripts/probe_garmin.py types
|
||||
uv run python scripts/probe_garmin.py calendar --date today --dump-json
|
||||
uv run python scripts/probe_garmin.py calendar --date tomorrow --dump-json
|
||||
uv run python scripts/probe_garmin.py workouts --limit 25 --dump-json
|
||||
uv run python scripts/probe_garmin.py generated --prefix GCClone
|
||||
uv run python scripts/probe_garmin.py generated-calendar --date today --prefix GCClone
|
||||
```
|
||||
|
||||
Check:
|
||||
|
||||
- Training plans are listed.
|
||||
- Any plan marked `FBT_ADAPTIVE`, Coach, or adaptive is shown as a candidate.
|
||||
- Workout type metadata is written to `debug/workout_types.json`.
|
||||
- Calendar JSON dumps are written under `debug/`.
|
||||
- Normal workouts are visible.
|
||||
- Generated probe/clone workout templates are listed separately by prefix, if any exist.
|
||||
- Generated scheduled calendar entries are listed separately by prefix for the inspected month, if any exist.
|
||||
|
||||
If you have a normal cycling workout that already syncs to the Edge 1030, inspect it:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py workout <workout-id> --dump-json
|
||||
```
|
||||
|
||||
This writes `debug/workout_<workout-id>.json`, which is useful as a known-good schema reference.
|
||||
|
||||
`debug/` is gitignored because dumps can contain private Garmin account data even after redaction.
|
||||
|
||||
## Coach Workout Inspection
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py coach --date today --dump-json
|
||||
uv run python scripts/probe_garmin.py coach --date tomorrow --dump-json
|
||||
```
|
||||
|
||||
Useful output:
|
||||
|
||||
- `Workout-like objects with workoutSegments/workoutSteps: N`
|
||||
- A readable step summary with warmup/interval/recovery/cooldown/repeat steps.
|
||||
- `debug/coach_workout_YYYY-MM-DD.json` when a matched workout is found.
|
||||
- `debug/coach_workout_today.json` when today's matched workout is found.
|
||||
- The probe should fetch the full `fbt-adaptive` detail from `workoutUuid`.
|
||||
|
||||
Blocking output:
|
||||
|
||||
- `No parseable workoutSegments/workoutSteps found for this date.`
|
||||
|
||||
If blocked, report the command output and keep the redacted JSON in `debug/` for inspection.
|
||||
|
||||
If `coach` finds dated Coach task summaries but no step arrays, run the read-only endpoint sweep:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py coach-endpoints --date today --dump-json
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- It prints the plan ID, workout name, `workoutUuid`, and calendar item ID.
|
||||
- It tries candidate private endpoints and continues through 404/errors.
|
||||
- It writes `debug/coach_endpoint_probe_YYYY-MM-DD.json`.
|
||||
|
||||
Useful output:
|
||||
|
||||
- `At least one candidate endpoint returned workout-like step data.`
|
||||
|
||||
Blocking output:
|
||||
|
||||
- `No candidate endpoint returned workoutSegments/workoutSteps.`
|
||||
|
||||
For the June 16, 2026 account probe, `/workout-service/fbt-adaptive/<workoutUuid>` returned step arrays. `/workout-service/workout/uuid/<workoutUuid>` returned an empty list, and other candidate detail endpoints returned errors.
|
||||
|
||||
## Offline Dump Analysis
|
||||
|
||||
Analyze a saved dump without logging in to Garmin:
|
||||
|
||||
```sh
|
||||
uv run python scripts/analyze_dump.py debug/coach_workout_today.json --date today --clone-dry-run --dump-json
|
||||
```
|
||||
|
||||
Expected, if the dump contains a dated workout:
|
||||
|
||||
- Date-containing object count is shown.
|
||||
- Workout-like object count is shown.
|
||||
- A matched workout summary is printed.
|
||||
- `Local clone payload passed validation.` is printed if conversion is structurally safe.
|
||||
|
||||
If the dump does not contain a dated workout-like object, the command exits non-zero and prints:
|
||||
|
||||
```text
|
||||
No dated workout-like object was found.
|
||||
```
|
||||
|
||||
## Dummy Upload And Schedule Test
|
||||
|
||||
Dry run:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py dummy --date tomorrow --dry-run
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `Dummy workout payload passed local validation.`
|
||||
- The generated JSON is printed.
|
||||
- Nothing is uploaded.
|
||||
|
||||
Upload and schedule:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py dummy --date tomorrow --schedule
|
||||
```
|
||||
|
||||
After upload/schedule succeeds:
|
||||
|
||||
1. Sync the Edge 1030 with Garmin Connect / Garmin Express.
|
||||
2. Check `Training > Workouts`.
|
||||
3. Check `Training > Training Plan > calendar icon > tomorrow's date`.
|
||||
4. Start normal route/course navigation, then start the dummy workout.
|
||||
5. Confirm whether workout prompts and navigation can run together.
|
||||
|
||||
Report:
|
||||
|
||||
- Did the dummy workout appear in Garmin Connect?
|
||||
- Did it appear on the Edge 1030?
|
||||
- Did it appear in the calendar/date view?
|
||||
- Could it start while navigation was active?
|
||||
|
||||
## Clone Test
|
||||
|
||||
Dry run:
|
||||
|
||||
```sh
|
||||
uv run python scripts/clone_today_workout.py --date today --dry-run --dump-json
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `Converted clone payload passed local validation.`
|
||||
- The converted JSON is printed.
|
||||
- Nothing is uploaded.
|
||||
|
||||
Upload and schedule only if the dry run shows plausible steps:
|
||||
|
||||
```sh
|
||||
uv run python scripts/clone_today_workout.py --date today --schedule
|
||||
```
|
||||
|
||||
The clone is named:
|
||||
|
||||
```text
|
||||
GCClone YYYY-MM-DD <original workout name>
|
||||
```
|
||||
|
||||
Repeated runs skip an existing clone for the same date unless `--force` is passed.
|
||||
|
||||
Report:
|
||||
|
||||
- Did the cloned workout appear in Garmin Connect?
|
||||
- Did it appear on the Edge 1030 under workouts/calendar?
|
||||
- Can it be started on the Edge?
|
||||
- Can it run together with normal Edge navigation?
|
||||
- After completion, did Garmin Coach mark the original Coach workout complete?
|
||||
|
||||
## Generated Workout Cleanup
|
||||
|
||||
List generated workout templates:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py generated --prefix GCClone
|
||||
```
|
||||
|
||||
List generated scheduled calendar entries for the month containing `--date`:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py generated-calendar --date today --prefix GCClone
|
||||
```
|
||||
|
||||
Unschedule a generated calendar entry only if you are sure the scheduled ID is from the generated-prefix calendar list:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py generated-calendar --date today --prefix GCClone --unschedule-id <scheduled-id> --confirm
|
||||
```
|
||||
|
||||
Delete a generated workout template only if you are sure the ID is from the generated-prefix list:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py generated --prefix GCClone --delete-id <workout-id> --confirm
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- Without `--delete-id` or `--unschedule-id`, these commands are read-only.
|
||||
- With `--delete-id` or `--unschedule-id`, the command refuses to run unless `--confirm` is also passed.
|
||||
- The commands refuse IDs that are not in the generated-prefix list.
|
||||
|
||||
Unknown:
|
||||
|
||||
- Whether deleting the workout template also removes any scheduled calendar entry. Prefer unscheduling first, then check Garmin Connect calendar afterward and report what happened.
|
||||
|
||||
## Current Acceptance Gate
|
||||
|
||||
Phase 3 should not start until the dummy scheduled workout works on the Edge 1030 and the Coach inspection either exposes parseable steps or we explicitly choose a fallback architecture.
|
||||
Reference in New Issue
Block a user