diff --git a/tests/integration/routes/parity/__snapshots__/measurements-batch.01-first-sync.json.snap b/tests/integration/routes/parity/__snapshots__/measurements-batch.01-first-sync.json.snap new file mode 100644 index 000000000..7c208aa7e --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements-batch.01-first-sync.json.snap @@ -0,0 +1,512 @@ +{ + "status": 200, + "body": { + "data": { + "processed": 21, + "inserted": 14, + "updated": 0, + "duplicates": 2, + "failed": 0, + "skipped": [ + { + "index": 12, + "reason": "unmappable_identifier" + }, + { + "index": 13, + "reason": "value_out_of_range" + }, + { + "index": 14, + "reason": "implausible_timestamp" + }, + { + "index": 15, + "reason": "malformed_hr_bucket_id" + }, + { + "index": 16, + "reason": "unstable_external_id" + } + ], + "entries": [ + { + "index": 0, + "status": "inserted" + }, + { + "index": 1, + "status": "inserted" + }, + { + "index": 2, + "status": "inserted" + }, + { + "index": 3, + "status": "inserted" + }, + { + "index": 4, + "status": "inserted" + }, + { + "index": 5, + "status": "inserted" + }, + { + "index": 6, + "status": "inserted" + }, + { + "index": 7, + "status": "inserted" + }, + { + "index": 8, + "status": "inserted" + }, + { + "index": 9, + "status": "inserted" + }, + { + "index": 10, + "status": "inserted" + }, + { + "index": 11, + "status": "inserted" + }, + { + "index": 12, + "status": "skipped", + "reason": "unmappable_identifier" + }, + { + "index": 13, + "status": "skipped", + "reason": "value_out_of_range", + "convertedValue": 900, + "range": { + "min": 20, + "max": 300, + "unit": "bpm" + } + }, + { + "index": 14, + "status": "skipped", + "reason": "implausible_timestamp" + }, + { + "index": 15, + "status": "skipped", + "reason": "malformed_hr_bucket_id" + }, + { + "index": 16, + "status": "skipped", + "reason": "unstable_external_id" + }, + { + "index": 17, + "status": "inserted" + }, + { + "index": 18, + "status": "duplicate" + }, + { + "index": 19, + "status": "duplicate", + "reason": "superseded_in_batch" + }, + { + "index": 20, + "status": "inserted" + } + ] + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "measurement.batch.ingest" + }, + "meta": { + "write_bucket": "own", + "external_id_rejected": 1, + "external_id_shapes": "object_description", + "external_id_surface": "measurement.batch", + "attempted": 3, + "inserted": 12, + "updated": 0, + "processed": 21, + "duplicate": 0, + "skipped": 5, + "failed": 0, + "pr_detection_enqueued": true, + "pr_detection_silent": false, + "stats_unchanged": 0, + "duplicates": 2, + "skipped_by_reason": { + "unmappable_identifier": 1, + "value_out_of_range": 1, + "implausible_timestamp": 1, + "malformed_hr_bucket_id": 1, + "unstable_external_id": 1 + }, + "syncTrigger": "foreground", + "folded_window": 0, + "set_inserted": 10 + }, + "warnings": null, + "error": null + } + ], + "effects": [ + { + "effect": "enqueueReminderSatisfy", + "args": [ + "" + ] + }, + { + "effect": "enqueuePrDetection", + "args": [ + "", + { + "silent": false + } + ] + }, + { + "effect": "maybeEnqueueMorningRefresh", + "args": [ + "", + [ + "2026-10-14T01:00:00.000Z", + "2026-10-14T02:30:00.000Z" + ] + ] + }, + { + "effect": "emitDataArrival", + "args": [ + { + "userId": "", + "kind": "weight", + "newestSampleAt": "2026-10-14T06:30:00.000Z", + "insertedCount": 1, + "source": "batch" + } + ] + } + ], + "stored": { + "measurements": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T06:30:00.000Z", + "externalId": "uuid-mass-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "scale", + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 62, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:00:00.000Z", + "externalId": "uuid-hr-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "watch", + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 64, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:05:00.000Z", + "externalId": "uuid-hr-2", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 65, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:25:00.000Z", + "externalId": "uuid-hr-twice", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 71, + "valueMin": 66, + "valueMax": 79, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T08:10:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierHeartRate:2026-10-14T08:10:00.000Z", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 70, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T08:20:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierHeartRate:2026-10-14T08:20:00.000Z", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 0, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T01:00:00.000Z", + "externalId": "uuid-sleep-1", + "externalSourceVersion": null, + "sleepStage": "CORE", + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 0, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T02:30:00.000Z", + "externalId": "uuid-sleep-2", + "externalSourceVersion": null, + "sleepStage": "DEEP", + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 7250, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-12T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-12", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 8400, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-13T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-13", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "phone", + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 5100, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-14", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "OXYGEN_SATURATION", + "value": 97, + "valueMin": null, + "valueMax": null, + "unit": "%", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:10:00.000Z", + "externalId": "uuid-spo2-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + } + ], + "hours": [ + { + "id": "", + "userId": "", + "type": "ACTIVITY_STEPS", + "dateKey": "2026-10-14", + "hourlyCumulative": [], + "dayTotal": 0, + "sampleCount": 0, + "hourlyIncrements": [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 900, + 1300, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1 + ], + "basis": "PER_SAMPLE", + "bucketCount": 2, + "timezone": "UTC", + "createdAt": "", + "updatedAt": "" + } + ], + "user": { + "lastSyncedAt": "", + "healthKitLastSyncedAt": "", + "healthKitLastSyncTrigger": "foreground", + "healthKitLastBackgroundSyncAt": null + }, + "typeSync": [ + { + "userId": "", + "type": "ACTIVITY_STEPS", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": null + }, + { + "userId": "", + "type": "OXYGEN_SATURATION", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": null + }, + { + "userId": "", + "type": "PULSE", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": null + }, + { + "userId": "", + "type": "SLEEP_DURATION", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": null + }, + { + "userId": "", + "type": "WEIGHT", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": null + } + ] + } +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements-batch.02-same-sync-again.json.snap b/tests/integration/routes/parity/__snapshots__/measurements-batch.02-same-sync-again.json.snap new file mode 100644 index 000000000..852e08794 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements-batch.02-same-sync-again.json.snap @@ -0,0 +1,497 @@ +{ + "status": 200, + "body": { + "data": { + "processed": 21, + "inserted": 0, + "updated": 7, + "duplicates": 9, + "failed": 0, + "skipped": [ + { + "index": 12, + "reason": "unmappable_identifier" + }, + { + "index": 13, + "reason": "value_out_of_range" + }, + { + "index": 14, + "reason": "implausible_timestamp" + }, + { + "index": 15, + "reason": "malformed_hr_bucket_id" + }, + { + "index": 16, + "reason": "unstable_external_id" + } + ], + "entries": [ + { + "index": 0, + "status": "duplicate" + }, + { + "index": 1, + "status": "duplicate" + }, + { + "index": 2, + "status": "duplicate" + }, + { + "index": 3, + "status": "duplicate" + }, + { + "index": 4, + "status": "updated" + }, + { + "index": 5, + "status": "updated" + }, + { + "index": 6, + "status": "updated" + }, + { + "index": 7, + "status": "updated" + }, + { + "index": 8, + "status": "duplicate" + }, + { + "index": 9, + "status": "duplicate" + }, + { + "index": 10, + "status": "updated" + }, + { + "index": 11, + "status": "updated" + }, + { + "index": 12, + "status": "skipped", + "reason": "unmappable_identifier" + }, + { + "index": 13, + "status": "skipped", + "reason": "value_out_of_range", + "convertedValue": 900, + "range": { + "min": 20, + "max": 300, + "unit": "bpm" + } + }, + { + "index": 14, + "status": "skipped", + "reason": "implausible_timestamp" + }, + { + "index": 15, + "status": "skipped", + "reason": "malformed_hr_bucket_id" + }, + { + "index": 16, + "status": "skipped", + "reason": "unstable_external_id" + }, + { + "index": 17, + "status": "duplicate" + }, + { + "index": 18, + "status": "duplicate" + }, + { + "index": 19, + "status": "duplicate", + "reason": "superseded_in_batch" + }, + { + "index": 20, + "status": "updated" + } + ] + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "measurement.batch.ingest" + }, + "meta": { + "write_bucket": "own", + "external_id_rejected": 1, + "external_id_shapes": "object_description", + "external_id_surface": "measurement.batch", + "attempted": 3, + "inserted": 0, + "updated": 5, + "processed": 21, + "duplicate": 0, + "skipped": 5, + "failed": 0, + "pr_detection_enqueued": true, + "pr_detection_silent": false, + "stats_unchanged": 4, + "duplicates": 9, + "skipped_by_reason": { + "unmappable_identifier": 1, + "value_out_of_range": 1, + "implausible_timestamp": 1, + "malformed_hr_bucket_id": 1, + "unstable_external_id": 1 + }, + "syncTrigger": "foreground", + "folded_window": 0, + "set_inserted": 0 + }, + "warnings": null, + "error": null + } + ], + "effects": [ + { + "effect": "enqueueReminderSatisfy", + "args": [ + "" + ] + }, + { + "effect": "enqueuePrDetection", + "args": [ + "", + { + "silent": false + } + ] + }, + { + "effect": "maybeEnqueueMorningRefresh", + "args": [ + "", + [] + ] + } + ], + "stored": { + "measurements": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T06:30:00.000Z", + "externalId": "uuid-mass-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "scale", + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 62, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:00:00.000Z", + "externalId": "uuid-hr-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "watch", + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 64, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:05:00.000Z", + "externalId": "uuid-hr-2", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 65, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:25:00.000Z", + "externalId": "uuid-hr-twice", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 71, + "valueMin": 66, + "valueMax": 79, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T08:10:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierHeartRate:2026-10-14T08:10:00.000Z", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 70, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T08:20:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierHeartRate:2026-10-14T08:20:00.000Z", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 0, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T01:00:00.000Z", + "externalId": "uuid-sleep-1", + "externalSourceVersion": null, + "sleepStage": "CORE", + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 0, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T02:30:00.000Z", + "externalId": "uuid-sleep-2", + "externalSourceVersion": null, + "sleepStage": "DEEP", + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 7250, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-12T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-12", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 8400, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-13T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-13", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "phone", + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 5100, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-14", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "OXYGEN_SATURATION", + "value": 97, + "valueMin": null, + "valueMax": null, + "unit": "%", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:10:00.000Z", + "externalId": "uuid-spo2-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + } + ], + "hours": [ + { + "id": "", + "userId": "", + "type": "ACTIVITY_STEPS", + "dateKey": "2026-10-14", + "hourlyCumulative": [], + "dayTotal": 0, + "sampleCount": 0, + "hourlyIncrements": [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 900, + 1300, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1 + ], + "basis": "PER_SAMPLE", + "bucketCount": 2, + "timezone": "UTC", + "createdAt": "", + "updatedAt": "" + } + ], + "user": { + "lastSyncedAt": "", + "healthKitLastSyncedAt": "", + "healthKitLastSyncTrigger": "foreground", + "healthKitLastBackgroundSyncAt": null + }, + "typeSync": [ + { + "userId": "", + "type": "ACTIVITY_STEPS", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": null + }, + { + "userId": "", + "type": "OXYGEN_SATURATION", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "PULSE", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "SLEEP_DURATION", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "WEIGHT", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + } + ] + } +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements-batch.03-background-day-total.json.snap b/tests/integration/routes/parity/__snapshots__/measurements-batch.03-background-day-total.json.snap new file mode 100644 index 000000000..4281d6adf --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements-batch.03-background-day-total.json.snap @@ -0,0 +1,382 @@ +{ + "status": 200, + "body": { + "data": { + "processed": 3, + "inserted": 0, + "updated": 3, + "duplicates": 0, + "failed": 0, + "skipped": [], + "entries": [ + { + "index": 0, + "status": "updated" + }, + { + "index": 1, + "status": "updated" + }, + { + "index": 2, + "status": "updated" + } + ] + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "measurement.batch.ingest" + }, + "meta": { + "write_bucket": "own", + "attempted": 1, + "inserted": 0, + "updated": 2, + "processed": 3, + "pr_detection_enqueued": true, + "pr_detection_silent": false, + "stats_unchanged": 1, + "duplicates": 0, + "skipped": 0, + "skipped_by_reason": {}, + "failed": 0, + "syncTrigger": "background", + "folded_window": 0, + "set_inserted": 0 + }, + "warnings": null, + "error": null + } + ], + "effects": [ + { + "effect": "enqueueReminderSatisfy", + "args": [ + "" + ] + }, + { + "effect": "enqueuePrDetection", + "args": [ + "", + { + "silent": false + } + ] + }, + { + "effect": "maybeEnqueueMorningRefresh", + "args": [ + "", + [] + ] + } + ], + "stored": { + "measurements": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T06:30:00.000Z", + "externalId": "uuid-mass-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "scale", + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 62, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:00:00.000Z", + "externalId": "uuid-hr-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "watch", + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 64, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:05:00.000Z", + "externalId": "uuid-hr-2", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 65, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:25:00.000Z", + "externalId": "uuid-hr-twice", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 71, + "valueMin": 66, + "valueMax": 79, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T08:10:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierHeartRate:2026-10-14T08:10:00.000Z", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 70, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T08:20:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierHeartRate:2026-10-14T08:20:00.000Z", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 0, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T01:00:00.000Z", + "externalId": "uuid-sleep-1", + "externalSourceVersion": null, + "sleepStage": "CORE", + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 0, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T02:30:00.000Z", + "externalId": "uuid-sleep-2", + "externalSourceVersion": null, + "sleepStage": "DEEP", + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 7250, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-12T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-12", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 8400, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-13T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-13", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "phone", + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 6300, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-14", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "OXYGEN_SATURATION", + "value": 97, + "valueMin": null, + "valueMax": null, + "unit": "%", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:10:00.000Z", + "externalId": "uuid-spo2-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + } + ], + "hours": [ + { + "id": "", + "userId": "", + "type": "ACTIVITY_STEPS", + "dateKey": "2026-10-14", + "hourlyCumulative": [], + "dayTotal": 0, + "sampleCount": 0, + "hourlyIncrements": [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 900, + 1450, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1 + ], + "basis": "PER_SAMPLE", + "bucketCount": 2, + "timezone": "UTC", + "createdAt": "", + "updatedAt": "" + } + ], + "user": { + "lastSyncedAt": "", + "healthKitLastSyncedAt": "", + "healthKitLastSyncTrigger": "background", + "healthKitLastBackgroundSyncAt": "" + }, + "typeSync": [ + { + "userId": "", + "type": "ACTIVITY_STEPS", + "lastReceivedAt": "", + "lastTrigger": "background", + "lastNewSampleAt": "", + "lastBackgroundAt": "", + "lastSeenDuplicateAt": null + }, + { + "userId": "", + "type": "OXYGEN_SATURATION", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "PULSE", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "SLEEP_DURATION", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "WEIGHT", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + } + ] + } +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements-batch.04-cross-source-merge.json.snap b/tests/integration/routes/parity/__snapshots__/measurements-batch.04-cross-source-merge.json.snap new file mode 100644 index 000000000..dee91ed1c --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements-batch.04-cross-source-merge.json.snap @@ -0,0 +1,408 @@ +{ + "status": 200, + "body": { + "data": { + "processed": 2, + "inserted": 1, + "updated": 0, + "duplicates": 1, + "failed": 0, + "skipped": [], + "entries": [ + { + "index": 0, + "status": "inserted" + }, + { + "index": 1, + "status": "duplicate", + "reason": "cross_source_merge" + } + ] + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "measurement.batch.ingest" + }, + "meta": { + "write_bucket": "own", + "merged": 1, + "processed": 2, + "pr_detection_enqueued": true, + "pr_detection_silent": false, + "inserted": 1, + "updated": 0, + "stats_unchanged": 0, + "duplicates": 1, + "skipped": 0, + "skipped_by_reason": {}, + "failed": 0, + "syncTrigger": null, + "folded_window": 0, + "set_inserted": 1 + }, + "warnings": null, + "error": null + } + ], + "effects": [ + { + "effect": "enqueueReminderSatisfy", + "args": [ + "" + ] + }, + { + "effect": "enqueuePrDetection", + "args": [ + "", + { + "silent": false + } + ] + }, + { + "effect": "maybeEnqueueMorningRefresh", + "args": [ + "", + [] + ] + }, + { + "effect": "emitDataArrival", + "args": [ + { + "userId": "", + "kind": "weight", + "newestSampleAt": "2026-10-15T06:30:00.000Z", + "insertedCount": 1, + "source": "batch" + } + ] + } + ], + "stored": { + "measurements": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T06:30:00.000Z", + "externalId": "uuid-mass-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "scale", + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "WEIGHT", + "value": 80.1, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-15T06:30:00.000Z", + "externalId": "manual-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 62, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:00:00.000Z", + "externalId": "uuid-hr-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "watch", + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 64, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:05:00.000Z", + "externalId": "uuid-hr-2", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 65, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:25:00.000Z", + "externalId": "uuid-hr-twice", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 71, + "valueMin": 66, + "valueMax": 79, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T08:10:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierHeartRate:2026-10-14T08:10:00.000Z", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 70, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T08:20:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierHeartRate:2026-10-14T08:20:00.000Z", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 0, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T01:00:00.000Z", + "externalId": "uuid-sleep-1", + "externalSourceVersion": null, + "sleepStage": "CORE", + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 0, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T02:30:00.000Z", + "externalId": "uuid-sleep-2", + "externalSourceVersion": null, + "sleepStage": "DEEP", + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 7250, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-12T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-12", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 8400, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-13T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-13", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "phone", + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 6300, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-14", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "OXYGEN_SATURATION", + "value": 97, + "valueMin": null, + "valueMax": null, + "unit": "%", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:10:00.000Z", + "externalId": "uuid-spo2-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + } + ], + "hours": [ + { + "id": "", + "userId": "", + "type": "ACTIVITY_STEPS", + "dateKey": "2026-10-14", + "hourlyCumulative": [], + "dayTotal": 0, + "sampleCount": 0, + "hourlyIncrements": [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 900, + 1450, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1 + ], + "basis": "PER_SAMPLE", + "bucketCount": 2, + "timezone": "UTC", + "createdAt": "", + "updatedAt": "" + } + ], + "user": { + "lastSyncedAt": "", + "healthKitLastSyncedAt": "", + "healthKitLastSyncTrigger": null, + "healthKitLastBackgroundSyncAt": "" + }, + "typeSync": [ + { + "userId": "", + "type": "ACTIVITY_STEPS", + "lastReceivedAt": "", + "lastTrigger": "background", + "lastNewSampleAt": "", + "lastBackgroundAt": "", + "lastSeenDuplicateAt": null + }, + { + "userId": "", + "type": "OXYGEN_SATURATION", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "PULSE", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "SLEEP_DURATION", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "WEIGHT", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + } + ] + } +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements-batch.05-backfill.json.snap b/tests/integration/routes/parity/__snapshots__/measurements-batch.05-backfill.json.snap new file mode 100644 index 000000000..c9febc322 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements-batch.05-backfill.json.snap @@ -0,0 +1,446 @@ +{ + "status": 200, + "body": { + "data": { + "processed": 2, + "inserted": 2, + "updated": 0, + "duplicates": 0, + "failed": 0, + "skipped": [], + "entries": [ + { + "index": 0, + "status": "inserted" + }, + { + "index": 1, + "status": "inserted" + } + ] + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "measurement.batch.ingest" + }, + "meta": { + "write_bucket": "own", + "pr_detection_enqueued": true, + "pr_detection_silent": false, + "processed": 2, + "inserted": 2, + "updated": 0, + "stats_unchanged": 0, + "duplicates": 0, + "skipped": 0, + "skipped_by_reason": {}, + "failed": 0, + "syncTrigger": "backfill", + "folded_window": 0, + "set_inserted": 2 + }, + "warnings": null, + "error": null + } + ], + "effects": [ + { + "effect": "enqueueReminderSatisfy", + "args": [ + "" + ] + }, + { + "effect": "enqueuePrDetection", + "args": [ + "", + { + "silent": false + } + ] + }, + { + "effect": "maybeEnqueueMorningRefresh", + "args": [ + "", + [] + ] + } + ], + "stored": { + "measurements": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T06:30:00.000Z", + "externalId": "uuid-mass-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "scale", + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "WEIGHT", + "value": 80.1, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-15T06:30:00.000Z", + "externalId": "manual-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 58, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-09-01T07:00:00.000Z", + "externalId": "uuid-hr-old-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 62, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:00:00.000Z", + "externalId": "uuid-hr-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "watch", + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 64, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:05:00.000Z", + "externalId": "uuid-hr-2", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 65, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:25:00.000Z", + "externalId": "uuid-hr-twice", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 71, + "valueMin": 66, + "valueMax": 79, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T08:10:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierHeartRate:2026-10-14T08:10:00.000Z", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 70, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T08:20:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierHeartRate:2026-10-14T08:20:00.000Z", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 0, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T01:00:00.000Z", + "externalId": "uuid-sleep-1", + "externalSourceVersion": null, + "sleepStage": "CORE", + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 0, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T02:30:00.000Z", + "externalId": "uuid-sleep-2", + "externalSourceVersion": null, + "sleepStage": "DEEP", + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 11200, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-09-01T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-09-01", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 7250, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-12T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-12", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 8400, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-13T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-13", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "phone", + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 6300, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-14", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "OXYGEN_SATURATION", + "value": 97, + "valueMin": null, + "valueMax": null, + "unit": "%", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:10:00.000Z", + "externalId": "uuid-spo2-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + } + ], + "hours": [ + { + "id": "", + "userId": "", + "type": "ACTIVITY_STEPS", + "dateKey": "2026-10-14", + "hourlyCumulative": [], + "dayTotal": 0, + "sampleCount": 0, + "hourlyIncrements": [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 900, + 1450, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1 + ], + "basis": "PER_SAMPLE", + "bucketCount": 2, + "timezone": "UTC", + "createdAt": "", + "updatedAt": "" + } + ], + "user": { + "lastSyncedAt": "", + "healthKitLastSyncedAt": "", + "healthKitLastSyncTrigger": null, + "healthKitLastBackgroundSyncAt": "" + }, + "typeSync": [ + { + "userId": "", + "type": "ACTIVITY_STEPS", + "lastReceivedAt": "", + "lastTrigger": "background", + "lastNewSampleAt": "", + "lastBackgroundAt": "", + "lastSeenDuplicateAt": null + }, + { + "userId": "", + "type": "OXYGEN_SATURATION", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "PULSE", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "SLEEP_DURATION", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "WEIGHT", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "backfill:ACTIVITY_STEPS", + "lastReceivedAt": "", + "lastTrigger": null, + "lastNewSampleAt": null, + "lastBackgroundAt": null, + "lastSeenDuplicateAt": null + }, + { + "userId": "", + "type": "backfill:PULSE", + "lastReceivedAt": "", + "lastTrigger": null, + "lastNewSampleAt": null, + "lastBackgroundAt": null, + "lastSeenDuplicateAt": null + } + ] + } +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements-batch.06-too-large.json.snap b/tests/integration/routes/parity/__snapshots__/measurements-batch.06-too-large.json.snap new file mode 100644 index 000000000..2d8937e16 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements-batch.06-too-large.json.snap @@ -0,0 +1,395 @@ +{ + "status": 422, + "body": { + "data": null, + "error": "Batch exceeds the 500-entry limit", + "meta": { + "errorCode": "measurement.batch.too_large", + "requestId": "" + } + }, + "events": [ + { + "status": 422, + "action": null, + "meta": { + "write_bucket": "own" + }, + "warnings": null, + "error": null + } + ], + "effects": [], + "stored": { + "measurements": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T06:30:00.000Z", + "externalId": "uuid-mass-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "scale", + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "WEIGHT", + "value": 80.1, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-15T06:30:00.000Z", + "externalId": "manual-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 58, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-09-01T07:00:00.000Z", + "externalId": "uuid-hr-old-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 62, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:00:00.000Z", + "externalId": "uuid-hr-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "watch", + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 64, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:05:00.000Z", + "externalId": "uuid-hr-2", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 65, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:25:00.000Z", + "externalId": "uuid-hr-twice", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 71, + "valueMin": 66, + "valueMax": 79, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T08:10:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierHeartRate:2026-10-14T08:10:00.000Z", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 70, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T08:20:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierHeartRate:2026-10-14T08:20:00.000Z", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 0, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T01:00:00.000Z", + "externalId": "uuid-sleep-1", + "externalSourceVersion": null, + "sleepStage": "CORE", + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 0, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T02:30:00.000Z", + "externalId": "uuid-sleep-2", + "externalSourceVersion": null, + "sleepStage": "DEEP", + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 11200, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-09-01T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-09-01", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 7250, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-12T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-12", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 8400, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-13T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-13", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "phone", + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 6300, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-14", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "OXYGEN_SATURATION", + "value": 97, + "valueMin": null, + "valueMax": null, + "unit": "%", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:10:00.000Z", + "externalId": "uuid-spo2-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + } + ], + "hours": [ + { + "id": "", + "userId": "", + "type": "ACTIVITY_STEPS", + "dateKey": "2026-10-14", + "hourlyCumulative": [], + "dayTotal": 0, + "sampleCount": 0, + "hourlyIncrements": [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 900, + 1450, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1 + ], + "basis": "PER_SAMPLE", + "bucketCount": 2, + "timezone": "UTC", + "createdAt": "", + "updatedAt": "" + } + ], + "user": { + "lastSyncedAt": "", + "healthKitLastSyncedAt": "", + "healthKitLastSyncTrigger": null, + "healthKitLastBackgroundSyncAt": "" + }, + "typeSync": [ + { + "userId": "", + "type": "ACTIVITY_STEPS", + "lastReceivedAt": "", + "lastTrigger": "background", + "lastNewSampleAt": "", + "lastBackgroundAt": "", + "lastSeenDuplicateAt": null + }, + { + "userId": "", + "type": "OXYGEN_SATURATION", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "PULSE", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "SLEEP_DURATION", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "WEIGHT", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "backfill:ACTIVITY_STEPS", + "lastReceivedAt": "", + "lastTrigger": null, + "lastNewSampleAt": null, + "lastBackgroundAt": null, + "lastSeenDuplicateAt": null + }, + { + "userId": "", + "type": "backfill:PULSE", + "lastReceivedAt": "", + "lastTrigger": null, + "lastNewSampleAt": null, + "lastBackgroundAt": null, + "lastSeenDuplicateAt": null + } + ] + } +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements-batch.07-invalid.json.snap b/tests/integration/routes/parity/__snapshots__/measurements-batch.07-invalid.json.snap new file mode 100644 index 000000000..bef5fb961 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements-batch.07-invalid.json.snap @@ -0,0 +1,419 @@ +{ + "status": 422, + "body": { + "data": null, + "error": "Invalid input: expected number, received string", + "details": { + "issues": [ + { + "path": "entries.0.value", + "code": "invalid_type", + "message": "Invalid input: expected number, received string" + }, + { + "path": "entries.0.startDate", + "code": "invalid_type", + "message": "Invalid input: expected string, received undefined" + }, + { + "path": "entries.0.endDate", + "code": "invalid_type", + "message": "Invalid input: expected string, received undefined" + }, + { + "path": "entries.0.externalId", + "code": "invalid_type", + "message": "Invalid input: expected string, received undefined" + } + ] + }, + "meta": { + "errorCode": "measurement.batch.invalid", + "requestId": "" + } + }, + "events": [ + { + "status": 422, + "action": null, + "meta": { + "write_bucket": "own" + }, + "warnings": null, + "error": null + } + ], + "effects": [], + "stored": { + "measurements": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T06:30:00.000Z", + "externalId": "uuid-mass-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "scale", + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "WEIGHT", + "value": 80.1, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-15T06:30:00.000Z", + "externalId": "manual-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 58, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-09-01T07:00:00.000Z", + "externalId": "uuid-hr-old-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 62, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:00:00.000Z", + "externalId": "uuid-hr-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "watch", + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 64, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:05:00.000Z", + "externalId": "uuid-hr-2", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 65, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:25:00.000Z", + "externalId": "uuid-hr-twice", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 71, + "valueMin": 66, + "valueMax": 79, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T08:10:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierHeartRate:2026-10-14T08:10:00.000Z", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 70, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T08:20:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierHeartRate:2026-10-14T08:20:00.000Z", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 0, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T01:00:00.000Z", + "externalId": "uuid-sleep-1", + "externalSourceVersion": null, + "sleepStage": "CORE", + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 0, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T02:30:00.000Z", + "externalId": "uuid-sleep-2", + "externalSourceVersion": null, + "sleepStage": "DEEP", + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 11200, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-09-01T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-09-01", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 7250, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-12T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-12", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 8400, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-13T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-13", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "phone", + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 6300, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-14", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "OXYGEN_SATURATION", + "value": 97, + "valueMin": null, + "valueMax": null, + "unit": "%", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:10:00.000Z", + "externalId": "uuid-spo2-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + } + ], + "hours": [ + { + "id": "", + "userId": "", + "type": "ACTIVITY_STEPS", + "dateKey": "2026-10-14", + "hourlyCumulative": [], + "dayTotal": 0, + "sampleCount": 0, + "hourlyIncrements": [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 900, + 1450, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1 + ], + "basis": "PER_SAMPLE", + "bucketCount": 2, + "timezone": "UTC", + "createdAt": "", + "updatedAt": "" + } + ], + "user": { + "lastSyncedAt": "", + "healthKitLastSyncedAt": "", + "healthKitLastSyncTrigger": null, + "healthKitLastBackgroundSyncAt": "" + }, + "typeSync": [ + { + "userId": "", + "type": "ACTIVITY_STEPS", + "lastReceivedAt": "", + "lastTrigger": "background", + "lastNewSampleAt": "", + "lastBackgroundAt": "", + "lastSeenDuplicateAt": null + }, + { + "userId": "", + "type": "OXYGEN_SATURATION", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "PULSE", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "SLEEP_DURATION", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "WEIGHT", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "backfill:ACTIVITY_STEPS", + "lastReceivedAt": "", + "lastTrigger": null, + "lastNewSampleAt": null, + "lastBackgroundAt": null, + "lastSeenDuplicateAt": null + }, + { + "userId": "", + "type": "backfill:PULSE", + "lastReceivedAt": "", + "lastTrigger": null, + "lastNewSampleAt": null, + "lastBackgroundAt": null, + "lastSeenDuplicateAt": null + } + ] + } +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements-batch.08a-bridge-with-source.json.snap b/tests/integration/routes/parity/__snapshots__/measurements-batch.08a-bridge-with-source.json.snap new file mode 100644 index 000000000..03a0d1cbc --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements-batch.08a-bridge-with-source.json.snap @@ -0,0 +1,398 @@ +{ + "status": 422, + "body": { + "data": null, + "error": "This credential resolves the source itself; omit the field", + "meta": { + "errorCode": "measurement.batch.source_not_permitted", + "requestId": "" + } + }, + "events": [ + { + "status": 422, + "action": { + "name": "measurement.batch.ingest" + }, + "meta": { + "write_bucket": "own", + "outcome": "source_not_permitted" + }, + "warnings": null, + "error": null + } + ], + "effects": [], + "stored": { + "measurements": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T06:30:00.000Z", + "externalId": "uuid-mass-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "scale", + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "WEIGHT", + "value": 80.1, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-15T06:30:00.000Z", + "externalId": "manual-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 58, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-09-01T07:00:00.000Z", + "externalId": "uuid-hr-old-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 62, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:00:00.000Z", + "externalId": "uuid-hr-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "watch", + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 64, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:05:00.000Z", + "externalId": "uuid-hr-2", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 65, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:25:00.000Z", + "externalId": "uuid-hr-twice", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 71, + "valueMin": 66, + "valueMax": 79, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T08:10:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierHeartRate:2026-10-14T08:10:00.000Z", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 70, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T08:20:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierHeartRate:2026-10-14T08:20:00.000Z", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 0, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T01:00:00.000Z", + "externalId": "uuid-sleep-1", + "externalSourceVersion": null, + "sleepStage": "CORE", + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 0, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T02:30:00.000Z", + "externalId": "uuid-sleep-2", + "externalSourceVersion": null, + "sleepStage": "DEEP", + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 11200, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-09-01T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-09-01", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 7250, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-12T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-12", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 8400, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-13T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-13", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "phone", + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 6300, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-14", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "OXYGEN_SATURATION", + "value": 97, + "valueMin": null, + "valueMax": null, + "unit": "%", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:10:00.000Z", + "externalId": "uuid-spo2-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + } + ], + "hours": [ + { + "id": "", + "userId": "", + "type": "ACTIVITY_STEPS", + "dateKey": "2026-10-14", + "hourlyCumulative": [], + "dayTotal": 0, + "sampleCount": 0, + "hourlyIncrements": [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 900, + 1450, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1 + ], + "basis": "PER_SAMPLE", + "bucketCount": 2, + "timezone": "UTC", + "createdAt": "", + "updatedAt": "" + } + ], + "user": { + "lastSyncedAt": "", + "healthKitLastSyncedAt": "", + "healthKitLastSyncTrigger": null, + "healthKitLastBackgroundSyncAt": "" + }, + "typeSync": [ + { + "userId": "", + "type": "ACTIVITY_STEPS", + "lastReceivedAt": "", + "lastTrigger": "background", + "lastNewSampleAt": "", + "lastBackgroundAt": "", + "lastSeenDuplicateAt": null + }, + { + "userId": "", + "type": "OXYGEN_SATURATION", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "PULSE", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "SLEEP_DURATION", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "WEIGHT", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "backfill:ACTIVITY_STEPS", + "lastReceivedAt": "", + "lastTrigger": null, + "lastNewSampleAt": null, + "lastBackgroundAt": null, + "lastSeenDuplicateAt": null + }, + { + "userId": "", + "type": "backfill:PULSE", + "lastReceivedAt": "", + "lastTrigger": null, + "lastNewSampleAt": null, + "lastBackgroundAt": null, + "lastSeenDuplicateAt": null + } + ] + } +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements-batch.08b-bridge.json.snap b/tests/integration/routes/parity/__snapshots__/measurements-batch.08b-bridge.json.snap new file mode 100644 index 000000000..ad53e941d --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements-batch.08b-bridge.json.snap @@ -0,0 +1,483 @@ +{ + "status": 200, + "body": { + "data": { + "processed": 2, + "inserted": 1, + "updated": 0, + "duplicates": 0, + "failed": 0, + "skipped": [ + { + "index": 1, + "reason": "hour_bucket_unsupported_type" + } + ], + "entries": [ + { + "index": 0, + "status": "inserted" + }, + { + "index": 1, + "status": "skipped", + "reason": "hour_bucket_unsupported_type" + } + ] + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "measurement.batch.ingest" + }, + "meta": { + "write_bucket": "own", + "pr_detection_enqueued": true, + "pr_detection_silent": false, + "processed": 2, + "inserted": 1, + "updated": 0, + "stats_unchanged": 0, + "duplicates": 0, + "skipped": 1, + "skipped_by_reason": { + "hour_bucket_unsupported_type": 1 + }, + "failed": 0, + "syncTrigger": "foreground", + "folded_window": 0, + "set_inserted": 1 + }, + "warnings": null, + "error": null + } + ], + "effects": [ + { + "effect": "enqueueReminderSatisfy", + "args": [ + "" + ] + }, + { + "effect": "enqueuePrDetection", + "args": [ + "", + { + "silent": false + } + ] + }, + { + "effect": "maybeEnqueueMorningRefresh", + "args": [ + "", + [] + ] + }, + { + "effect": "emitDataArrival", + "args": [ + { + "userId": "", + "kind": "weight", + "newestSampleAt": "2026-10-15T07:00:00.000Z", + "insertedCount": 1, + "source": "batch" + } + ] + } + ], + "stored": { + "measurements": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T06:30:00.000Z", + "externalId": "uuid-mass-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "scale", + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "WEIGHT", + "value": 80.1, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-15T06:30:00.000Z", + "externalId": "manual-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "WEIGHT", + "value": 80, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "EXTERNAL", + "measuredAt": "2026-10-15T07:00:00.000Z", + "externalId": "bridge-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 58, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-09-01T07:00:00.000Z", + "externalId": "uuid-hr-old-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 62, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:00:00.000Z", + "externalId": "uuid-hr-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "watch", + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 64, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:05:00.000Z", + "externalId": "uuid-hr-2", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 65, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:25:00.000Z", + "externalId": "uuid-hr-twice", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 71, + "valueMin": 66, + "valueMax": 79, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T08:10:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierHeartRate:2026-10-14T08:10:00.000Z", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "PULSE", + "value": 70, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T08:20:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierHeartRate:2026-10-14T08:20:00.000Z", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 0, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T01:00:00.000Z", + "externalId": "uuid-sleep-1", + "externalSourceVersion": null, + "sleepStage": "CORE", + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 0, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T02:30:00.000Z", + "externalId": "uuid-sleep-2", + "externalSourceVersion": null, + "sleepStage": "DEEP", + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 11200, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-09-01T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-09-01", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 7250, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-12T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-12", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 8400, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-13T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-13", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "phone", + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "ACTIVITY_STEPS", + "value": 6300, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T18:00:00.000Z", + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-14", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "deletedAt": null + }, + { + "id": "", + "type": "OXYGEN_SATURATION", + "value": 97, + "valueMin": null, + "valueMax": null, + "unit": "%", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T07:10:00.000Z", + "externalId": "uuid-spo2-1", + "externalSourceVersion": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "aggregationProvenance": null, + "deletedAt": null + } + ], + "hours": [ + { + "id": "", + "userId": "", + "type": "ACTIVITY_STEPS", + "dateKey": "2026-10-14", + "hourlyCumulative": [], + "dayTotal": 0, + "sampleCount": 0, + "hourlyIncrements": [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 900, + 1450, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1 + ], + "basis": "PER_SAMPLE", + "bucketCount": 2, + "timezone": "UTC", + "createdAt": "", + "updatedAt": "" + } + ], + "user": { + "lastSyncedAt": "", + "healthKitLastSyncedAt": "", + "healthKitLastSyncTrigger": null, + "healthKitLastBackgroundSyncAt": "" + }, + "typeSync": [ + { + "userId": "", + "type": "ACTIVITY_STEPS", + "lastReceivedAt": "", + "lastTrigger": "background", + "lastNewSampleAt": "", + "lastBackgroundAt": "", + "lastSeenDuplicateAt": null + }, + { + "userId": "", + "type": "OXYGEN_SATURATION", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "PULSE", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "SLEEP_DURATION", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "WEIGHT", + "lastReceivedAt": "", + "lastTrigger": "foreground", + "lastNewSampleAt": "", + "lastBackgroundAt": null, + "lastSeenDuplicateAt": "" + }, + { + "userId": "", + "type": "backfill:ACTIVITY_STEPS", + "lastReceivedAt": "", + "lastTrigger": null, + "lastNewSampleAt": null, + "lastBackgroundAt": null, + "lastSeenDuplicateAt": null + }, + { + "userId": "", + "type": "backfill:PULSE", + "lastReceivedAt": "", + "lastTrigger": null, + "lastNewSampleAt": null, + "lastBackgroundAt": null, + "lastSeenDuplicateAt": null + } + ] + } +} diff --git a/tests/integration/routes/parity/measurements-batch.parity.test.ts b/tests/integration/routes/parity/measurements-batch.parity.test.ts new file mode 100644 index 000000000..2cb309931 --- /dev/null +++ b/tests/integration/routes/parity/measurements-batch.parity.test.ts @@ -0,0 +1,437 @@ +/** + * Parity for `POST /api/measurements/batch` while its ingest moves into + * `src/lib/measurements/batch-ingest/`. + * + * One account walks through the cases the route tells apart, in order, each + * request seeing what the earlier ones stored: a first sync of mixed + * entries (fresh samples, a day total, a heart-rate bucket with its spread, + * sleep stages, hourly sums, and every per-entry refusal), the same sync + * again, a re-computed day total from the background, a hand-entered + * reading the phone also mirrored, a history catch-up, the two whole-batch + * refusals, and a narrow bridge token with and without a `source`. After + * each request the snapshot holds the response, the request's wide event, + * the stored measurements and hourly profile, and the account's sync stamps. + * + * The clock is pinned (`Date` only), so the snapshot is the same on every + * day it runs. + */ +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; + +import { POST } from "@/app/api/measurements/batch/route"; +import { MEASUREMENTS_WRITE_SCOPE } from "@/lib/measurements/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); + }, + }; +}); + +// The queue and arrival tails the route hands work to, recorded instead of +// run (see `effectRecorder`). +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/daily/morning-refresh-trigger", async (importOriginal) => { + const actual = + await importOriginal< + typeof import("@/lib/daily/morning-refresh-trigger") + >(); + const { effectRecorder } = await import("../../../kit/parity"); + return { + ...actual, + maybeEnqueueMorningRefresh: async (...args: unknown[]) => { + effectRecorder.record("maybeEnqueueMorningRefresh", args); + }, + }; +}); +vi.mock("@/lib/jobs/reminder-satisfy", async (importOriginal) => { + const actual = + await importOriginal(); + const { effectRecorder } = await import("../../../kit/parity"); + return { + ...actual, + enqueueReminderSatisfy: async (...args: unknown[]) => { + effectRecorder.record("enqueueReminderSatisfy", 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"); +const HR = "HKQuantityTypeIdentifierHeartRate"; +const STEPS = "HKQuantityTypeIdentifierStepCount"; +const MASS = "HKQuantityTypeIdentifierBodyMass"; +const SPO2 = "HKQuantityTypeIdentifierOxygenSaturation"; +const SLEEP = "HKCategoryTypeIdentifierSleepAnalysis"; + +type Entry = Record; + +function sample( + hkIdentifier: string, + externalId: string, + at: string, + value: number, + unit: string, + extra: Entry = {}, +): Entry { + return { + hkIdentifier, + value, + unit, + startDate: at, + endDate: at, + externalId, + ...extra, + }; +} + +function dayTotal(day: string, value: number, extra: Entry = {}): Entry { + return { + hkIdentifier: STEPS, + value, + unit: "count", + startDate: `${day}T00:00:00.000Z`, + endDate: `${day}T18:00:00.000Z`, + externalId: `stats:${STEPS}:${day}`, + ...extra, + }; +} + +function hourSum(day: string, hour: number, value: number): Entry { + const hh = String(hour).padStart(2, "0"); + const start = `${day}T${hh}:00:00.000Z`; + return { + hkIdentifier: STEPS, + value, + unit: "count", + startDate: start, + endDate: new Date(Date.parse(start) + 3_600_000).toISOString(), + externalId: `stats:${STEPS}:${day}T${hh}`, + }; +} + +function sleep(externalId: string, from: string, to: string, stage: number) { + return { + hkIdentifier: SLEEP, + value: 0, + unit: "min", + startDate: from, + endDate: to, + externalId, + sleepStage: stage, + }; +} + +const FIRST_SYNC: Entry[] = [ + sample(MASS, "uuid-mass-1", "2026-10-14T06:30:00.000Z", 80.4, "kg", { + deviceType: "scale", + }), + sample(HR, "uuid-hr-1", "2026-10-14T07:00:00.000Z", 62, "count/min", { + deviceType: "watch", + }), + sample(HR, "uuid-hr-2", "2026-10-14T07:05:00.000Z", 64, "count/min"), + sample(SPO2, "uuid-spo2-1", "2026-10-14T07:10:00.000Z", 0.97, "%"), + dayTotal("2026-10-13", 8_400, { deviceType: "phone" }), + dayTotal("2026-10-14", 5_100), + sample( + HR, + `stats:${HR}:2026-10-14T08:10:00.000Z`, + "2026-10-14T08:10:00.000Z", + 71, + "count/min", + { valueMin: 66, valueMax: 79 }, + ), + // A spread that does not bracket the mean is dropped, the mean kept. + sample( + HR, + `stats:${HR}:2026-10-14T08:20:00.000Z`, + "2026-10-14T08:20:00.000Z", + 70, + "count/min", + { valueMin: 72, valueMax: 75 }, + ), + sleep( + "uuid-sleep-1", + "2026-10-13T22:00:00.000Z", + "2026-10-14T01:00:00.000Z", + 3, + ), + sleep( + "uuid-sleep-2", + "2026-10-14T01:00:00.000Z", + "2026-10-14T02:30:00.000Z", + 4, + ), + hourSum("2026-10-14", 8, 900), + hourSum("2026-10-14", 9, 1_300), + // Refused per entry. + sample( + "HKQuantityTypeIdentifierNotAThing", + "uuid-x-1", + "2026-10-14T07:00:00.000Z", + 1, + "count", + ), + sample(HR, "uuid-hr-range", "2026-10-14T07:15:00.000Z", 900, "count/min"), + sample(HR, "uuid-hr-future", "2027-03-01T07:15:00.000Z", 61, "count/min"), + sample( + HR, + `stats:${HR}:2026-10-14T08:13:00.000Z`, + "2026-10-14T08:13:00.000Z", + 70, + "count/min", + ), + sample( + HR, + "", + "2026-10-14T07:20:00.000Z", + 63, + "count/min", + ), + // The same sample twice in one batch, and a day total sent twice. + sample(HR, "uuid-hr-twice", "2026-10-14T07:25:00.000Z", 65, "count/min"), + sample(HR, "uuid-hr-twice", "2026-10-14T07:25:00.000Z", 65, "count/min"), + dayTotal("2026-10-12", 7_000), + dayTotal("2026-10-12", 7_250), +]; + +const MEASUREMENT_SELECT = { + id: true, + type: true, + value: true, + valueMin: true, + valueMax: true, + unit: true, + source: true, + measuredAt: true, + externalId: true, + externalSourceVersion: true, + sleepStage: true, + rhythmClassification: true, + deviceType: true, + aggregationProvenance: true, + deletedAt: true, +} as const; + +let userId: string; +let session: RouteAuth; + +async function state() { + const [measurements, hours, user, typeSync] = await Promise.all([ + prisma.measurement.findMany({ + where: { userId }, + select: MEASUREMENT_SELECT, + orderBy: [ + { type: "asc" }, + { measuredAt: "asc" }, + { source: "asc" }, + { externalId: "asc" }, + ], + }), + prisma.intradayCumulativeProfile.findMany({ + where: { userId }, + orderBy: [{ type: "asc" }, { dateKey: "asc" }], + }), + prisma.user.findUniqueOrThrow({ + where: { id: userId }, + select: { + lastSyncedAt: true, + healthKitLastSyncedAt: true, + healthKitLastSyncTrigger: true, + healthKitLastBackgroundSyncAt: true, + }, + }), + prisma.healthKitTypeSync.findMany({ + where: { userId }, + orderBy: { type: "asc" }, + }), + ]); + return { measurements, hours, user, typeSync }; +} + +async function step( + name: string, + body: unknown, + auth: RouteAuth = session, +): Promise { + wideEventRecorder.take(); + effectRecorder.take(); + const res = await callRoute(POST, { + method: "POST", + route: "/api/measurements/batch", + body, + auth, + }); + const events = wideEventRecorder.take(); + expect(events.length).toBeGreaterThan(0); + await expectParity( + `measurements-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/measurements/batch parity", () => { + it("01 a first sync of mixed entries", async () => { + await step("01-first-sync", { + entries: FIRST_SYNC, + syncTrigger: "foreground", + }); + }); + + it("02 the same sync again", async () => { + await step("02-same-sync-again", { + entries: FIRST_SYNC, + syncTrigger: "foreground", + }); + }); + + it("03 a re-computed day total from the background", async () => { + await step("03-background-day-total", { + entries: [ + dayTotal("2026-10-14", 6_300), + dayTotal("2026-10-13", 8_400, { deviceType: "phone" }), + hourSum("2026-10-14", 9, 1_450), + ], + syncTrigger: "background", + }); + }); + + it("04 a hand-entered reading the phone also mirrored", async () => { + await step("04-cross-source-merge", { + entries: [ + sample(MASS, "manual-1", "2026-10-15T06:30:00.000Z", 80.1, "kg", { + source: "MANUAL", + }), + sample( + MASS, + "uuid-mass-mirror", + "2026-10-15T06:30:01.000Z", + 80.1, + "kg", + ), + ], + }); + }); + + it("05 a history catch-up", async () => { + await step("05-backfill", { + entries: [ + sample( + HR, + "uuid-hr-old-1", + "2026-09-01T07:00:00.000Z", + 58, + "count/min", + ), + dayTotal("2026-09-01", 11_200), + ], + syncTrigger: "backfill", + }); + }); + + it("06 a batch over the entry limit", async () => { + await step("06-too-large", { + entries: Array.from({ length: 501 }, (_, i) => + sample( + HR, + `uuid-bulk-${i}`, + "2026-10-14T10:00:00.000Z", + 60, + "count/min", + ), + ), + }); + }); + + it("07 a batch the schema refuses", async () => { + await step("07-invalid", { + entries: [{ hkIdentifier: HR, value: "fast", unit: "count/min" }], + }); + }); + + it("08 a bridge token naming a source, and one that does not", async () => { + const bridge = await makeToken( + { id: userId }, + { permissions: [MEASUREMENTS_WRITE_SCOPE] }, + ); + await step( + "08a-bridge-with-source", + { + entries: [ + sample(MASS, "bridge-1", "2026-10-15T07:00:00.000Z", 80.0, "kg", { + source: "APPLE_HEALTH", + }), + ], + }, + bridge, + ); + await step( + "08b-bridge", + { + entries: [ + sample(MASS, "bridge-1", "2026-10-15T07:00:00.000Z", 80.0, "kg"), + hourSum("2026-10-15", 6, 400), + ], + syncTrigger: "foreground", + }, + bridge, + ); + }); +}); diff --git a/tests/kit/parity.ts b/tests/kit/parity.ts new file mode 100644 index 000000000..7e2bf2d58 --- /dev/null +++ b/tests/kit/parity.ts @@ -0,0 +1,160 @@ +/** + * Response parity for a route whose code moves: record what the route + * answers, stores and reports before the move, and compare after it. + * + * A parity test drives a route through `callRoute` across the cases it + * distinguishes, and pins three things per case in a committed snapshot + * under `tests/integration/routes/parity/__snapshots__/`: + * + * - the response (status, envelope); + * - what the request left in the database, read back by the test; + * - the wide event the request emitted: its action and meta, which is what + * the operator dashboards read. + * + * The snapshot is written on the code before the move, committed on its own, + * and must come out byte-identical after it. Values that differ between two + * runs of the same code (generated ids, server timestamps, request ids, + * durations) are replaced by stable placeholders first, so the comparison + * sees only what the code decides. Ids are numbered by first appearance, + * so two references to the same row still read as the same placeholder. + */ +import { expect } from "vitest"; + +import type { WideEvent } from "@/lib/logging/types"; + +/** Generated identifiers: Prisma `cuid()` values and UUIDs. */ +const GENERATED_ID = + /^(c[a-z0-9]{24}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/; +const ISO_INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,6})?Z$/; + +/** + * Keys whose values the clock or a random source decides on every run. Their + * presence is kept, their value is not. + */ +const VOLATILE_KEYS = new Set([ + "requestId", + "request_id", + "createdAt", + "updatedAt", + "syncVersion", + "duration_ms", + "durationMs", + "timestamp", + "lastUsedAt", +]); + +export interface StableOptions { + /** + * Instants inside `[since, until]` are server clock readings (a row's + * `createdAt`, a stamp the route wrote) and read as ``. Instants the + * test sent, which lie outside that window, are kept as they are. + */ + since: Date; + until?: Date; + /** Further keys to blank, for a route with a volatile field of its own. */ + volatileKeys?: readonly string[]; +} + +/** A copy of `value` with the run-dependent parts replaced. */ +export function stable(value: unknown, options: StableOptions): unknown { + const ids = new Map(); + const until = (options.until ?? new Date(Date.now() + 60_000)).getTime(); + const since = options.since.getTime(); + const volatile = new Set([...VOLATILE_KEYS, ...(options.volatileKeys ?? [])]); + + const walk = (node: unknown, key: string | null): unknown => { + if ( + key !== null && + volatile.has(key) && + node !== null && + node !== undefined + ) { + return `<${key}>`; + } + if (node instanceof Date) node = node.toISOString(); + if (typeof node === "string") { + if (GENERATED_ID.test(node)) { + let label = ids.get(node); + if (!label) { + label = ``; + ids.set(node, label); + } + return label; + } + if (ISO_INSTANT.test(node)) { + const t = Date.parse(node); + if (t >= since && t <= until) return ""; + } + return node; + } + if (typeof node === "bigint") return `${node}n`; + if (Array.isArray(node)) return node.map((item) => walk(item, null)); + if (node && typeof node === "object") { + const out: Record = {}; + for (const [k, v] of Object.entries(node)) out[k] = walk(v, k); + return out; + } + return node; + }; + return walk(value, null); +} + +/** + * The wide events requests emitted, collected in order. Install with + * `vi.mock("@/lib/logging/transports", wideEventRecorder.mockFactory)` in + * the test file, then read `wideEventRecorder.take()` after each call. + */ +export const wideEventRecorder = { + events: [] as WideEvent[], + /** The events since the last call, emptied. */ + take(): WideEvent[] { + return this.events.splice(0, this.events.length); + }, +}; + +/** + * Calls the route handed to a queue or another fire-and-forget tail (a job + * enqueue, a data-arrival emit), recorded in order with their arguments. A + * parity test replaces those collaborators with recorders: the snapshot then + * shows what the route asked for, and the request's wide event no longer + * depends on whether a tail it did not await happened to finish first. + */ +export const effectRecorder = { + calls: [] as Array<{ effect: string; args: unknown[] }>, + record(effect: string, args: unknown[]): void { + this.calls.push({ effect, args }); + }, + /** The calls since the last take, emptied. */ + take(): Array<{ effect: string; args: unknown[] }> { + return this.calls.splice(0, this.calls.length); + }, +}; + +/** What of a wide event a parity snapshot keeps: what the code decided. */ +export function eventFacts(event: WideEvent): unknown { + return { + status: event.http?.status ?? null, + action: event.action ?? null, + meta: event.meta ?? null, + warnings: event.warnings ?? null, + error: event.error + ? { type: event.error.type, message: event.error.message } + : null, + }; +} + +/** + * Compare `value` with the committed snapshot `name`, a file under + * `__snapshots__/` beside the calling test (parity tests live in + * `tests/integration/routes/parity/`). A missing snapshot is written outside + * CI (the recording run); in CI a missing one fails, and a present one must + * match everywhere. + */ +export async function expectParity( + name: string, + value: unknown, +): Promise { + await expect(`${JSON.stringify(value, null, 2)}\n`).toMatchFileSnapshot( + `./__snapshots__/${name}.json.snap`, + ); +}