mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
test(workouts): pin the batch ingest before moving it into a service
A parity walk over POST /api/workouts/batch: a mixed first batch with every per-entry refusal and the cross-source twin, the same batch with an enrichment, a corrected hand entry, a kept track, both whole-batch refusals and a narrow bridge token. Responses, wide events, queue tails and stored workouts with their series and tracks are pinned.
This commit is contained in:
1 parent
7ea247e193
commit
dd52a8d92a
9 files changed
+2334
No files matched your search
+284
@@ -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": [
|
||||
"<id:1>",
|
||||
{
|
||||
"silent": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"effect": "emitDataArrival",
|
||||
"args": [
|
||||
{
|
||||
"userId": "<id:1>",
|
||||
"kind": "workout",
|
||||
"newestSampleAt": "2026-10-14T06:30:00.000Z",
|
||||
"insertedCount": 1,
|
||||
"refId": "<id:2>",
|
||||
"source": "batch"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"effect": "emitDataArrival",
|
||||
"args": [
|
||||
{
|
||||
"userId": "<id:1>",
|
||||
"kind": "workout",
|
||||
"newestSampleAt": "2026-10-13T16:00:00.000Z",
|
||||
"insertedCount": 1,
|
||||
"refId": "<id:3>",
|
||||
"source": "batch"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"effect": "emitDataArrival",
|
||||
"args": [
|
||||
{
|
||||
"userId": "<id:1>",
|
||||
"kind": "workout",
|
||||
"newestSampleAt": "2026-10-12T18:00:00.000Z",
|
||||
"insertedCount": 1,
|
||||
"refId": "<id:4>",
|
||||
"source": "batch"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"effect": "emitDataArrival",
|
||||
"args": [
|
||||
{
|
||||
"userId": "<id:1>",
|
||||
"kind": "workout",
|
||||
"newestSampleAt": "2026-10-11T07:00:00.000Z",
|
||||
"insertedCount": 1,
|
||||
"refId": "<id:5>",
|
||||
"source": "batch"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"effect": "emitDataArrival",
|
||||
"args": [
|
||||
{
|
||||
"userId": "<id:1>",
|
||||
"kind": "workout",
|
||||
"newestSampleAt": "2026-10-08T07:00:00.000Z",
|
||||
"insertedCount": 1,
|
||||
"refId": "<id:6>",
|
||||
"source": "batch"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"stored": [
|
||||
{
|
||||
"id": "<id:6>",
|
||||
"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": "<id:5>",
|
||||
"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": "<id:4>",
|
||||
"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": "<id:3>",
|
||||
"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": "<id:2>",
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
+270
@@ -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": [
|
||||
"<id:1>",
|
||||
{
|
||||
"silent": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"effect": "emitDataArrival",
|
||||
"args": [
|
||||
{
|
||||
"userId": "<id:1>",
|
||||
"kind": "workout",
|
||||
"newestSampleAt": "2026-10-12T18:00:00.000Z",
|
||||
"insertedCount": 1,
|
||||
"refId": "<id:2>",
|
||||
"source": "batch"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"stored": [
|
||||
{
|
||||
"id": "<id:3>",
|
||||
"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": "<id:4>",
|
||||
"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": "<id:5>",
|
||||
"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": "<id:2>",
|
||||
"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": "<id:6>",
|
||||
"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": "<id:7>",
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
+232
@@ -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": [
|
||||
"<id:1>",
|
||||
{
|
||||
"silent": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"effect": "emitDataArrival",
|
||||
"args": [
|
||||
{
|
||||
"userId": "<id:1>",
|
||||
"kind": "workout",
|
||||
"newestSampleAt": "2026-10-13T16:00:00.000Z",
|
||||
"insertedCount": 1,
|
||||
"refId": "<id:2>",
|
||||
"source": "batch"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"stored": [
|
||||
{
|
||||
"id": "<id:3>",
|
||||
"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": "<id:4>",
|
||||
"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": "<id:5>",
|
||||
"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": "<id:6>",
|
||||
"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": "<id:2>",
|
||||
"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": "<id:7>",
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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": [
|
||||
"<id:1>",
|
||||
{
|
||||
"silent": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"effect": "emitDataArrival",
|
||||
"args": [
|
||||
{
|
||||
"userId": "<id:1>",
|
||||
"kind": "workout",
|
||||
"newestSampleAt": "2026-10-15T06:30:00.000Z",
|
||||
"insertedCount": 1,
|
||||
"refId": "<id:2>",
|
||||
"source": "batch"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"stored": [
|
||||
{
|
||||
"id": "<id:3>",
|
||||
"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": "<id:4>",
|
||||
"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": "<id:5>",
|
||||
"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": "<id:6>",
|
||||
"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": "<id:7>",
|
||||
"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": "<id:8>",
|
||||
"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": "<id:2>",
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
{
|
||||
"status": 400,
|
||||
"body": {
|
||||
"data": null,
|
||||
"error": "Batch exceeds the 100-workout limit",
|
||||
"meta": {
|
||||
"errorCode": "workout.batch.too_large",
|
||||
"requestId": "<requestId>"
|
||||
}
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"status": 400,
|
||||
"action": null,
|
||||
"meta": {
|
||||
"write_bucket": "own"
|
||||
},
|
||||
"warnings": null,
|
||||
"error": null
|
||||
}
|
||||
],
|
||||
"effects": [],
|
||||
"stored": [
|
||||
{
|
||||
"id": "<id:1>",
|
||||
"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": "<id:2>",
|
||||
"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": "<id:3>",
|
||||
"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": "<id:4>",
|
||||
"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": "<id:5>",
|
||||
"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": "<id:6>",
|
||||
"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": "<id:7>",
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
{
|
||||
"status": 400,
|
||||
"body": {
|
||||
"data": null,
|
||||
"error": "Invalid ISO datetime",
|
||||
"meta": {
|
||||
"errorCode": "workout.batch.invalid",
|
||||
"requestId": "<requestId>"
|
||||
}
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"status": 400,
|
||||
"action": null,
|
||||
"meta": {
|
||||
"write_bucket": "own"
|
||||
},
|
||||
"warnings": null,
|
||||
"error": null
|
||||
}
|
||||
],
|
||||
"effects": [],
|
||||
"stored": [
|
||||
{
|
||||
"id": "<id:1>",
|
||||
"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": "<id:2>",
|
||||
"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": "<id:3>",
|
||||
"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": "<id:4>",
|
||||
"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": "<id:5>",
|
||||
"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": "<id:6>",
|
||||
"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": "<id:7>",
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+228
@@ -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": "<requestId>"
|
||||
}
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"status": 422,
|
||||
"action": {
|
||||
"name": "workout.batch.ingest"
|
||||
},
|
||||
"meta": {
|
||||
"write_bucket": "own",
|
||||
"outcome": "source_not_permitted"
|
||||
},
|
||||
"warnings": null,
|
||||
"error": null
|
||||
}
|
||||
],
|
||||
"effects": [],
|
||||
"stored": [
|
||||
{
|
||||
"id": "<id:1>",
|
||||
"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": "<id:2>",
|
||||
"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": "<id:3>",
|
||||
"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": "<id:4>",
|
||||
"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": "<id:5>",
|
||||
"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": "<id:6>",
|
||||
"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": "<id:7>",
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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": [
|
||||
"<id:1>",
|
||||
{
|
||||
"silent": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"effect": "emitDataArrival",
|
||||
"args": [
|
||||
{
|
||||
"userId": "<id:1>",
|
||||
"kind": "workout",
|
||||
"newestSampleAt": "2026-10-15T09:00:00.000Z",
|
||||
"insertedCount": 1,
|
||||
"refId": "<id:2>",
|
||||
"source": "batch"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"stored": [
|
||||
{
|
||||
"id": "<id:3>",
|
||||
"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": "<id:4>",
|
||||
"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": "<id:5>",
|
||||
"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": "<id:6>",
|
||||
"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": "<id:7>",
|
||||
"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": "<id:8>",
|
||||
"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": "<id:9>",
|
||||
"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": "<id:2>",
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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<typeof import("@/lib/logging/transports")>();
|
||||
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<typeof import("@/lib/arrivals/emit-shared")>();
|
||||
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<typeof import("@/lib/jobs/pr-detection")>();
|
||||
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<string, unknown>;
|
||||
|
||||
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("<HKWorkout: 0x600002f0c1e0>", "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<void> {
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user