diff --git a/tests/integration/routes/parity/__snapshots__/workouts-batch.01-first-batch.json.snap b/tests/integration/routes/parity/__snapshots__/workouts-batch.01-first-batch.json.snap new file mode 100644 index 000000000..755012285 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/workouts-batch.01-first-batch.json.snap @@ -0,0 +1,284 @@ +{ + "status": 200, + "body": { + "data": { + "processed": 8, + "inserted": 5, + "duplicates": 1, + "skipped": [ + { + "index": 5, + "reason": "unstable_external_id" + }, + { + "index": 6, + "reason": "invalid_samples" + } + ], + "entries": [ + { + "index": 0, + "status": "inserted" + }, + { + "index": 1, + "status": "inserted" + }, + { + "index": 2, + "status": "inserted" + }, + { + "index": 3, + "status": "inserted" + }, + { + "index": 4, + "status": "duplicate" + }, + { + "index": 5, + "status": "skipped", + "reason": "unstable_external_id" + }, + { + "index": 6, + "status": "skipped", + "reason": "invalid_samples" + }, + { + "index": 7, + "status": "inserted" + } + ] + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "workout.batch.ingest" + }, + "meta": { + "write_bucket": "own", + "external_id_rejected": 1, + "external_id_shapes": "object_description", + "external_id_surface": "workout.batch", + "pr_detection_enqueued": true, + "pr_detection_silent": false, + "processed": 8, + "inserted": 5, + "duplicates": 1, + "enriched": 0, + "updated": 0, + "skipped": 2, + "routes_dropped": 1 + }, + "warnings": null, + "error": null + } + ], + "effects": [ + { + "effect": "enqueuePrDetection", + "args": [ + "", + { + "silent": false + } + ] + }, + { + "effect": "emitDataArrival", + "args": [ + { + "userId": "", + "kind": "workout", + "newestSampleAt": "2026-10-14T06:30:00.000Z", + "insertedCount": 1, + "refId": "", + "source": "batch" + } + ] + }, + { + "effect": "emitDataArrival", + "args": [ + { + "userId": "", + "kind": "workout", + "newestSampleAt": "2026-10-13T16:00:00.000Z", + "insertedCount": 1, + "refId": "", + "source": "batch" + } + ] + }, + { + "effect": "emitDataArrival", + "args": [ + { + "userId": "", + "kind": "workout", + "newestSampleAt": "2026-10-12T18:00:00.000Z", + "insertedCount": 1, + "refId": "", + "source": "batch" + } + ] + }, + { + "effect": "emitDataArrival", + "args": [ + { + "userId": "", + "kind": "workout", + "newestSampleAt": "2026-10-11T07:00:00.000Z", + "insertedCount": 1, + "refId": "", + "source": "batch" + } + ] + }, + { + "effect": "emitDataArrival", + "args": [ + { + "userId": "", + "kind": "workout", + "newestSampleAt": "2026-10-08T07:00:00.000Z", + "insertedCount": 1, + "refId": "", + "source": "batch" + } + ] + } + ], + "stored": [ + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-08T07:00:00.000Z", + "endedAt": "2026-10-08T07:50:00.000Z", + "durationSec": 3000, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-2", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "swimming", + "startedAt": "2026-10-11T07:00:00.000Z", + "endedAt": "2026-10-11T07:40:00.000Z", + "durationSec": 2400, + "totalEnergyKcal": null, + "totalDistanceM": 1500, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-swim-1", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "walking", + "startedAt": "2026-10-12T18:00:00.000Z", + "endedAt": "2026-10-12T18:30:00.000Z", + "durationSec": 1800, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": null, + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "cycling", + "startedAt": "2026-10-13T16:00:00.000Z", + "endedAt": "2026-10-13T17:00:00.000Z", + "durationSec": 3600, + "totalEnergyKcal": null, + "totalDistanceM": 24000, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": "manual:ride-1", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-14T06:30:00.000Z", + "endedAt": "2026-10-14T07:15:00.000Z", + "durationSec": 2700, + "totalEnergyKcal": 610, + "totalDistanceM": 8200, + "avgHeartRate": 148, + "maxHeartRate": 171, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-1", + "externalSourceVersion": null, + "metadata": { + "HKIndoorWorkout": false + }, + "samples": { + "samples": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 121 + }, + { + "t": "2026-10-14T06:30:05.000Z", + "hr": 128, + "cadence": 168 + }, + { + "t": "2026-10-14T06:30:10.000Z", + "hr": 133 + } + ], + "sampleCount": 3 + }, + "route": null + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/workouts-batch.02-same-batch-enrich.json.snap b/tests/integration/routes/parity/__snapshots__/workouts-batch.02-same-batch-enrich.json.snap new file mode 100644 index 000000000..613422c54 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/workouts-batch.02-same-batch-enrich.json.snap @@ -0,0 +1,270 @@ +{ + "status": 200, + "body": { + "data": { + "processed": 8, + "inserted": 1, + "duplicates": 5, + "skipped": [ + { + "index": 5, + "reason": "unstable_external_id" + }, + { + "index": 6, + "reason": "invalid_samples" + } + ], + "entries": [ + { + "index": 0, + "status": "duplicate" + }, + { + "index": 1, + "status": "duplicate" + }, + { + "index": 2, + "status": "inserted" + }, + { + "index": 3, + "status": "duplicate" + }, + { + "index": 4, + "status": "duplicate" + }, + { + "index": 5, + "status": "skipped", + "reason": "unstable_external_id" + }, + { + "index": 6, + "status": "skipped", + "reason": "invalid_samples" + }, + { + "index": 7, + "status": "enriched" + } + ] + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "workout.batch.ingest" + }, + "meta": { + "write_bucket": "own", + "external_id_rejected": 1, + "external_id_shapes": "object_description", + "external_id_surface": "workout.batch", + "pr_detection_enqueued": true, + "pr_detection_silent": false, + "processed": 8, + "inserted": 1, + "duplicates": 5, + "enriched": 1, + "updated": 0, + "skipped": 2, + "routes_dropped": 1 + }, + "warnings": null, + "error": null + } + ], + "effects": [ + { + "effect": "enqueuePrDetection", + "args": [ + "", + { + "silent": false + } + ] + }, + { + "effect": "emitDataArrival", + "args": [ + { + "userId": "", + "kind": "workout", + "newestSampleAt": "2026-10-12T18:00:00.000Z", + "insertedCount": 1, + "refId": "", + "source": "batch" + } + ] + } + ], + "stored": [ + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-08T07:00:00.000Z", + "endedAt": "2026-10-08T07:50:00.000Z", + "durationSec": 3000, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-2", + "externalSourceVersion": null, + "metadata": null, + "samples": { + "samples": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 121 + }, + { + "t": "2026-10-14T06:30:05.000Z", + "hr": 128, + "cadence": 168 + }, + { + "t": "2026-10-14T06:30:10.000Z", + "hr": 133 + } + ], + "sampleCount": 3 + }, + "route": null + }, + { + "id": "", + "sportType": "swimming", + "startedAt": "2026-10-11T07:00:00.000Z", + "endedAt": "2026-10-11T07:40:00.000Z", + "durationSec": 2400, + "totalEnergyKcal": null, + "totalDistanceM": 1500, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-swim-1", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "walking", + "startedAt": "2026-10-12T18:00:00.000Z", + "endedAt": "2026-10-12T18:30:00.000Z", + "durationSec": 1800, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": null, + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "walking", + "startedAt": "2026-10-12T18:00:00.000Z", + "endedAt": "2026-10-12T18:30:00.000Z", + "durationSec": 1800, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": null, + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "cycling", + "startedAt": "2026-10-13T16:00:00.000Z", + "endedAt": "2026-10-13T17:00:00.000Z", + "durationSec": 3600, + "totalEnergyKcal": null, + "totalDistanceM": 24000, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": "manual:ride-1", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-14T06:30:00.000Z", + "endedAt": "2026-10-14T07:15:00.000Z", + "durationSec": 2700, + "totalEnergyKcal": 610, + "totalDistanceM": 8200, + "avgHeartRate": 148, + "maxHeartRate": 171, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-1", + "externalSourceVersion": null, + "metadata": { + "HKIndoorWorkout": false + }, + "samples": { + "samples": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 121 + }, + { + "t": "2026-10-14T06:30:05.000Z", + "hr": 128, + "cadence": 168 + }, + { + "t": "2026-10-14T06:30:10.000Z", + "hr": 133 + } + ], + "sampleCount": 3 + }, + "route": null + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/workouts-batch.03-manual-correction.json.snap b/tests/integration/routes/parity/__snapshots__/workouts-batch.03-manual-correction.json.snap new file mode 100644 index 000000000..132588043 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/workouts-batch.03-manual-correction.json.snap @@ -0,0 +1,232 @@ +{ + "status": 200, + "body": { + "data": { + "processed": 2, + "inserted": 0, + "duplicates": 2, + "skipped": [], + "entries": [ + { + "index": 0, + "status": "updated" + }, + { + "index": 1, + "status": "duplicate" + } + ] + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "workout.batch.ingest" + }, + "meta": { + "write_bucket": "own", + "pr_detection_enqueued": true, + "pr_detection_silent": false, + "processed": 2, + "inserted": 0, + "duplicates": 2, + "enriched": 0, + "updated": 1, + "skipped": 0, + "routes_dropped": 0 + }, + "warnings": null, + "error": null + } + ], + "effects": [ + { + "effect": "enqueuePrDetection", + "args": [ + "", + { + "silent": false + } + ] + }, + { + "effect": "emitDataArrival", + "args": [ + { + "userId": "", + "kind": "workout", + "newestSampleAt": "2026-10-13T16:00:00.000Z", + "insertedCount": 1, + "refId": "", + "source": "batch" + } + ] + } + ], + "stored": [ + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-08T07:00:00.000Z", + "endedAt": "2026-10-08T07:50:00.000Z", + "durationSec": 3000, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-2", + "externalSourceVersion": null, + "metadata": null, + "samples": { + "samples": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 121 + }, + { + "t": "2026-10-14T06:30:05.000Z", + "hr": 128, + "cadence": 168 + }, + { + "t": "2026-10-14T06:30:10.000Z", + "hr": 133 + } + ], + "sampleCount": 3 + }, + "route": null + }, + { + "id": "", + "sportType": "swimming", + "startedAt": "2026-10-11T07:00:00.000Z", + "endedAt": "2026-10-11T07:40:00.000Z", + "durationSec": 2400, + "totalEnergyKcal": null, + "totalDistanceM": 1500, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-swim-1", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "walking", + "startedAt": "2026-10-12T18:00:00.000Z", + "endedAt": "2026-10-12T18:30:00.000Z", + "durationSec": 1800, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": null, + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "walking", + "startedAt": "2026-10-12T18:00:00.000Z", + "endedAt": "2026-10-12T18:30:00.000Z", + "durationSec": 1800, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": null, + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "cycling", + "startedAt": "2026-10-13T16:00:00.000Z", + "endedAt": "2026-10-13T17:05:00.000Z", + "durationSec": 3900, + "totalEnergyKcal": null, + "totalDistanceM": 2400, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": "manual:ride-1", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-14T06:30:00.000Z", + "endedAt": "2026-10-14T07:15:00.000Z", + "durationSec": 2700, + "totalEnergyKcal": 610, + "totalDistanceM": 8200, + "avgHeartRate": 148, + "maxHeartRate": 171, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-1", + "externalSourceVersion": null, + "metadata": { + "HKIndoorWorkout": false + }, + "samples": { + "samples": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 121 + }, + { + "t": "2026-10-14T06:30:05.000Z", + "hr": 128, + "cadence": 168 + }, + { + "t": "2026-10-14T06:30:10.000Z", + "hr": 133 + } + ], + "sampleCount": 3 + }, + "route": null + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/workouts-batch.04-route-kept.json.snap b/tests/integration/routes/parity/__snapshots__/workouts-batch.04-route-kept.json.snap new file mode 100644 index 000000000..39a3769e3 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/workouts-batch.04-route-kept.json.snap @@ -0,0 +1,266 @@ +{ + "status": 200, + "body": { + "data": { + "processed": 1, + "inserted": 1, + "duplicates": 0, + "skipped": [], + "entries": [ + { + "index": 0, + "status": "inserted" + } + ] + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "workout.batch.ingest" + }, + "meta": { + "write_bucket": "own", + "pr_detection_enqueued": true, + "pr_detection_silent": false, + "processed": 1, + "inserted": 1, + "duplicates": 0, + "enriched": 0, + "updated": 0, + "skipped": 0, + "routes_dropped": 0 + }, + "warnings": null, + "error": null + } + ], + "effects": [ + { + "effect": "enqueuePrDetection", + "args": [ + "", + { + "silent": false + } + ] + }, + { + "effect": "emitDataArrival", + "args": [ + { + "userId": "", + "kind": "workout", + "newestSampleAt": "2026-10-15T06:30:00.000Z", + "insertedCount": 1, + "refId": "", + "source": "batch" + } + ] + } + ], + "stored": [ + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-08T07:00:00.000Z", + "endedAt": "2026-10-08T07:50:00.000Z", + "durationSec": 3000, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-2", + "externalSourceVersion": null, + "metadata": null, + "samples": { + "samples": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 121 + }, + { + "t": "2026-10-14T06:30:05.000Z", + "hr": 128, + "cadence": 168 + }, + { + "t": "2026-10-14T06:30:10.000Z", + "hr": 133 + } + ], + "sampleCount": 3 + }, + "route": null + }, + { + "id": "", + "sportType": "swimming", + "startedAt": "2026-10-11T07:00:00.000Z", + "endedAt": "2026-10-11T07:40:00.000Z", + "durationSec": 2400, + "totalEnergyKcal": null, + "totalDistanceM": 1500, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-swim-1", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "walking", + "startedAt": "2026-10-12T18:00:00.000Z", + "endedAt": "2026-10-12T18:30:00.000Z", + "durationSec": 1800, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": null, + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "walking", + "startedAt": "2026-10-12T18:00:00.000Z", + "endedAt": "2026-10-12T18:30:00.000Z", + "durationSec": 1800, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": null, + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "cycling", + "startedAt": "2026-10-13T16:00:00.000Z", + "endedAt": "2026-10-13T17:05:00.000Z", + "durationSec": 3900, + "totalEnergyKcal": null, + "totalDistanceM": 2400, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": "manual:ride-1", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-14T06:30:00.000Z", + "endedAt": "2026-10-14T07:15:00.000Z", + "durationSec": 2700, + "totalEnergyKcal": 610, + "totalDistanceM": 8200, + "avgHeartRate": 148, + "maxHeartRate": 171, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-1", + "externalSourceVersion": null, + "metadata": { + "HKIndoorWorkout": false + }, + "samples": { + "samples": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 121 + }, + { + "t": "2026-10-14T06:30:05.000Z", + "hr": 128, + "cadence": 168 + }, + { + "t": "2026-10-14T06:30:10.000Z", + "hr": 133 + } + ], + "sampleCount": 3 + }, + "route": null + }, + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-15T06:30:00.000Z", + "endedAt": "2026-10-15T07:15:00.000Z", + "durationSec": 2700, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": 6, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-3", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": { + "geometry": null, + "sealed": true, + "sampleTimestamps": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 120 + }, + { + "t": "2026-10-14T06:31:00.000Z", + "hr": 131 + }, + { + "t": "2026-10-14T06:32:00.000Z", + "hr": 137 + } + ] + } + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/workouts-batch.05-too-large.json.snap b/tests/integration/routes/parity/__snapshots__/workouts-batch.05-too-large.json.snap new file mode 100644 index 000000000..7239701b4 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/workouts-batch.05-too-large.json.snap @@ -0,0 +1,225 @@ +{ + "status": 400, + "body": { + "data": null, + "error": "Batch exceeds the 100-workout limit", + "meta": { + "errorCode": "workout.batch.too_large", + "requestId": "" + } + }, + "events": [ + { + "status": 400, + "action": null, + "meta": { + "write_bucket": "own" + }, + "warnings": null, + "error": null + } + ], + "effects": [], + "stored": [ + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-08T07:00:00.000Z", + "endedAt": "2026-10-08T07:50:00.000Z", + "durationSec": 3000, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-2", + "externalSourceVersion": null, + "metadata": null, + "samples": { + "samples": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 121 + }, + { + "t": "2026-10-14T06:30:05.000Z", + "hr": 128, + "cadence": 168 + }, + { + "t": "2026-10-14T06:30:10.000Z", + "hr": 133 + } + ], + "sampleCount": 3 + }, + "route": null + }, + { + "id": "", + "sportType": "swimming", + "startedAt": "2026-10-11T07:00:00.000Z", + "endedAt": "2026-10-11T07:40:00.000Z", + "durationSec": 2400, + "totalEnergyKcal": null, + "totalDistanceM": 1500, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-swim-1", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "walking", + "startedAt": "2026-10-12T18:00:00.000Z", + "endedAt": "2026-10-12T18:30:00.000Z", + "durationSec": 1800, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": null, + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "walking", + "startedAt": "2026-10-12T18:00:00.000Z", + "endedAt": "2026-10-12T18:30:00.000Z", + "durationSec": 1800, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": null, + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "cycling", + "startedAt": "2026-10-13T16:00:00.000Z", + "endedAt": "2026-10-13T17:05:00.000Z", + "durationSec": 3900, + "totalEnergyKcal": null, + "totalDistanceM": 2400, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": "manual:ride-1", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-14T06:30:00.000Z", + "endedAt": "2026-10-14T07:15:00.000Z", + "durationSec": 2700, + "totalEnergyKcal": 610, + "totalDistanceM": 8200, + "avgHeartRate": 148, + "maxHeartRate": 171, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-1", + "externalSourceVersion": null, + "metadata": { + "HKIndoorWorkout": false + }, + "samples": { + "samples": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 121 + }, + { + "t": "2026-10-14T06:30:05.000Z", + "hr": 128, + "cadence": 168 + }, + { + "t": "2026-10-14T06:30:10.000Z", + "hr": 133 + } + ], + "sampleCount": 3 + }, + "route": null + }, + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-15T06:30:00.000Z", + "endedAt": "2026-10-15T07:15:00.000Z", + "durationSec": 2700, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": 6, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-3", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": { + "geometry": null, + "sealed": true, + "sampleTimestamps": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 120 + }, + { + "t": "2026-10-14T06:31:00.000Z", + "hr": 131 + }, + { + "t": "2026-10-14T06:32:00.000Z", + "hr": 137 + } + ] + } + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/workouts-batch.06-invalid.json.snap b/tests/integration/routes/parity/__snapshots__/workouts-batch.06-invalid.json.snap new file mode 100644 index 000000000..2b79858ba --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/workouts-batch.06-invalid.json.snap @@ -0,0 +1,225 @@ +{ + "status": 400, + "body": { + "data": null, + "error": "Invalid ISO datetime", + "meta": { + "errorCode": "workout.batch.invalid", + "requestId": "" + } + }, + "events": [ + { + "status": 400, + "action": null, + "meta": { + "write_bucket": "own" + }, + "warnings": null, + "error": null + } + ], + "effects": [], + "stored": [ + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-08T07:00:00.000Z", + "endedAt": "2026-10-08T07:50:00.000Z", + "durationSec": 3000, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-2", + "externalSourceVersion": null, + "metadata": null, + "samples": { + "samples": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 121 + }, + { + "t": "2026-10-14T06:30:05.000Z", + "hr": 128, + "cadence": 168 + }, + { + "t": "2026-10-14T06:30:10.000Z", + "hr": 133 + } + ], + "sampleCount": 3 + }, + "route": null + }, + { + "id": "", + "sportType": "swimming", + "startedAt": "2026-10-11T07:00:00.000Z", + "endedAt": "2026-10-11T07:40:00.000Z", + "durationSec": 2400, + "totalEnergyKcal": null, + "totalDistanceM": 1500, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-swim-1", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "walking", + "startedAt": "2026-10-12T18:00:00.000Z", + "endedAt": "2026-10-12T18:30:00.000Z", + "durationSec": 1800, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": null, + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "walking", + "startedAt": "2026-10-12T18:00:00.000Z", + "endedAt": "2026-10-12T18:30:00.000Z", + "durationSec": 1800, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": null, + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "cycling", + "startedAt": "2026-10-13T16:00:00.000Z", + "endedAt": "2026-10-13T17:05:00.000Z", + "durationSec": 3900, + "totalEnergyKcal": null, + "totalDistanceM": 2400, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": "manual:ride-1", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-14T06:30:00.000Z", + "endedAt": "2026-10-14T07:15:00.000Z", + "durationSec": 2700, + "totalEnergyKcal": 610, + "totalDistanceM": 8200, + "avgHeartRate": 148, + "maxHeartRate": 171, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-1", + "externalSourceVersion": null, + "metadata": { + "HKIndoorWorkout": false + }, + "samples": { + "samples": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 121 + }, + { + "t": "2026-10-14T06:30:05.000Z", + "hr": 128, + "cadence": 168 + }, + { + "t": "2026-10-14T06:30:10.000Z", + "hr": 133 + } + ], + "sampleCount": 3 + }, + "route": null + }, + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-15T06:30:00.000Z", + "endedAt": "2026-10-15T07:15:00.000Z", + "durationSec": 2700, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": 6, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-3", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": { + "geometry": null, + "sealed": true, + "sampleTimestamps": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 120 + }, + { + "t": "2026-10-14T06:31:00.000Z", + "hr": 131 + }, + { + "t": "2026-10-14T06:32:00.000Z", + "hr": 137 + } + ] + } + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/workouts-batch.07a-bridge-with-source.json.snap b/tests/integration/routes/parity/__snapshots__/workouts-batch.07a-bridge-with-source.json.snap new file mode 100644 index 000000000..c44129f53 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/workouts-batch.07a-bridge-with-source.json.snap @@ -0,0 +1,228 @@ +{ + "status": 422, + "body": { + "data": null, + "error": "This credential resolves the source itself; omit the field", + "meta": { + "errorCode": "workout.batch.source_not_permitted", + "requestId": "" + } + }, + "events": [ + { + "status": 422, + "action": { + "name": "workout.batch.ingest" + }, + "meta": { + "write_bucket": "own", + "outcome": "source_not_permitted" + }, + "warnings": null, + "error": null + } + ], + "effects": [], + "stored": [ + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-08T07:00:00.000Z", + "endedAt": "2026-10-08T07:50:00.000Z", + "durationSec": 3000, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-2", + "externalSourceVersion": null, + "metadata": null, + "samples": { + "samples": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 121 + }, + { + "t": "2026-10-14T06:30:05.000Z", + "hr": 128, + "cadence": 168 + }, + { + "t": "2026-10-14T06:30:10.000Z", + "hr": 133 + } + ], + "sampleCount": 3 + }, + "route": null + }, + { + "id": "", + "sportType": "swimming", + "startedAt": "2026-10-11T07:00:00.000Z", + "endedAt": "2026-10-11T07:40:00.000Z", + "durationSec": 2400, + "totalEnergyKcal": null, + "totalDistanceM": 1500, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-swim-1", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "walking", + "startedAt": "2026-10-12T18:00:00.000Z", + "endedAt": "2026-10-12T18:30:00.000Z", + "durationSec": 1800, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": null, + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "walking", + "startedAt": "2026-10-12T18:00:00.000Z", + "endedAt": "2026-10-12T18:30:00.000Z", + "durationSec": 1800, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": null, + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "cycling", + "startedAt": "2026-10-13T16:00:00.000Z", + "endedAt": "2026-10-13T17:05:00.000Z", + "durationSec": 3900, + "totalEnergyKcal": null, + "totalDistanceM": 2400, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": "manual:ride-1", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-14T06:30:00.000Z", + "endedAt": "2026-10-14T07:15:00.000Z", + "durationSec": 2700, + "totalEnergyKcal": 610, + "totalDistanceM": 8200, + "avgHeartRate": 148, + "maxHeartRate": 171, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-1", + "externalSourceVersion": null, + "metadata": { + "HKIndoorWorkout": false + }, + "samples": { + "samples": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 121 + }, + { + "t": "2026-10-14T06:30:05.000Z", + "hr": 128, + "cadence": 168 + }, + { + "t": "2026-10-14T06:30:10.000Z", + "hr": 133 + } + ], + "sampleCount": 3 + }, + "route": null + }, + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-15T06:30:00.000Z", + "endedAt": "2026-10-15T07:15:00.000Z", + "durationSec": 2700, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": 6, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-3", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": { + "geometry": null, + "sealed": true, + "sampleTimestamps": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 120 + }, + { + "t": "2026-10-14T06:31:00.000Z", + "hr": 131 + }, + { + "t": "2026-10-14T06:32:00.000Z", + "hr": 137 + } + ] + } + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/workouts-batch.07b-bridge.json.snap b/tests/integration/routes/parity/__snapshots__/workouts-batch.07b-bridge.json.snap new file mode 100644 index 000000000..6841862c7 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/workouts-batch.07b-bridge.json.snap @@ -0,0 +1,287 @@ +{ + "status": 200, + "body": { + "data": { + "processed": 1, + "inserted": 1, + "duplicates": 0, + "skipped": [], + "entries": [ + { + "index": 0, + "status": "inserted" + } + ] + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "workout.batch.ingest" + }, + "meta": { + "write_bucket": "own", + "pr_detection_enqueued": true, + "pr_detection_silent": false, + "processed": 1, + "inserted": 1, + "duplicates": 0, + "enriched": 0, + "updated": 0, + "skipped": 0, + "routes_dropped": 0 + }, + "warnings": null, + "error": null + } + ], + "effects": [ + { + "effect": "enqueuePrDetection", + "args": [ + "", + { + "silent": false + } + ] + }, + { + "effect": "emitDataArrival", + "args": [ + { + "userId": "", + "kind": "workout", + "newestSampleAt": "2026-10-15T09:00:00.000Z", + "insertedCount": 1, + "refId": "", + "source": "batch" + } + ] + } + ], + "stored": [ + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-08T07:00:00.000Z", + "endedAt": "2026-10-08T07:50:00.000Z", + "durationSec": 3000, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-2", + "externalSourceVersion": null, + "metadata": null, + "samples": { + "samples": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 121 + }, + { + "t": "2026-10-14T06:30:05.000Z", + "hr": 128, + "cadence": 168 + }, + { + "t": "2026-10-14T06:30:10.000Z", + "hr": 133 + } + ], + "sampleCount": 3 + }, + "route": null + }, + { + "id": "", + "sportType": "swimming", + "startedAt": "2026-10-11T07:00:00.000Z", + "endedAt": "2026-10-11T07:40:00.000Z", + "durationSec": 2400, + "totalEnergyKcal": null, + "totalDistanceM": 1500, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-swim-1", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "walking", + "startedAt": "2026-10-12T18:00:00.000Z", + "endedAt": "2026-10-12T18:30:00.000Z", + "durationSec": 1800, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": null, + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "walking", + "startedAt": "2026-10-12T18:00:00.000Z", + "endedAt": "2026-10-12T18:30:00.000Z", + "durationSec": 1800, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": null, + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "cycling", + "startedAt": "2026-10-13T16:00:00.000Z", + "endedAt": "2026-10-13T17:05:00.000Z", + "durationSec": 3900, + "totalEnergyKcal": null, + "totalDistanceM": 2400, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "MANUAL", + "externalId": "manual:ride-1", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + }, + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-14T06:30:00.000Z", + "endedAt": "2026-10-14T07:15:00.000Z", + "durationSec": 2700, + "totalEnergyKcal": 610, + "totalDistanceM": 8200, + "avgHeartRate": 148, + "maxHeartRate": 171, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-1", + "externalSourceVersion": null, + "metadata": { + "HKIndoorWorkout": false + }, + "samples": { + "samples": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 121 + }, + { + "t": "2026-10-14T06:30:05.000Z", + "hr": 128, + "cadence": 168 + }, + { + "t": "2026-10-14T06:30:10.000Z", + "hr": 133 + } + ], + "sampleCount": 3 + }, + "route": null + }, + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-15T06:30:00.000Z", + "endedAt": "2026-10-15T07:15:00.000Z", + "durationSec": 2700, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": 6, + "pauseDurationSec": null, + "source": "APPLE_HEALTH", + "externalId": "hk-run-3", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": { + "geometry": null, + "sealed": true, + "sampleTimestamps": [ + { + "t": "2026-10-14T06:30:00.000Z", + "hr": 120 + }, + { + "t": "2026-10-14T06:31:00.000Z", + "hr": 131 + }, + { + "t": "2026-10-14T06:32:00.000Z", + "hr": 137 + } + ] + } + }, + { + "id": "", + "sportType": "running", + "startedAt": "2026-10-15T09:00:00.000Z", + "endedAt": "2026-10-15T09:20:00.000Z", + "durationSec": 1200, + "totalEnergyKcal": null, + "totalDistanceM": null, + "avgHeartRate": null, + "maxHeartRate": null, + "minHeartRate": null, + "stepCount": null, + "elevationM": null, + "pauseDurationSec": null, + "source": "EXTERNAL", + "externalId": "bridge-1", + "externalSourceVersion": null, + "metadata": null, + "samples": null, + "route": null + } + ] +} diff --git a/tests/integration/routes/parity/workouts-batch.parity.test.ts b/tests/integration/routes/parity/workouts-batch.parity.test.ts new file mode 100644 index 000000000..66b2ff8b6 --- /dev/null +++ b/tests/integration/routes/parity/workouts-batch.parity.test.ts @@ -0,0 +1,317 @@ +/** + * Parity for `POST /api/workouts/batch` while its ingest moves into + * `src/lib/workouts/batch/`. + * + * One account walks through the cases the route tells apart, each request + * seeing what the earlier ones stored: a first batch (a HealthKit run with + * its heart-rate series and a track the account does not keep, a + * hand-entered ride, one with no external id, the same session from two + * sources, an unstable id and a series that fails validation), the same + * batch again with a series for a stored workout that had none, a corrected + * hand-entered workout, a track kept once the account opts in, the two + * whole-batch refusals, and a narrow bridge token with and without a + * `source`. After each request the snapshot holds the response, the wide + * event, the queue and arrival tails, and the stored workouts with their + * series and tracks (a sealed track as its presence, since its bytes carry + * a random nonce). + */ +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; + +import { POST } from "@/app/api/workouts/batch/route"; +import { WORKOUTS_WRITE_SCOPE } from "@/lib/workouts/scopes"; + +import { callRoute, type RouteAuth } from "../../../kit/call-route"; +import { makeSession, makeToken, makeUser } from "../../../kit/factories"; +import { + effectRecorder, + eventFacts, + expectParity, + stable, + wideEventRecorder, +} from "../../../kit/parity"; +import { getPrismaClient, truncateAllTables } from "../../setup"; + +vi.mock("@/lib/logging/transports", async (importOriginal) => { + const actual = + await importOriginal(); + const { wideEventRecorder: recorder } = await import("../../../kit/parity"); + return { + ...actual, + emitIfSampled: (event: (typeof recorder.events)[number]) => { + recorder.events.push(event); + }, + }; +}); +vi.mock("@/lib/arrivals/emit-shared", async (importOriginal) => { + const actual = + await importOriginal(); + const { effectRecorder } = await import("../../../kit/parity"); + return { + ...actual, + emitDataArrival: async (...args: unknown[]) => { + effectRecorder.record("emitDataArrival", args); + }, + }; +}); +vi.mock("@/lib/jobs/pr-detection", async (importOriginal) => { + const actual = + await importOriginal(); + const { effectRecorder } = await import("../../../kit/parity"); + return { + ...actual, + enqueuePrDetection: async (...args: unknown[]) => { + effectRecorder.record("enqueuePrDetection", args); + }, + }; +}); + +const prisma = getPrismaClient(); +const NOW = new Date("2026-10-15T12:00:00.000Z"); + +type Workout = Record; + +function workout( + externalId: string | undefined, + start: string, + minutes: number, + extra: Workout = {}, +): Workout { + const startedAt = new Date(start); + return { + sportType: "running", + startedAt: startedAt.toISOString(), + endedAt: new Date(startedAt.getTime() + minutes * 60_000).toISOString(), + source: "APPLE_HEALTH", + ...(externalId === undefined ? {} : { externalId }), + ...extra, + }; +} + +const SERIES = [ + { t: "2026-10-14T06:30:00.000Z", hr: 121 }, + { t: "2026-10-14T06:30:05.000Z", hr: 128, cadence: 168 }, + { t: "2026-10-14T06:30:10.000Z", hr: 133 }, +]; + +const TRACK = { + geometry: { + type: "LineString", + coordinates: [ + [7.2162, 51.4818, 98], + [7.2171, 51.4822, 104], + [7.2183, 51.4829, 101], + ], + }, + sampleTimestamps: [ + { t: "2026-10-14T06:30:00.000Z", hr: 120 }, + { t: "2026-10-14T06:31:00.000Z", hr: 131 }, + { t: "2026-10-14T06:32:00.000Z", hr: 137 }, + ], +}; + +const FIRST_BATCH: Workout[] = [ + workout("hk-run-1", "2026-10-14T06:30:00.000Z", 45, { + totalDistanceM: 8_200, + totalEnergyKcal: 610, + avgHeartRate: 148, + maxHeartRate: 171, + samples: SERIES, + route: TRACK, + metadata: { HKIndoorWorkout: false }, + }), + workout("manual:ride-1", "2026-10-13T16:00:00.000Z", 60, { + sportType: "cycling", + source: "MANUAL", + totalDistanceM: 24_000, + }), + workout(undefined, "2026-10-12T18:00:00.000Z", 30, { + sportType: "walking", + source: "MANUAL", + }), + // The same session from the phone and from a hand entry. + workout("hk-swim-1", "2026-10-11T07:00:00.000Z", 40, { + sportType: "swimming", + totalDistanceM: 1_500, + }), + workout("manual:swim-1", "2026-10-11T07:00:30.000Z", 40, { + sportType: "swimming", + source: "MANUAL", + totalDistanceM: 1_500, + }), + // Refused per entry. + workout("", "2026-10-10T07:00:00.000Z", 30), + workout("hk-run-bad-series", "2026-10-09T07:00:00.000Z", 30, { + samples: [{ t: "2026-10-09T07:00:00.000Z", hr: 999 }], + }), + workout("hk-run-2", "2026-10-08T07:00:00.000Z", 50), +]; + +let userId: string; +let session: RouteAuth; + +async function state() { + const workouts = await prisma.workout.findMany({ + where: { userId }, + orderBy: [{ startedAt: "asc" }, { source: "asc" }], + select: { + id: true, + sportType: true, + startedAt: true, + endedAt: true, + durationSec: true, + totalEnergyKcal: true, + totalDistanceM: true, + avgHeartRate: true, + maxHeartRate: true, + minHeartRate: true, + stepCount: true, + elevationM: true, + pauseDurationSec: true, + source: true, + externalId: true, + externalSourceVersion: true, + metadata: true, + samples: { select: { samples: true, sampleCount: true } }, + route: { + select: { + geometry: true, + geometryEncrypted: true, + sampleTimestamps: true, + }, + }, + }, + }); + return workouts.map(({ route, ...w }) => ({ + ...w, + route: route + ? { + geometry: route.geometry, + sealed: route.geometryEncrypted !== null, + sampleTimestamps: route.sampleTimestamps, + } + : null, + })); +} + +async function step( + name: string, + body: unknown, + auth: RouteAuth = session, +): Promise { + wideEventRecorder.take(); + effectRecorder.take(); + const res = await callRoute(POST, { + method: "POST", + route: "/api/workouts/batch", + body, + auth, + }); + const events = wideEventRecorder.take(); + expect(events.length).toBeGreaterThan(0); + // Arrivals are handed off without being awaited; let them reach the + // recorder before the snapshot is taken. + await new Promise((resolve) => setTimeout(resolve, 0)); + await expectParity( + `workouts-batch.${name}`, + stable( + { + status: res.status, + body: res.body, + events: events.map(eventFacts), + effects: effectRecorder.take(), + stored: await state(), + }, + { since: NOW }, + ), + ); +} + +beforeAll(async () => { + vi.useFakeTimers({ toFake: ["Date"], now: NOW }); + await truncateAllTables(prisma); + const user = await makeUser({ timezone: "UTC" }); + userId = user.id; + session = await makeSession(user); +}); + +afterAll(() => { + vi.useRealTimers(); +}); + +describe("POST /api/workouts/batch parity", () => { + it("01 a first batch", async () => { + await step("01-first-batch", { workouts: FIRST_BATCH }); + }); + + it("02 the same batch again, with a series for a stored workout", async () => { + await step("02-same-batch-enrich", { + workouts: [ + ...FIRST_BATCH.slice(0, -1), + workout("hk-run-2", "2026-10-08T07:00:00.000Z", 50, { + samples: SERIES, + }), + ], + }); + }); + + it("03 a corrected hand-entered workout", async () => { + await step("03-manual-correction", { + workouts: [ + workout("manual:ride-1", "2026-10-13T16:00:00.000Z", 65, { + sportType: "cycling", + source: "MANUAL", + totalDistanceM: 2_400, + }), + workout("manual:ride-1", "2026-10-13T16:00:00.000Z", 65, { + sportType: "cycling", + source: "MANUAL", + totalDistanceM: 2_500, + }), + ], + }); + }); + + it("04 a track kept once the account opts in", async () => { + await prisma.user.update({ + where: { id: userId }, + data: { storeWorkoutRoutes: true }, + }); + await step("04-route-kept", { + workouts: [ + workout("hk-run-3", "2026-10-15T06:30:00.000Z", 45, { route: TRACK }), + ], + }); + }); + + it("05 a batch over the workout limit", async () => { + await step("05-too-large", { + workouts: Array.from({ length: 101 }, (_, i) => + workout(`hk-bulk-${i}`, "2026-09-01T07:00:00.000Z", 30), + ), + }); + }); + + it("06 a batch the schema refuses", async () => { + await step("06-invalid", { + workouts: [{ sportType: "running", startedAt: "yesterday" }], + }); + }); + + it("07 a bridge token naming a source, and one that does not", async () => { + const bridge = await makeToken( + { id: userId }, + { permissions: [WORKOUTS_WRITE_SCOPE] }, + ); + await step( + "07a-bridge-with-source", + { workouts: [workout("bridge-1", "2026-10-15T09:00:00.000Z", 20)] }, + bridge, + ); + const { source: _source, ...unsourced } = workout( + "bridge-1", + "2026-10-15T09:00:00.000Z", + 20, + ); + await step("07b-bridge", { workouts: [unsourced] }, bridge); + }); +});