From 8d48733c6aab743ec999b97d4cf2e3fa49156ad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 23 Oct 2026 11:47:09 +0200 Subject: [PATCH] test(measurements): pin the measurement list and create before moving them into services A parity walk over GET and POST /api/measurements: every create arm (single, combined, named sleep stage) with its duplicate and refusal, a bridge token, a delegated write, and every list mode. Sealed bytes are recorded by length, since each write carries a fresh nonce. --- .../measurements.get-01-page.json.snap | 282 ++++++++++++ .../measurements.get-02-type-sorted.json.snap | 76 ++++ .../measurements.get-03-filtered.json.snap | 75 ++++ .../measurements.get-04-sleep.json.snap | 54 +++ .../measurements.get-05-drill-down.json.snap | 57 +++ .../measurements.get-06-day-grouped.json.snap | 69 +++ ...easurements.get-07-rollup-series.json.snap | 51 +++ ...asurements.get-08-live-aggregate.json.snap | 40 ++ .../measurements.get-09-invalid.json.snap | 40 ++ .../measurements.post-01-single.json.snap | 129 ++++++ ...rements.post-02-single-duplicate.json.snap | 38 ++ ...surements.post-03-single-invalid.json.snap | 52 +++ ...surements.post-04-single-glucose.json.snap | 145 ++++++ .../measurements.post-05-batch.json.snap | 305 +++++++++++++ ...urements.post-06-batch-duplicate.json.snap | 98 +++++ ...asurements.post-07-batch-invalid.json.snap | 112 +++++ ...measurements.post-08-sleep-stage.json.snap | 189 ++++++++ ...ements.post-09-sleep-stage-again.json.snap | 141 ++++++ ...ments.post-10-sleep-stage-mirror.json.snap | 141 ++++++ ...ents.post-11-sleep-stage-invalid.json.snap | 126 ++++++ ...ments.post-12-bridge-with-source.json.snap | 115 +++++ .../measurements.post-13-bridge.json.snap | 219 ++++++++++ .../measurements.post-14-delegated.json.snap | 234 ++++++++++ .../routes/parity/measurements.parity.test.ts | 411 ++++++++++++++++++ tests/kit/parity.ts | 2 + 25 files changed, 3201 insertions(+) create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.get-01-page.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.get-02-type-sorted.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.get-03-filtered.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.get-04-sleep.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.get-05-drill-down.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.get-06-day-grouped.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.get-07-rollup-series.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.get-08-live-aggregate.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.get-09-invalid.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.post-01-single.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.post-02-single-duplicate.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.post-03-single-invalid.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.post-04-single-glucose.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.post-05-batch.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.post-06-batch-duplicate.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.post-07-batch-invalid.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.post-08-sleep-stage.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.post-09-sleep-stage-again.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.post-10-sleep-stage-mirror.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.post-11-sleep-stage-invalid.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.post-12-bridge-with-source.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.post-13-bridge.json.snap create mode 100644 tests/integration/routes/parity/__snapshots__/measurements.post-14-delegated.json.snap create mode 100644 tests/integration/routes/parity/measurements.parity.test.ts diff --git a/tests/integration/routes/parity/__snapshots__/measurements.get-01-page.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.get-01-page.json.snap new file mode 100644 index 000000000..9c445be46 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.get-01-page.json.snap @@ -0,0 +1,282 @@ +{ + "status": 200, + "body": { + "data": { + "measurements": [ + { + "id": "", + "userId": "", + "type": "PULSE", + "value": 71, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "MANUAL", + "measuredAt": "2026-10-15T07:00:00.000Z", + "notes": null, + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + { + "id": "", + "userId": "", + "type": "WEIGHT", + "value": 80.1, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "EXTERNAL", + "measuredAt": "2026-10-15T06:30:00.000Z", + "notes": null, + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + { + "id": "", + "userId": "", + "type": "ACTIVITY_STEPS", + "value": 5100, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T12:00:00.000Z", + "notes": null, + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-14", + "externalSourceVersion": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + { + "id": "", + "userId": "", + "type": "PULSE", + "value": 66, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": "seated", + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + { + "id": "", + "userId": "", + "type": "BLOOD_PRESSURE_DIA", + "value": 82, + "valueMin": null, + "valueMax": null, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + { + "id": "", + "userId": "", + "type": "BLOOD_PRESSURE_SYS", + "value": 128, + "valueMin": null, + "valueMax": null, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + { + "id": "", + "userId": "", + "type": "BLOOD_GLUCOSE", + "value": 104, + "valueMin": null, + "valueMax": null, + "unit": "mg/dL", + "source": "MANUAL", + "measuredAt": "2026-10-14T07:00:00.000Z", + "notes": null, + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": "FASTING", + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + { + "id": "", + "userId": "", + "type": "WEIGHT", + "value": 80.4, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-14T06:30:00.000Z", + "notes": "after the run", + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "scale", + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + { + "id": "", + "userId": "", + "type": "SLEEP_DURATION", + "value": 90, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "MANUAL", + "measuredAt": "2026-10-14T02:30:00.000Z", + "notes": null, + "externalId": "bridge-sleep-1", + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": "DEEP", + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + { + "id": "", + "userId": "", + "type": "ACTIVITY_STEPS", + "value": 8400, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-13T12:00:00.000Z", + "notes": null, + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-13", + "externalSourceVersion": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + { + "id": "", + "userId": "", + "type": "ACTIVITY_STEPS", + "value": 7100, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-12T12:00:00.000Z", + "notes": null, + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-12", + "externalSourceVersion": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + } + ], + "meta": { + "total": 11, + "limit": 50, + "offset": 0 + } + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "measurement.list" + }, + "meta": { + "total": 11 + }, + "warnings": null, + "error": null + } + ], + "effects": [] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.get-02-type-sorted.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.get-02-type-sorted.json.snap new file mode 100644 index 000000000..825225714 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.get-02-type-sorted.json.snap @@ -0,0 +1,76 @@ +{ + "status": 200, + "body": { + "data": { + "measurements": [ + { + "id": "", + "userId": "", + "type": "PULSE", + "value": 66, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": "seated", + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + { + "id": "", + "userId": "", + "type": "PULSE", + "value": 71, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "MANUAL", + "measuredAt": "2026-10-15T07:00:00.000Z", + "notes": null, + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + } + ], + "meta": { + "total": 2, + "limit": 100, + "offset": 0 + } + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "measurement.list" + }, + "meta": { + "total": 2, + "type": "PULSE" + }, + "warnings": null, + "error": null + } + ], + "effects": [] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.get-03-filtered.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.get-03-filtered.json.snap new file mode 100644 index 000000000..41d0feadc --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.get-03-filtered.json.snap @@ -0,0 +1,75 @@ +{ + "status": 200, + "body": { + "data": { + "measurements": [ + { + "id": "", + "userId": "", + "type": "BLOOD_PRESSURE_DIA", + "value": 82, + "valueMin": null, + "valueMax": null, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + { + "id": "", + "userId": "", + "type": "BLOOD_PRESSURE_SYS", + "value": 128, + "valueMin": null, + "valueMax": null, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + } + ], + "meta": { + "total": 6, + "limit": 2, + "offset": 1 + } + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "measurement.list" + }, + "meta": { + "total": 6 + }, + "warnings": null, + "error": null + } + ], + "effects": [] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.get-04-sleep.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.get-04-sleep.json.snap new file mode 100644 index 000000000..a0ccbbc53 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.get-04-sleep.json.snap @@ -0,0 +1,54 @@ +{ + "status": 200, + "body": { + "data": { + "measurements": [ + { + "id": "sleep:2026-10-14", + "type": "SLEEP_DURATION", + "value": 90, + "mainAsleepMinutes": 90, + "unit": "minutes", + "source": "MANUAL", + "measuredAt": "2026-10-14T02:30:00.000Z", + "notes": null, + "dayKey": "2026-10-14", + "sampleCount": 1, + "sleepStages": { + "DEEP": 90 + }, + "inBedMinutes": null, + "awakeMinutes": null, + "napCount": 0, + "napAsleepMinutes": 0, + "awakenings": 0 + } + ], + "meta": { + "total": 1, + "limit": 100, + "offset": 0, + "groupBy": "night" + } + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "measurement.list" + }, + "meta": { + "total": 1, + "type": "SLEEP_DURATION", + "mode": "sleep-per-night", + "nights": 1, + "rowsScanned": 1 + }, + "warnings": null, + "error": null + } + ], + "effects": [] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.get-05-drill-down.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.get-05-drill-down.json.snap new file mode 100644 index 000000000..4e6691c32 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.get-05-drill-down.json.snap @@ -0,0 +1,57 @@ +{ + "status": 200, + "body": { + "data": { + "measurements": [ + { + "id": "", + "userId": "", + "type": "ACTIVITY_STEPS", + "value": 8400, + "valueMin": null, + "valueMax": null, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-13T12:00:00.000Z", + "notes": null, + "notesEncrypted": null, + "externalId": "stats:HKQuantityTypeIdentifierStepCount:2026-10-13", + "externalSourceVersion": null, + "aggregationProvenance": "HEALTHKIT_STATISTICS", + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + } + ], + "meta": { + "total": 1, + "limit": 100, + "offset": 0, + "dayKey": "2026-10-13" + } + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "measurement.list" + }, + "meta": { + "total": 1, + "type": "ACTIVITY_STEPS", + "dayKey": "2026-10-13", + "mode": "drill-down" + }, + "warnings": null, + "error": null + } + ], + "effects": [] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.get-06-day-grouped.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.get-06-day-grouped.json.snap new file mode 100644 index 000000000..4ebb10ecf --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.get-06-day-grouped.json.snap @@ -0,0 +1,69 @@ +{ + "status": 200, + "body": { + "data": { + "measurements": [ + { + "id": "day:ACTIVITY_STEPS:2026-10-14", + "type": "ACTIVITY_STEPS", + "value": 5100, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-14T12:00:00.000Z", + "notes": null, + "dayKey": "2026-10-14", + "sampleCount": 1 + }, + { + "id": "day:ACTIVITY_STEPS:2026-10-13", + "type": "ACTIVITY_STEPS", + "value": 8400, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-13T12:00:00.000Z", + "notes": null, + "dayKey": "2026-10-13", + "sampleCount": 1 + }, + { + "id": "day:ACTIVITY_STEPS:2026-10-12", + "type": "ACTIVITY_STEPS", + "value": 7100, + "unit": "steps", + "source": "APPLE_HEALTH", + "measuredAt": "2026-10-12T12:00:00.000Z", + "notes": null, + "dayKey": "2026-10-12", + "sampleCount": 1 + } + ], + "meta": { + "total": 3, + "limit": 100, + "offset": 0, + "groupBy": "day", + "droppedDuplicates": 0 + } + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "measurement.list" + }, + "meta": { + "total": 3, + "type": "ACTIVITY_STEPS", + "groupBy": "day", + "days": 3, + "rowsScanned": 3, + "droppedDuplicates": 0 + }, + "warnings": null, + "error": null + } + ], + "effects": [] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.get-07-rollup-series.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.get-07-rollup-series.json.snap new file mode 100644 index 000000000..983fab507 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.get-07-rollup-series.json.snap @@ -0,0 +1,51 @@ +{ + "status": 200, + "body": { + "data": { + "measurements": [ + { + "type": "ACTIVITY_STEPS", + "value": 7100, + "measuredAt": "2026-10-12T00:00:00.000Z", + "count": 1 + }, + { + "type": "ACTIVITY_STEPS", + "value": 8400, + "measuredAt": "2026-10-13T00:00:00.000Z", + "count": 1 + }, + { + "type": "ACTIVITY_STEPS", + "value": 5100, + "measuredAt": "2026-10-14T00:00:00.000Z", + "count": 1 + } + ], + "meta": { + "total": 3, + "limit": 100, + "offset": 0, + "aggregate": "daily" + } + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "measurement.list" + }, + "meta": { + "total": 3, + "type": "ACTIVITY_STEPS", + "aggregate": "daily", + "source": "live" + }, + "warnings": null, + "error": null + } + ], + "effects": [] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.get-08-live-aggregate.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.get-08-live-aggregate.json.snap new file mode 100644 index 000000000..1ca06e6ad --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.get-08-live-aggregate.json.snap @@ -0,0 +1,40 @@ +{ + "status": 200, + "body": { + "data": { + "measurements": [ + { + "type": "PULSE", + "value": 68.5, + "measuredAt": "2026-10-12T00:00:00.000Z", + "count": 2, + "minValue": 66, + "maxValue": 71 + } + ], + "meta": { + "total": 1, + "limit": 100, + "offset": 0, + "aggregate": "weekly" + } + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "measurement.list" + }, + "meta": { + "total": 1, + "type": "PULSE", + "aggregate": "weekly" + }, + "warnings": null, + "error": null + } + ], + "effects": [] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.get-09-invalid.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.get-09-invalid.json.snap new file mode 100644 index 000000000..37ff5ab81 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.get-09-invalid.json.snap @@ -0,0 +1,40 @@ +{ + "status": 422, + "body": { + "data": null, + "error": "Validation failed", + "details": { + "issues": [ + { + "path": "limit", + "code": "too_small", + "message": "Too small: expected number to be >=1" + }, + { + "path": "sortDir", + "code": "invalid_value", + "message": "Invalid option: expected one of \"asc\"|\"desc\"" + } + ] + }, + "meta": { + "errorCode": "http.invalid", + "requestId": "" + } + }, + "events": [ + { + "status": 422, + "action": { + "name": "measurements.list.validation-failed" + }, + "meta": { + "issue_count": 2, + "rejected_attempt": "measurements.list.validation-failed" + }, + "warnings": null, + "error": null + } + ], + "effects": [] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.post-01-single.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.post-01-single.json.snap new file mode 100644 index 000000000..28e0b3bfc --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.post-01-single.json.snap @@ -0,0 +1,129 @@ +{ + "status": 201, + "body": { + "data": { + "id": "", + "userId": "", + "type": "WEIGHT", + "value": 80.4, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-14T06:30:00.000Z", + "notes": "after the run", + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "scale", + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + "error": null + }, + "events": [ + { + "status": 201, + "action": { + "name": "measurement.create", + "entity_type": "measurement", + "entity_id": "" + }, + "meta": null, + "warnings": null, + "error": null + } + ], + "effects": [ + { + "effect": "enqueueReminderSatisfy", + "args": [ + "" + ] + }, + { + "effect": "emitInsertedMeasurementArrivals", + "args": [ + "", + [ + { + "id": "", + "userId": "", + "type": "WEIGHT", + "value": 80.4, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-14T06:30:00.000Z", + "notes": null, + "notesEncrypted": "", + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": "scale", + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + } + ], + "manual" + ] + }, + { + "effect": "runSafetyFloorCheck", + "args": [ + { + "userId": "", + "written": [ + { + "type": "WEIGHT", + "value": 80.4, + "measuredAt": "2026-10-14T06:30:00.000Z", + "glucoseContext": null + } + ], + "timezone": "UTC" + } + ] + }, + { + "effect": "invalidateStatusInsightsForTypes", + "args": [ + "", + [ + "WEIGHT" + ], + { + "source": "manual" + } + ] + } + ], + "stored": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-14T06:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": "scale", + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.post-02-single-duplicate.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.post-02-single-duplicate.json.snap new file mode 100644 index 000000000..d92462a54 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.post-02-single-duplicate.json.snap @@ -0,0 +1,38 @@ +{ + "status": 409, + "body": { + "data": null, + "error": "A measurement with this data already exists", + "meta": { + "errorCode": "measurement.duplicate_timestamp", + "requestId": "" + } + }, + "events": [ + { + "status": 409, + "action": null, + "meta": null, + "warnings": null, + "error": null + } + ], + "effects": [], + "stored": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-14T06:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": "scale", + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.post-03-single-invalid.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.post-03-single-invalid.json.snap new file mode 100644 index 000000000..70abd6d39 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.post-03-single-invalid.json.snap @@ -0,0 +1,52 @@ +{ + "status": 422, + "body": { + "data": null, + "error": "Validation failed", + "details": { + "issues": [ + { + "path": "value", + "code": "invalid_type", + "message": "Invalid input: expected number, received string" + } + ] + }, + "meta": { + "errorCode": "measurement.create.invalid", + "requestId": "" + } + }, + "events": [ + { + "status": 422, + "action": { + "name": "measurements.create.validation-failed" + }, + "meta": { + "issue_count": 1, + "rejected_attempt": "measurements.create.validation-failed" + }, + "warnings": null, + "error": null + } + ], + "effects": [], + "stored": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-14T06:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": "scale", + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.post-04-single-glucose.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.post-04-single-glucose.json.snap new file mode 100644 index 000000000..29f7eb5da --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.post-04-single-glucose.json.snap @@ -0,0 +1,145 @@ +{ + "status": 201, + "body": { + "data": { + "id": "", + "userId": "", + "type": "BLOOD_GLUCOSE", + "value": 104, + "valueMin": null, + "valueMax": null, + "unit": "mg/dL", + "source": "MANUAL", + "measuredAt": "2026-10-14T07:00:00.000Z", + "notes": null, + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": "FASTING", + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + "error": null + }, + "events": [ + { + "status": 201, + "action": { + "name": "measurement.create", + "entity_type": "measurement", + "entity_id": "" + }, + "meta": null, + "warnings": null, + "error": null + } + ], + "effects": [ + { + "effect": "enqueueReminderSatisfy", + "args": [ + "" + ] + }, + { + "effect": "emitInsertedMeasurementArrivals", + "args": [ + "", + [ + { + "id": "", + "userId": "", + "type": "BLOOD_GLUCOSE", + "value": 104, + "valueMin": null, + "valueMax": null, + "unit": "mg/dL", + "source": "MANUAL", + "measuredAt": "2026-10-14T07:00:00.000Z", + "notes": null, + "notesEncrypted": null, + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": "FASTING", + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + } + ], + "manual" + ] + }, + { + "effect": "runSafetyFloorCheck", + "args": [ + { + "userId": "", + "written": [ + { + "type": "BLOOD_GLUCOSE", + "value": 104, + "measuredAt": "2026-10-14T07:00:00.000Z", + "glucoseContext": "FASTING" + } + ], + "symptomsPresent": false, + "timezone": "UTC" + } + ] + }, + { + "effect": "invalidateStatusInsightsForTypes", + "args": [ + "", + [ + "BLOOD_GLUCOSE" + ], + { + "source": "manual" + } + ] + } + ], + "stored": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-14T06:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": "scale", + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "BLOOD_GLUCOSE", + "value": 104, + "unit": "mg/dL", + "source": "MANUAL", + "measuredAt": "2026-10-14T07:00:00.000Z", + "notes": null, + "glucoseContext": "FASTING", + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.post-05-batch.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.post-05-batch.json.snap new file mode 100644 index 000000000..4227a1ed3 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.post-05-batch.json.snap @@ -0,0 +1,305 @@ +{ + "status": 201, + "body": { + "data": [ + { + "id": "", + "userId": "", + "type": "BLOOD_PRESSURE_SYS", + "value": 128, + "valueMin": null, + "valueMax": null, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + { + "id": "", + "userId": "", + "type": "BLOOD_PRESSURE_DIA", + "value": 82, + "valueMin": null, + "valueMax": null, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + { + "id": "", + "userId": "", + "type": "PULSE", + "value": 66, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": "seated", + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + } + ], + "error": null + }, + "events": [ + { + "status": 201, + "action": { + "name": "measurement.create.batch" + }, + "meta": { + "count": 3, + "types": [ + "BLOOD_PRESSURE_SYS", + "BLOOD_PRESSURE_DIA", + "PULSE" + ] + }, + "warnings": null, + "error": null + } + ], + "effects": [ + { + "effect": "enqueueReminderSatisfy", + "args": [ + "" + ] + }, + { + "effect": "emitInsertedMeasurementArrivals", + "args": [ + "", + [ + { + "id": "", + "userId": "", + "type": "BLOOD_PRESSURE_SYS", + "value": 128, + "valueMin": null, + "valueMax": null, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "notesEncrypted": null, + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + { + "id": "", + "userId": "", + "type": "BLOOD_PRESSURE_DIA", + "value": 82, + "valueMin": null, + "valueMax": null, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "notesEncrypted": null, + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + { + "id": "", + "userId": "", + "type": "PULSE", + "value": 66, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "notesEncrypted": "", + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + } + ], + "manual" + ] + }, + { + "effect": "runSafetyFloorCheck", + "args": [ + { + "userId": "", + "written": [ + { + "type": "BLOOD_PRESSURE_SYS", + "value": 128, + "measuredAt": "2026-10-14T08:00:00.000Z", + "glucoseContext": null + }, + { + "type": "BLOOD_PRESSURE_DIA", + "value": 82, + "measuredAt": "2026-10-14T08:00:00.000Z", + "glucoseContext": null + }, + { + "type": "PULSE", + "value": 66, + "measuredAt": "2026-10-14T08:00:00.000Z", + "glucoseContext": null + } + ], + "symptomsPresent": true, + "timezone": "UTC" + } + ] + }, + { + "effect": "invalidateStatusInsightsForTypes", + "args": [ + "", + [ + "BLOOD_PRESSURE_SYS", + "BLOOD_PRESSURE_DIA", + "PULSE" + ], + { + "source": "manual" + } + ] + } + ], + "stored": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-14T06:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": "scale", + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "BLOOD_PRESSURE_SYS", + "value": 128, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "BLOOD_PRESSURE_DIA", + "value": 82, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "PULSE", + "value": 66, + "unit": "bpm", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "BLOOD_GLUCOSE", + "value": 104, + "unit": "mg/dL", + "source": "MANUAL", + "measuredAt": "2026-10-14T07:00:00.000Z", + "notes": null, + "glucoseContext": "FASTING", + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.post-06-batch-duplicate.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.post-06-batch-duplicate.json.snap new file mode 100644 index 000000000..ed7395c4b --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.post-06-batch-duplicate.json.snap @@ -0,0 +1,98 @@ +{ + "status": 409, + "body": { + "data": null, + "error": "A measurement with this data already exists", + "meta": { + "errorCode": "measurement.duplicate_timestamp", + "requestId": "" + } + }, + "events": [ + { + "status": 409, + "action": null, + "meta": null, + "warnings": null, + "error": null + } + ], + "effects": [], + "stored": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-14T06:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": "scale", + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "BLOOD_PRESSURE_SYS", + "value": 128, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "BLOOD_PRESSURE_DIA", + "value": 82, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "PULSE", + "value": 66, + "unit": "bpm", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "BLOOD_GLUCOSE", + "value": 104, + "unit": "mg/dL", + "source": "MANUAL", + "measuredAt": "2026-10-14T07:00:00.000Z", + "notes": null, + "glucoseContext": "FASTING", + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.post-07-batch-invalid.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.post-07-batch-invalid.json.snap new file mode 100644 index 000000000..7ff61ddfb --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.post-07-batch-invalid.json.snap @@ -0,0 +1,112 @@ +{ + "status": 422, + "body": { + "data": null, + "error": "Validation failed", + "details": { + "issues": [ + { + "path": "measurements.0.measuredAt", + "code": "invalid_type", + "message": "Invalid input: expected string, received undefined" + } + ] + }, + "meta": { + "errorCode": "measurement.create.invalid", + "requestId": "" + } + }, + "events": [ + { + "status": 422, + "action": { + "name": "measurements.create.batch.validation-failed" + }, + "meta": { + "issue_count": 1, + "rejected_attempt": "measurements.create.batch.validation-failed" + }, + "warnings": null, + "error": null + } + ], + "effects": [], + "stored": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-14T06:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": "scale", + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "BLOOD_PRESSURE_SYS", + "value": 128, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "BLOOD_PRESSURE_DIA", + "value": 82, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "PULSE", + "value": 66, + "unit": "bpm", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "BLOOD_GLUCOSE", + "value": 104, + "unit": "mg/dL", + "source": "MANUAL", + "measuredAt": "2026-10-14T07:00:00.000Z", + "notes": null, + "glucoseContext": "FASTING", + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.post-08-sleep-stage.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.post-08-sleep-stage.json.snap new file mode 100644 index 000000000..8dd26af54 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.post-08-sleep-stage.json.snap @@ -0,0 +1,189 @@ +{ + "status": 201, + "body": { + "data": { + "id": "", + "userId": "", + "type": "SLEEP_DURATION", + "value": 90, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "MANUAL", + "measuredAt": "2026-10-14T02:30:00.000Z", + "notes": null, + "externalId": "bridge-sleep-1", + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": "DEEP", + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "", + "status": "inserted" + }, + "error": null + }, + "events": [ + { + "status": 201, + "action": { + "name": "measurement.create.sleep-stage", + "entity_type": "measurement", + "entity_id": "" + }, + "meta": { + "status": "inserted", + "stage": "DEEP", + "source": "MANUAL", + "cross_source_merge": false + }, + "warnings": null, + "error": null + } + ], + "effects": [ + { + "effect": "enqueueReminderSatisfy", + "args": [ + "" + ] + }, + { + "effect": "emitInsertedMeasurementArrivals", + "args": [ + "", + [ + { + "id": "", + "userId": "", + "type": "SLEEP_DURATION", + "source": "MANUAL", + "measuredAt": "2026-10-14T02:30:00.000Z", + "sleepStage": "DEEP", + "externalId": "bridge-sleep-1", + "deletedAt": null, + "aggregationProvenance": null + } + ], + "manual" + ] + }, + { + "effect": "maybeEnqueueMorningRefresh", + "args": [ + "", + [ + "2026-10-14T02:30:00.000Z" + ] + ] + }, + { + "effect": "invalidateStatusInsightsForTypes", + "args": [ + "", + [ + "SLEEP_DURATION" + ], + { + "source": "manual" + } + ] + } + ], + "stored": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-14T06:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": "scale", + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "BLOOD_PRESSURE_SYS", + "value": 128, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "BLOOD_PRESSURE_DIA", + "value": 82, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "PULSE", + "value": 66, + "unit": "bpm", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 90, + "unit": "minutes", + "source": "MANUAL", + "measuredAt": "2026-10-14T02:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": "bridge-sleep-1", + "sleepStage": "DEEP", + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "BLOOD_GLUCOSE", + "value": 104, + "unit": "mg/dL", + "source": "MANUAL", + "measuredAt": "2026-10-14T07:00:00.000Z", + "notes": null, + "glucoseContext": "FASTING", + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.post-09-sleep-stage-again.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.post-09-sleep-stage-again.json.snap new file mode 100644 index 000000000..8c9e12f6d --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.post-09-sleep-stage-again.json.snap @@ -0,0 +1,141 @@ +{ + "status": 200, + "body": { + "data": { + "id": "", + "userId": "", + "type": "SLEEP_DURATION", + "value": 90, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "MANUAL", + "measuredAt": "2026-10-14T02:30:00.000Z", + "notes": null, + "externalId": "bridge-sleep-1", + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": "DEEP", + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "", + "status": "duplicate" + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "measurement.create.sleep-stage", + "entity_type": "measurement", + "entity_id": "" + }, + "meta": { + "status": "duplicate", + "stage": "DEEP", + "source": "MANUAL", + "cross_source_merge": false + }, + "warnings": null, + "error": null + } + ], + "effects": [], + "stored": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-14T06:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": "scale", + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "BLOOD_PRESSURE_SYS", + "value": 128, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "BLOOD_PRESSURE_DIA", + "value": 82, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "PULSE", + "value": 66, + "unit": "bpm", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 90, + "unit": "minutes", + "source": "MANUAL", + "measuredAt": "2026-10-14T02:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": "bridge-sleep-1", + "sleepStage": "DEEP", + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "BLOOD_GLUCOSE", + "value": 104, + "unit": "mg/dL", + "source": "MANUAL", + "measuredAt": "2026-10-14T07:00:00.000Z", + "notes": null, + "glucoseContext": "FASTING", + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.post-10-sleep-stage-mirror.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.post-10-sleep-stage-mirror.json.snap new file mode 100644 index 000000000..1e51a4d35 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.post-10-sleep-stage-mirror.json.snap @@ -0,0 +1,141 @@ +{ + "status": 200, + "body": { + "data": { + "id": "", + "userId": "", + "type": "SLEEP_DURATION", + "value": 90, + "valueMin": null, + "valueMax": null, + "unit": "minutes", + "source": "MANUAL", + "measuredAt": "2026-10-14T02:30:00.000Z", + "notes": null, + "externalId": "bridge-sleep-1", + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": "DEEP", + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "", + "status": "duplicate" + }, + "error": null + }, + "events": [ + { + "status": 200, + "action": { + "name": "measurement.create.sleep-stage", + "entity_type": "measurement", + "entity_id": "" + }, + "meta": { + "status": "duplicate", + "stage": "DEEP", + "source": "APPLE_HEALTH", + "cross_source_merge": true + }, + "warnings": null, + "error": null + } + ], + "effects": [], + "stored": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-14T06:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": "scale", + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "BLOOD_PRESSURE_SYS", + "value": 128, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "BLOOD_PRESSURE_DIA", + "value": 82, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "PULSE", + "value": 66, + "unit": "bpm", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 90, + "unit": "minutes", + "source": "MANUAL", + "measuredAt": "2026-10-14T02:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": "bridge-sleep-1", + "sleepStage": "DEEP", + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "BLOOD_GLUCOSE", + "value": 104, + "unit": "mg/dL", + "source": "MANUAL", + "measuredAt": "2026-10-14T07:00:00.000Z", + "notes": null, + "glucoseContext": "FASTING", + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.post-11-sleep-stage-invalid.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.post-11-sleep-stage-invalid.json.snap new file mode 100644 index 000000000..18daae734 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.post-11-sleep-stage-invalid.json.snap @@ -0,0 +1,126 @@ +{ + "status": 422, + "body": { + "data": null, + "error": "Validation failed", + "details": { + "issues": [ + { + "path": "startDate", + "code": "custom", + "message": "Send the segment as startDate and endDate, or as measuredAt (its end) and value (its length in minutes)" + } + ] + }, + "meta": { + "errorCode": "measurement.create.invalid", + "requestId": "" + } + }, + "events": [ + { + "status": 422, + "action": { + "name": "measurements.create.sleep-stage.validation-failed" + }, + "meta": { + "issue_count": 1 + }, + "warnings": null, + "error": null + } + ], + "effects": [], + "stored": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-14T06:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": "scale", + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "BLOOD_PRESSURE_SYS", + "value": 128, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "BLOOD_PRESSURE_DIA", + "value": 82, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "PULSE", + "value": 66, + "unit": "bpm", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 90, + "unit": "minutes", + "source": "MANUAL", + "measuredAt": "2026-10-14T02:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": "bridge-sleep-1", + "sleepStage": "DEEP", + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "BLOOD_GLUCOSE", + "value": 104, + "unit": "mg/dL", + "source": "MANUAL", + "measuredAt": "2026-10-14T07:00:00.000Z", + "notes": null, + "glucoseContext": "FASTING", + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.post-12-bridge-with-source.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.post-12-bridge-with-source.json.snap new file mode 100644 index 000000000..20f55d666 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.post-12-bridge-with-source.json.snap @@ -0,0 +1,115 @@ +{ + "status": 422, + "body": { + "data": null, + "error": "This credential resolves the source itself; omit the field", + "meta": { + "errorCode": "measurement.create.source_not_permitted", + "requestId": "" + } + }, + "events": [ + { + "status": 422, + "action": { + "name": "measurements.create.source-not-permitted" + }, + "meta": null, + "warnings": null, + "error": null + } + ], + "effects": [], + "stored": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-14T06:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": "scale", + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "BLOOD_PRESSURE_SYS", + "value": 128, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "BLOOD_PRESSURE_DIA", + "value": 82, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "PULSE", + "value": 66, + "unit": "bpm", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 90, + "unit": "minutes", + "source": "MANUAL", + "measuredAt": "2026-10-14T02:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": "bridge-sleep-1", + "sleepStage": "DEEP", + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "BLOOD_GLUCOSE", + "value": 104, + "unit": "mg/dL", + "source": "MANUAL", + "measuredAt": "2026-10-14T07:00:00.000Z", + "notes": null, + "glucoseContext": "FASTING", + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.post-13-bridge.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.post-13-bridge.json.snap new file mode 100644 index 000000000..1b16eb525 --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.post-13-bridge.json.snap @@ -0,0 +1,219 @@ +{ + "status": 201, + "body": { + "data": { + "id": "", + "userId": "", + "type": "WEIGHT", + "value": 80.1, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "EXTERNAL", + "measuredAt": "2026-10-15T06:30:00.000Z", + "notes": null, + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + "error": null + }, + "events": [ + { + "status": 201, + "action": { + "name": "measurement.create", + "entity_type": "measurement", + "entity_id": "" + }, + "meta": null, + "warnings": null, + "error": null + } + ], + "effects": [ + { + "effect": "enqueueReminderSatisfy", + "args": [ + "" + ] + }, + { + "effect": "emitInsertedMeasurementArrivals", + "args": [ + "", + [ + { + "id": "", + "userId": "", + "type": "WEIGHT", + "value": 80.1, + "valueMin": null, + "valueMax": null, + "unit": "kg", + "source": "EXTERNAL", + "measuredAt": "2026-10-15T06:30:00.000Z", + "notes": null, + "notesEncrypted": null, + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + } + ], + "manual" + ] + }, + { + "effect": "runSafetyFloorCheck", + "args": [ + { + "userId": "", + "written": [ + { + "type": "WEIGHT", + "value": 80.1, + "measuredAt": "2026-10-15T06:30:00.000Z", + "glucoseContext": null + } + ], + "timezone": "UTC" + } + ] + }, + { + "effect": "invalidateStatusInsightsForTypes", + "args": [ + "", + [ + "WEIGHT" + ], + { + "source": "manual" + } + ] + } + ], + "stored": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-14T06:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": "scale", + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "WEIGHT", + "value": 80.1, + "unit": "kg", + "source": "EXTERNAL", + "measuredAt": "2026-10-15T06:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "BLOOD_PRESSURE_SYS", + "value": 128, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "BLOOD_PRESSURE_DIA", + "value": 82, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "PULSE", + "value": 66, + "unit": "bpm", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 90, + "unit": "minutes", + "source": "MANUAL", + "measuredAt": "2026-10-14T02:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": "bridge-sleep-1", + "sleepStage": "DEEP", + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "BLOOD_GLUCOSE", + "value": 104, + "unit": "mg/dL", + "source": "MANUAL", + "measuredAt": "2026-10-14T07:00:00.000Z", + "notes": null, + "glucoseContext": "FASTING", + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + } + ] +} diff --git a/tests/integration/routes/parity/__snapshots__/measurements.post-14-delegated.json.snap b/tests/integration/routes/parity/__snapshots__/measurements.post-14-delegated.json.snap new file mode 100644 index 000000000..acf18c4fc --- /dev/null +++ b/tests/integration/routes/parity/__snapshots__/measurements.post-14-delegated.json.snap @@ -0,0 +1,234 @@ +{ + "status": 201, + "body": { + "data": { + "id": "", + "userId": "", + "type": "PULSE", + "value": 71, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "MANUAL", + "measuredAt": "2026-10-15T07:00:00.000Z", + "notes": null, + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + }, + "error": null + }, + "events": [ + { + "status": 201, + "action": { + "name": "measurement.create", + "entity_type": "measurement", + "entity_id": "" + }, + "meta": null, + "warnings": null, + "error": null + } + ], + "effects": [ + { + "effect": "enqueueReminderSatisfy", + "args": [ + "" + ] + }, + { + "effect": "emitInsertedMeasurementArrivals", + "args": [ + "", + [ + { + "id": "", + "userId": "", + "type": "PULSE", + "value": 71, + "valueMin": null, + "valueMax": null, + "unit": "bpm", + "source": "MANUAL", + "measuredAt": "2026-10-15T07:00:00.000Z", + "notes": null, + "notesEncrypted": null, + "externalId": null, + "externalSourceVersion": null, + "aggregationProvenance": null, + "glucoseContext": null, + "sleepStage": null, + "rhythmClassification": null, + "deviceType": null, + "syncVersion": "", + "deletedAt": null, + "createdAt": "", + "updatedAt": "" + } + ], + "manual" + ] + }, + { + "effect": "runSafetyFloorCheck", + "args": [ + { + "userId": "", + "written": [ + { + "type": "PULSE", + "value": 71, + "measuredAt": "2026-10-15T07:00:00.000Z", + "glucoseContext": null + } + ], + "timezone": "UTC" + } + ] + }, + { + "effect": "invalidateStatusInsightsForTypes", + "args": [ + "", + [ + "PULSE" + ], + { + "source": "manual" + } + ] + } + ], + "stored": [ + { + "id": "", + "type": "WEIGHT", + "value": 80.4, + "unit": "kg", + "source": "MANUAL", + "measuredAt": "2026-10-14T06:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": "scale", + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "WEIGHT", + "value": 80.1, + "unit": "kg", + "source": "EXTERNAL", + "measuredAt": "2026-10-15T06:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "BLOOD_PRESSURE_SYS", + "value": 128, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "BLOOD_PRESSURE_DIA", + "value": 82, + "unit": "mmHg", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "PULSE", + "value": 66, + "unit": "bpm", + "source": "MANUAL", + "measuredAt": "2026-10-14T08:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": true + }, + { + "id": "", + "type": "PULSE", + "value": 71, + "unit": "bpm", + "source": "MANUAL", + "measuredAt": "2026-10-15T07:00:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "SLEEP_DURATION", + "value": 90, + "unit": "minutes", + "source": "MANUAL", + "measuredAt": "2026-10-14T02:30:00.000Z", + "notes": null, + "glucoseContext": null, + "deviceType": null, + "externalId": "bridge-sleep-1", + "sleepStage": "DEEP", + "deletedAt": null, + "sealedNote": false + }, + { + "id": "", + "type": "BLOOD_GLUCOSE", + "value": 104, + "unit": "mg/dL", + "source": "MANUAL", + "measuredAt": "2026-10-14T07:00:00.000Z", + "notes": null, + "glucoseContext": "FASTING", + "deviceType": null, + "externalId": null, + "sleepStage": null, + "deletedAt": null, + "sealedNote": false + } + ] +} diff --git a/tests/integration/routes/parity/measurements.parity.test.ts b/tests/integration/routes/parity/measurements.parity.test.ts new file mode 100644 index 000000000..48f63850e --- /dev/null +++ b/tests/integration/routes/parity/measurements.parity.test.ts @@ -0,0 +1,411 @@ +/** + * Parity for `GET` and `POST /api/measurements` while their logic moves into + * `src/lib/measurements/`. + * + * The POST walk covers each arm the handler tells apart: a single reading, + * its duplicate, a refused one; a combined two-value reading, its duplicate, + * a refused one; a named sleep-stage segment, its re-post and its mirror + * from the other source; a narrow bridge token with and without a `source`; + * and a delegate writing into a record shared with them. The GET walk then + * reads the stored rows back through every list mode: the plain page with + * filters, the sleep list, the cumulative day drill-down, the day-grouped + * list, the rollup series, a live aggregate, and a refused query. + * + * After each request the snapshot holds the response, the wide event, the + * fire-and-forget tails the route handed work to, and (for writes) the + * account's stored measurements, a sealed note as its presence. + */ +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; + +import { GET, POST } from "@/app/api/measurements/route"; +import { MEASUREMENTS_WRITE_SCOPE } from "@/lib/measurements/scopes"; + +import { callRoute, type RouteAuth } from "../../../kit/call-route"; +import { + makeGrant, + makeSession, + makeToken, + makeUser, +} from "../../../kit/factories"; +import { + effectRecorder, + eventFacts, + expectParity, + stable, + wideEventRecorder, +} from "../../../kit/parity"; +import { + getPrismaClient, + switchSessionTo, + truncateAllTables, +} from "../../setup"; + +vi.mock("@/lib/logging/transports", async (importOriginal) => { + const actual = + await importOriginal(); + const { wideEventRecorder: recorder } = await import("../../../kit/parity"); + return { + ...actual, + emitIfSampled: (event: (typeof recorder.events)[number]) => { + recorder.events.push(event); + }, + }; +}); +vi.mock("@/lib/arrivals/measurement-emit", async (importOriginal) => { + const actual = + await importOriginal(); + const { effectRecorder } = await import("../../../kit/parity"); + return { + ...actual, + emitInsertedMeasurementArrivals: async (...args: unknown[]) => { + effectRecorder.record("emitInsertedMeasurementArrivals", 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/insights/status-invalidation", async (importOriginal) => { + const actual = + await importOriginal(); + const { effectRecorder } = await import("../../../kit/parity"); + return { + ...actual, + invalidateStatusInsightsForTypes: async (...args: unknown[]) => { + effectRecorder.record("invalidateStatusInsightsForTypes", args); + }, + }; +}); +vi.mock("@/lib/illness/safety-floor-check", async (importOriginal) => { + const actual = + await importOriginal(); + const { effectRecorder } = await import("../../../kit/parity"); + return { + ...actual, + runSafetyFloorCheck: async (...args: unknown[]) => { + effectRecorder.record("runSafetyFloorCheck", args); + }, + }; +}); + +const prisma = getPrismaClient(); +const NOW = new Date("2026-10-15T12:00:00.000Z"); +const STEPS = "HKQuantityTypeIdentifierStepCount"; + +let ownerId: string; +let owner: RouteAuth; + +async function stored(userId: string) { + const rows = await prisma.measurement.findMany({ + where: { userId }, + orderBy: [ + { type: "asc" }, + { measuredAt: "asc" }, + { source: "asc" }, + { externalId: "asc" }, + ], + select: { + id: true, + type: true, + value: true, + unit: true, + source: true, + measuredAt: true, + notes: true, + notesEncrypted: true, + glucoseContext: true, + deviceType: true, + externalId: true, + sleepStage: true, + deletedAt: true, + }, + }); + return rows.map(({ notesEncrypted, ...row }) => ({ + ...row, + sealedNote: notesEncrypted !== null, + })); +} + +async function step( + name: string, + call: { + handler: typeof GET | typeof POST; + method: "GET" | "POST"; + body?: unknown; + query?: Record; + auth?: RouteAuth; + readBack?: string; + contract?: { skip: string }; + }, +): Promise { + wideEventRecorder.take(); + effectRecorder.take(); + const res = await callRoute(call.handler, { + method: call.method, + route: "/api/measurements", + body: call.body, + query: call.query, + auth: call.auth ?? owner, + contract: call.contract, + }); + const events = wideEventRecorder.take(); + expect(events.length).toBeGreaterThan(0); + // The tails are handed off without being awaited; let them reach the + // recorder before the snapshot is taken. + await new Promise((resolve) => setTimeout(resolve, 0)); + await expectParity( + `measurements.${name}`, + stable( + { + status: res.status, + body: res.body, + events: events.map(eventFacts), + effects: effectRecorder.take(), + ...(call.readBack ? { stored: await stored(call.readBack) } : {}), + }, + { since: NOW }, + ), + ); +} + +const post = ( + name: string, + body: unknown, + auth?: RouteAuth, + readBack?: string, +) => + step(name, { + handler: POST, + method: "POST", + body, + auth, + readBack: readBack ?? ownerId, + }); +const get = ( + name: string, + query: Record, + contract?: { skip: string }, +) => step(name, { handler: GET, method: "GET", query, contract }); + +beforeAll(async () => { + vi.useFakeTimers({ toFake: ["Date"], now: NOW }); + await truncateAllTables(prisma); + const user = await makeUser({ timezone: "UTC" }); + ownerId = user.id; + owner = await makeSession(user); +}); + +afterAll(() => { + vi.useRealTimers(); +}); + +describe("POST /api/measurements parity", () => { + it("a single reading, its duplicate, a refused one", async () => { + const weight = { + type: "WEIGHT", + value: 80.4, + measuredAt: "2026-10-14T06:30:00.000Z", + notes: "after the run", + deviceType: "scale", + }; + await post("post-01-single", weight); + await post("post-02-single-duplicate", weight); + await post("post-03-single-invalid", { + type: "WEIGHT", + value: "heavy", + measuredAt: "2026-10-14T06:30:00.000Z", + }); + await post("post-04-single-glucose", { + type: "BLOOD_GLUCOSE", + value: 104, + measuredAt: "2026-10-14T07:00:00.000Z", + glucoseContext: "FASTING", + symptomsPresent: false, + }); + }); + + it("a combined two-value reading, its duplicate, a refused one", async () => { + const reading = [ + { + type: "BLOOD_PRESSURE_SYS", + value: 128, + measuredAt: "2026-10-14T08:00:00.000Z", + }, + { + type: "BLOOD_PRESSURE_DIA", + value: 82, + measuredAt: "2026-10-14T08:00:00.000Z", + }, + { + type: "PULSE", + value: 66, + measuredAt: "2026-10-14T08:00:00.000Z", + notes: "seated", + symptomsPresent: true, + }, + ]; + await post("post-05-batch", reading); + await post("post-06-batch-duplicate", reading); + await post("post-07-batch-invalid", [{ type: "PULSE", value: -1 }]); + }); + + it("a named sleep-stage segment, its re-post and its mirror", async () => { + const segment = { + type: "SLEEP_DURATION", + sleepStage: "DEEP", + startDate: "2026-10-14T01:00:00.000Z", + endDate: "2026-10-14T02:30:00.000Z", + externalId: "bridge-sleep-1", + }; + await post("post-08-sleep-stage", segment); + await post("post-09-sleep-stage-again", segment); + await post("post-10-sleep-stage-mirror", { + ...segment, + externalId: "hk-sleep-1", + source: "APPLE_HEALTH", + }); + await post("post-11-sleep-stage-invalid", { + type: "SLEEP_DURATION", + sleepStage: "DEEP", + }); + }); + + it("a narrow bridge token with and without a source", async () => { + const bridge = await makeToken( + { id: ownerId }, + { permissions: [MEASUREMENTS_WRITE_SCOPE] }, + ); + await post( + "post-12-bridge-with-source", + { + type: "WEIGHT", + value: 80.1, + measuredAt: "2026-10-15T06:30:00.000Z", + source: "MANUAL", + }, + bridge, + ); + await post( + "post-13-bridge", + { + type: "WEIGHT", + value: 80.1, + measuredAt: "2026-10-15T06:30:00.000Z", + }, + bridge, + ); + }); + + it("a delegate writing into a shared record", async () => { + const delegateUser = await makeUser({ timezone: "UTC" }); + await makeGrant({ id: ownerId }, delegateUser, { access: "WRITE" }); + const delegate = await makeSession(delegateUser); + await switchSessionTo(delegate.sessionId, ownerId); + await post( + "post-14-delegated", + { + type: "PULSE", + value: 71, + measuredAt: "2026-10-15T07:00:00.000Z", + }, + delegate, + ); + await switchSessionTo(delegate.sessionId, null); + }); +}); + +describe("GET /api/measurements parity", () => { + beforeAll(async () => { + // Day totals for the cumulative list modes, from the phone. + for (const [day, value] of [ + ["2026-10-12", 7_100], + ["2026-10-13", 8_400], + ["2026-10-14", 5_100], + ] as const) { + await prisma.measurement.create({ + data: { + userId: ownerId, + type: "ACTIVITY_STEPS", + value, + unit: "steps", + source: "APPLE_HEALTH", + measuredAt: new Date(`${day}T12:00:00.000Z`), + externalId: `stats:${STEPS}:${day}`, + aggregationProvenance: "HEALTHKIT_STATISTICS", + }, + }); + } + }); + + it("the plain page and its filters", async () => { + await get("get-01-page", { limit: 50 }); + await get("get-02-type-sorted", { + type: "PULSE", + sortBy: "value", + sortDir: "asc", + }); + await get("get-03-filtered", { + sourceEq: "MANUAL", + valueMin: 70, + valueMax: 130, + from: "2026-10-14T00:00:00.000Z", + to: "2026-10-15T23:59:59.000Z", + limit: 2, + offset: 1, + }); + }); + + it("the list modes", async () => { + await get("get-04-sleep", { type: "SLEEP_DURATION" }); + // The drill-down answers the stored rows as they are, `notesEncrypted` + // included, which the contract does not declare (the other list modes + // strip it). Pinned as it is; this walk records behaviour, it does not + // change it. + await get( + "get-05-drill-down", + { type: "ACTIVITY_STEPS", dayKey: "2026-10-13" }, + { + skip: "the drill-down returns notesEncrypted, which the contract omits", + }, + ); + await get("get-06-day-grouped", { type: "ACTIVITY_STEPS", groupBy: "day" }); + await get("get-07-rollup-series", { + type: "ACTIVITY_STEPS", + source: "rollup", + aggregate: "daily", + from: "2026-10-10T00:00:00.000Z", + to: "2026-10-15T23:59:59.000Z", + }); + await get("get-08-live-aggregate", { + type: "PULSE", + aggregate: "weekly", + from: "2026-09-01T00:00:00.000Z", + to: "2026-10-15T23:59:59.000Z", + }); + }); + + it("a refused query", async () => { + await get("get-09-invalid", { limit: 0, sortDir: "sideways" }); + }); +}); diff --git a/tests/kit/parity.ts b/tests/kit/parity.ts index 7e2bf2d58..07bd7641a 100644 --- a/tests/kit/parity.ts +++ b/tests/kit/parity.ts @@ -72,6 +72,8 @@ export function stable(value: unknown, options: StableOptions): unknown { return `<${key}>`; } if (node instanceof Date) node = node.toISOString(); + // Sealed columns carry a fresh nonce on every write: keep the length. + if (node instanceof Uint8Array) return ``; if (typeof node === "string") { if (GENERATED_ID.test(node)) { let label = ids.get(node);