test(measurements): pin the measurement series before moving its readers

A parity walk over GET /api/measurements/series through every reader it chooses between: the plain and ladder-picked rows, pulse with its spread, the paired blood pressure (two sources and a corrupt systolic), the sleep nights, glucose raw and day-bucketed, a dense stream bucketed per hour, a refused query and a delegate in another language. The readers cut their windows against the database clock, so the record is laid out from the start of the current UTC day and the snapshot holds instants and day keys relative to it.
This commit is contained in:
Marc-André Bombeck committed 2026-10-23 14:05:55 +02:00
1 parent 817a922df5
commit 8feeaed473
11 files changed
+2792

No files matched your search

@@ -0,0 +1,154 @@
{
"status": 200,
"body": {
"data": {
"kind": "weight",
"unit": "kg",
"points": [
{
"id": "<id:1>",
"at": "anchor-20d+06:30:00",
"value": 78,
"secondary": null
},
{
"id": "<id:2>",
"at": "anchor-19d+06:30:00",
"value": 78.1,
"secondary": null
},
{
"id": "<id:3>",
"at": "anchor-18d+06:30:00",
"value": 78.2,
"secondary": null
},
{
"id": "<id:4>",
"at": "anchor-17d+06:30:00",
"value": 78.3,
"secondary": null
},
{
"id": "<id:5>",
"at": "anchor-16d+06:30:00",
"value": 78.4,
"secondary": null
},
{
"id": "<id:6>",
"at": "anchor-15d+06:30:00",
"value": 78.5,
"secondary": null
},
{
"id": "<id:7>",
"at": "anchor-13d+06:30:00",
"value": 78.7,
"secondary": null
},
{
"id": "<id:8>",
"at": "anchor-12d+06:30:00",
"value": 78.8,
"secondary": null
},
{
"id": "<id:9>",
"at": "anchor-11d+06:30:00",
"value": 78.9,
"secondary": null
},
{
"id": "<id:10>",
"at": "anchor-10d+06:30:00",
"value": 79,
"secondary": null
},
{
"id": "<id:11>",
"at": "anchor-9d+06:30:00",
"value": 79.1,
"secondary": null
},
{
"id": "<id:12>",
"at": "anchor-8d+06:30:00",
"value": 79.2,
"secondary": null
},
{
"id": "<id:13>",
"at": "anchor-6d+06:30:00",
"value": 79.4,
"secondary": null
},
{
"id": "<id:14>",
"at": "anchor-5d+06:30:00",
"value": 79.5,
"secondary": null
},
{
"id": "<id:15>",
"at": "anchor-4d+06:30:00",
"value": 79.6,
"secondary": null
},
{
"id": "<id:16>",
"at": "anchor-3d+06:30:00",
"value": 79.7,
"secondary": null
},
{
"id": "<id:17>",
"at": "anchor-2d+06:30:00",
"value": 79.8,
"secondary": null
},
{
"id": "<id:18>",
"at": "anchor-1d+06:30:00",
"value": 79.9,
"secondary": null
}
],
"direction": {
"direction": "up-bad",
"targetPosition": null,
"basis": "metric_default"
},
"change": {
"from": 78,
"to": 79.9,
"delta": 1.9,
"sentiment": "negative"
},
"referenceLines": [],
"stats": {
"mean": 78.95,
"min": 78,
"max": 79.9,
"stdDev": 0.6,
"count": 18
}
},
"error": null
},
"events": [
{
"status": 200,
"action": {
"name": "measurements.series"
},
"meta": {
"kind": "weight",
"days": 30,
"count": 18
},
"warnings": null,
"error": null
}
]
}
@@ -0,0 +1,118 @@
{
"status": 200,
"body": {
"data": {
"kind": "steps",
"unit": "steps",
"points": [
{
"id": "<id:1>",
"at": "anchor-13d+12:00:00",
"value": 7300,
"secondary": null
},
{
"id": "<id:2>",
"at": "anchor-12d+12:00:00",
"value": 7200,
"secondary": null
},
{
"id": "<id:3>",
"at": "anchor-11d+12:00:00",
"value": 7100,
"secondary": null
},
{
"id": "<id:4>",
"at": "anchor-10d+12:00:00",
"value": 7000,
"secondary": null
},
{
"id": "<id:5>",
"at": "anchor-9d+12:00:00",
"value": 6900,
"secondary": null
},
{
"id": "<id:6>",
"at": "anchor-8d+12:00:00",
"value": 6800,
"secondary": null
},
{
"id": "<id:7>",
"at": "anchor-6d+12:00:00",
"value": 6600,
"secondary": null
},
{
"id": "<id:8>",
"at": "anchor-5d+12:00:00",
"value": 6500,
"secondary": null
},
{
"id": "<id:9>",
"at": "anchor-4d+12:00:00",
"value": 6400,
"secondary": null
},
{
"id": "<id:10>",
"at": "anchor-3d+12:00:00",
"value": 6300,
"secondary": null
},
{
"id": "<id:11>",
"at": "anchor-2d+12:00:00",
"value": 6200,
"secondary": null
},
{
"id": "<id:12>",
"at": "anchor-1d+12:00:00",
"value": 6100,
"secondary": null
}
],
"direction": {
"direction": "up-good",
"targetPosition": null,
"basis": "metric_default"
},
"change": {
"from": 7300,
"to": 6100,
"delta": -1200,
"sentiment": "negative"
},
"referenceLines": [],
"stats": {
"mean": 6700,
"min": 6100,
"max": 7300,
"stdDev": 389.44,
"count": 12
}
},
"error": null
},
"events": [
{
"status": 200,
"action": {
"name": "measurements.series"
},
"meta": {
"kind": "steps",
"days": 14,
"count": 12
},
"warnings": null,
"error": null
}
]
}
@@ -0,0 +1,190 @@
{
"status": 200,
"body": {
"data": {
"kind": "pulse",
"unit": "bpm",
"points": [
{
"id": "<id:1>",
"at": "anchor-6d+09:00:00",
"value": 69,
"secondary": null,
"valueMin": 64,
"valueMax": 79
},
{
"id": "<id:2>",
"at": "anchor-6d+14:00:00",
"value": 74,
"secondary": null,
"valueMin": 69,
"valueMax": 84
},
{
"id": "<id:3>",
"at": "anchor-6d+20:00:00",
"value": 80,
"secondary": null,
"valueMin": 75,
"valueMax": 90
},
{
"id": "<id:4>",
"at": "anchor-5d+09:00:00",
"value": 69,
"secondary": null,
"valueMin": 64,
"valueMax": 79
},
{
"id": "<id:5>",
"at": "anchor-5d+14:00:00",
"value": 74,
"secondary": null,
"valueMin": 69,
"valueMax": 84
},
{
"id": "<id:6>",
"at": "anchor-5d+20:00:00",
"value": 80,
"secondary": null,
"valueMin": 75,
"valueMax": 90
},
{
"id": "<id:7>",
"at": "anchor-4d+09:00:00",
"value": 69,
"secondary": null,
"valueMin": 64,
"valueMax": 79
},
{
"id": "<id:8>",
"at": "anchor-4d+14:00:00",
"value": 74,
"secondary": null,
"valueMin": 69,
"valueMax": 84
},
{
"id": "<id:9>",
"at": "anchor-4d+20:00:00",
"value": 80,
"secondary": null,
"valueMin": 75,
"valueMax": 90
},
{
"id": "<id:10>",
"at": "anchor-3d+09:00:00",
"value": 69,
"secondary": null,
"valueMin": 64,
"valueMax": 79
},
{
"id": "<id:11>",
"at": "anchor-3d+14:00:00",
"value": 74,
"secondary": null,
"valueMin": 69,
"valueMax": 84
},
{
"id": "<id:12>",
"at": "anchor-3d+20:00:00",
"value": 80,
"secondary": null,
"valueMin": 75,
"valueMax": 90
},
{
"id": "<id:13>",
"at": "anchor-2d+09:00:00",
"value": 69,
"secondary": null,
"valueMin": 64,
"valueMax": 79
},
{
"id": "<id:14>",
"at": "anchor-2d+14:00:00",
"value": 74,
"secondary": null,
"valueMin": 69,
"valueMax": 84
},
{
"id": "<id:15>",
"at": "anchor-2d+20:00:00",
"value": 80,
"secondary": null,
"valueMin": 75,
"valueMax": 90
},
{
"id": "<id:16>",
"at": "anchor-1d+09:00:00",
"value": 69,
"secondary": null,
"valueMin": 64,
"valueMax": 79
},
{
"id": "<id:17>",
"at": "anchor-1d+14:00:00",
"value": 74,
"secondary": null,
"valueMin": 69,
"valueMax": 84
},
{
"id": "<id:18>",
"at": "anchor-1d+20:00:00",
"value": 80,
"secondary": null,
"valueMin": 75,
"valueMax": 90
}
],
"direction": {
"direction": "hold",
"targetPosition": "inside",
"basis": "profile_default"
},
"change": {
"from": 74.33,
"to": 74.33,
"delta": 0,
"sentiment": "positive"
},
"referenceLines": [],
"stats": {
"mean": 74.33,
"min": 69,
"max": 80,
"stdDev": 4.5,
"count": 18
}
},
"error": null
},
"events": [
{
"status": 200,
"action": {
"name": "measurements.series"
},
"meta": {
"kind": "pulse",
"days": 7,
"count": 18
},
"warnings": null,
"error": null
}
]
}
@@ -0,0 +1,187 @@
{
"status": 200,
"body": {
"data": {
"kind": "bloodPressure",
"unit": "mmHg",
"points": [
{
"id": "<id:1>",
"at": "anchor-20d+07:01:00",
"value": 126,
"secondary": 83
},
{
"id": "<id:2>",
"at": "anchor-19d+07:01:00",
"value": 130,
"secondary": 82
},
{
"id": "<id:3>",
"at": "anchor-18d+07:01:00",
"value": 129,
"secondary": 81
},
{
"id": "<id:4>",
"at": "anchor-17d+07:01:00",
"value": 128,
"secondary": 83
},
{
"id": "<id:5>",
"at": "anchor-16d+07:01:00",
"value": 127,
"secondary": 82
},
{
"id": "<id:6>",
"at": "anchor-15d+07:01:00",
"value": 126,
"secondary": 81
},
{
"id": "<id:7>",
"at": "anchor-13d+07:01:00",
"value": 129,
"secondary": 82
},
{
"id": "<id:8>",
"at": "anchor-12d+07:01:00",
"value": 128,
"secondary": 81
},
{
"id": "<id:9>",
"at": "anchor-11d+07:01:00",
"value": 127,
"secondary": 83
},
{
"id": "<id:10>",
"at": "anchor-10d+07:01:00",
"value": 126,
"secondary": 82
},
{
"id": "<id:11>",
"at": "anchor-9d+07:01:00",
"value": 130,
"secondary": 81
},
{
"id": "<id:12>",
"at": "anchor-8d+07:01:00",
"value": 129,
"secondary": 83
},
{
"id": "<id:13>",
"at": "anchor-6d+07:01:00",
"value": 127,
"secondary": 81
},
{
"id": "<id:14>",
"at": "anchor-5d+07:01:00",
"value": 126,
"secondary": 83
},
{
"id": "<id:15>",
"at": "anchor-4d+07:01:00",
"value": 130,
"secondary": 82
},
{
"id": "<id:16>",
"at": "anchor-3d+07:01:00",
"value": 129,
"secondary": 81
},
{
"id": "<id:17>",
"at": "anchor-2d+07:01:00",
"value": 128,
"secondary": 83
},
{
"id": "<id:18>",
"at": "anchor-1d+07:01:00",
"value": 127,
"secondary": 82
}
],
"direction": {
"direction": "hold",
"targetPosition": "inside",
"basis": "profile_default"
},
"change": {
"from": 126,
"to": 127,
"delta": 1,
"sentiment": "neutral"
},
"referenceLines": [
{
"value": 120,
"kind": "lower",
"source": "personal",
"appliesTo": "value",
"labelKey": "series.reference.targetLower",
"label": "Your target, lower limit"
},
{
"value": 129,
"kind": "upper",
"source": "personal",
"appliesTo": "value",
"labelKey": "series.reference.targetUpper",
"label": "Your target, upper limit"
},
{
"value": 70,
"kind": "lower",
"source": "personal",
"appliesTo": "secondary",
"labelKey": "series.reference.targetLower",
"label": "Your target, lower limit"
},
{
"value": 79,
"kind": "upper",
"source": "personal",
"appliesTo": "secondary",
"labelKey": "series.reference.targetUpper",
"label": "Your target, upper limit"
}
],
"stats": {
"mean": 127.89,
"min": 126,
"max": 130,
"stdDev": 1.41,
"count": 18
}
},
"error": null
},
"events": [
{
"status": 200,
"action": {
"name": "measurements.series"
},
"meta": {
"kind": "bloodPressure",
"days": 30,
"count": 18
},
"warnings": null,
"error": null
}
]
}
@@ -0,0 +1,190 @@
{
"status": 200,
"body": {
"data": {
"kind": "sleep",
"unit": "h",
"points": [
{
"id": "sleep:day-13",
"at": "anchor-13d+04:30:00",
"value": 5.5,
"secondary": null,
"sleepStages": {
"CORE": 3,
"DEEP": 1,
"REM": 1.5,
"AWAKE": 0.17
}
},
{
"id": "sleep:day-12",
"at": "anchor-12d+04:30:00",
"value": 5.5,
"secondary": null,
"sleepStages": {
"CORE": 3,
"DEEP": 1,
"REM": 1.5,
"AWAKE": 0.17
}
},
{
"id": "sleep:day-11",
"at": "anchor-11d+04:30:00",
"value": 5.5,
"secondary": null,
"sleepStages": {
"CORE": 3,
"DEEP": 1,
"REM": 1.5,
"AWAKE": 0.17
}
},
{
"id": "sleep:day-10",
"at": "anchor-10d+04:30:00",
"value": 5.5,
"secondary": null,
"sleepStages": {
"CORE": 3,
"DEEP": 1,
"REM": 1.5,
"AWAKE": 0.17
}
},
{
"id": "sleep:day-9",
"at": "anchor-9d+04:30:00",
"value": 5.5,
"secondary": null,
"sleepStages": {
"CORE": 3,
"DEEP": 1,
"REM": 1.5,
"AWAKE": 0.17
}
},
{
"id": "sleep:day-8",
"at": "anchor-8d+04:30:00",
"value": 5.5,
"secondary": null,
"sleepStages": {
"CORE": 3,
"DEEP": 1,
"REM": 1.5,
"AWAKE": 0.17
}
},
{
"id": "sleep:day-6",
"at": "anchor-6d+04:30:00",
"value": 5.5,
"secondary": null,
"sleepStages": {
"CORE": 3,
"DEEP": 1,
"REM": 1.5,
"AWAKE": 0.17
}
},
{
"id": "sleep:day-5",
"at": "anchor-5d+04:30:00",
"value": 5.5,
"secondary": null,
"sleepStages": {
"CORE": 3,
"DEEP": 1,
"REM": 1.5,
"AWAKE": 0.17
}
},
{
"id": "sleep:day-4",
"at": "anchor-4d+04:30:00",
"value": 5.5,
"secondary": null,
"sleepStages": {
"CORE": 3,
"DEEP": 1,
"REM": 1.5,
"AWAKE": 0.17
}
},
{
"id": "sleep:day-3",
"at": "anchor-3d+04:30:00",
"value": 5.5,
"secondary": null,
"sleepStages": {
"CORE": 3,
"DEEP": 1,
"REM": 1.5,
"AWAKE": 0.17
}
},
{
"id": "sleep:day-2",
"at": "anchor-2d+04:30:00",
"value": 5.5,
"secondary": null,
"sleepStages": {
"CORE": 3,
"DEEP": 1,
"REM": 1.5,
"AWAKE": 0.17
}
},
{
"id": "sleep:day-1",
"at": "anchor-1d+04:30:00",
"value": 5.5,
"secondary": null,
"sleepStages": {
"CORE": 3,
"DEEP": 1,
"REM": 1.5,
"AWAKE": 0.17
}
}
],
"direction": {
"direction": "up-good",
"targetPosition": null,
"basis": "metric_default"
},
"change": {
"from": 5.5,
"to": 5.5,
"delta": 0,
"sentiment": "neutral"
},
"referenceLines": [],
"stats": {
"mean": 5.5,
"min": 5.5,
"max": 5.5,
"stdDev": 0,
"count": 12
}
},
"error": null
},
"events": [
{
"status": 200,
"action": {
"name": "measurements.series"
},
"meta": {
"kind": "sleep",
"days": 14,
"count": 12
},
"warnings": null,
"error": null
}
]
}
@@ -0,0 +1,171 @@
{
"status": 200,
"body": {
"data": {
"kind": "glucose",
"unit": "mmol/L",
"points": [
{
"id": "<id:1>",
"at": "anchor-20d+08:00:00",
"value": 6.4,
"secondary": null
},
{
"id": "<id:2>",
"at": "anchor-19d+08:00:00",
"value": 6.3,
"secondary": null
},
{
"id": "<id:3>",
"at": "anchor-18d+08:00:00",
"value": 6.3,
"secondary": null
},
{
"id": "<id:4>",
"at": "anchor-17d+08:00:00",
"value": 6.2,
"secondary": null
},
{
"id": "<id:5>",
"at": "anchor-16d+08:00:00",
"value": 6.2,
"secondary": null
},
{
"id": "<id:6>",
"at": "anchor-15d+08:00:00",
"value": 6.1,
"secondary": null
},
{
"id": "<id:7>",
"at": "anchor-13d+08:00:00",
"value": 6,
"secondary": null
},
{
"id": "<id:8>",
"at": "anchor-12d+08:00:00",
"value": 5.9,
"secondary": null
},
{
"id": "<id:9>",
"at": "anchor-11d+08:00:00",
"value": 5.9,
"secondary": null
},
{
"id": "<id:10>",
"at": "anchor-10d+08:00:00",
"value": 5.8,
"secondary": null
},
{
"id": "<id:11>",
"at": "anchor-9d+08:00:00",
"value": 5.8,
"secondary": null
},
{
"id": "<id:12>",
"at": "anchor-8d+08:00:00",
"value": 5.7,
"secondary": null
},
{
"id": "<id:13>",
"at": "anchor-6d+08:00:00",
"value": 5.6,
"secondary": null
},
{
"id": "<id:14>",
"at": "anchor-5d+08:00:00",
"value": 5.5,
"secondary": null
},
{
"id": "<id:15>",
"at": "anchor-4d+08:00:00",
"value": 5.5,
"secondary": null
},
{
"id": "<id:16>",
"at": "anchor-3d+08:00:00",
"value": 5.4,
"secondary": null
},
{
"id": "<id:17>",
"at": "anchor-2d+08:00:00",
"value": 5.4,
"secondary": null
},
{
"id": "<id:18>",
"at": "anchor-1d+08:00:00",
"value": 5.3,
"secondary": null
}
],
"direction": {
"direction": "up-bad",
"targetPosition": null,
"basis": "metric_default"
},
"change": {
"from": 6.4,
"to": 5.3,
"delta": -1.1,
"sentiment": "positive"
},
"referenceLines": [
{
"value": 3.9,
"kind": "lower",
"source": "clinical",
"appliesTo": "value",
"labelKey": "series.reference.clinicalLower",
"label": "Clinical lower limit"
},
{
"value": 10,
"kind": "upper",
"source": "clinical",
"appliesTo": "value",
"labelKey": "series.reference.clinicalUpper",
"label": "Clinical upper limit"
}
],
"stats": {
"mean": 5.9,
"min": 5.3,
"max": 6.4,
"stdDev": 0.3,
"count": 18
}
},
"error": null
},
"events": [
{
"status": 200,
"action": {
"name": "measurements.series"
},
"meta": {
"kind": "glucose",
"days": 30,
"count": 18
},
"warnings": null,
"error": null
}
]
}
@@ -0,0 +1,237 @@
{
"status": 200,
"body": {
"data": {
"kind": "glucose",
"unit": "mmol/L",
"points": [
{
"id": "day:day-70",
"at": "anchor-70d+00:00:00",
"value": 6.1,
"secondary": null
},
{
"id": "day:day-69",
"at": "anchor-69d+00:00:00",
"value": 6.1,
"secondary": null
},
{
"id": "day:day-68",
"at": "anchor-68d+00:00:00",
"value": 6.1,
"secondary": null
},
{
"id": "day:day-67",
"at": "anchor-67d+00:00:00",
"value": 6.1,
"secondary": null
},
{
"id": "day:day-66",
"at": "anchor-66d+00:00:00",
"value": 6.1,
"secondary": null
},
{
"id": "day:day-65",
"at": "anchor-65d+00:00:00",
"value": 6.1,
"secondary": null
},
{
"id": "day:day-64",
"at": "anchor-64d+00:00:00",
"value": 6.1,
"secondary": null
},
{
"id": "day:day-63",
"at": "anchor-63d+00:00:00",
"value": 6.1,
"secondary": null
},
{
"id": "day:day-62",
"at": "anchor-62d+00:00:00",
"value": 6.1,
"secondary": null
},
{
"id": "day:day-61",
"at": "anchor-61d+00:00:00",
"value": 6.1,
"secondary": null
},
{
"id": "day:day-60",
"at": "anchor-60d+00:00:00",
"value": 6.1,
"secondary": null
},
{
"id": "day:day-20",
"at": "anchor-20d+00:00:00",
"value": 6.4,
"secondary": null
},
{
"id": "day:day-19",
"at": "anchor-19d+00:00:00",
"value": 6.3,
"secondary": null
},
{
"id": "day:day-18",
"at": "anchor-18d+00:00:00",
"value": 6.3,
"secondary": null
},
{
"id": "day:day-17",
"at": "anchor-17d+00:00:00",
"value": 6.2,
"secondary": null
},
{
"id": "day:day-16",
"at": "anchor-16d+00:00:00",
"value": 6.2,
"secondary": null
},
{
"id": "day:day-15",
"at": "anchor-15d+00:00:00",
"value": 6.1,
"secondary": null
},
{
"id": "day:day-13",
"at": "anchor-13d+00:00:00",
"value": 6,
"secondary": null
},
{
"id": "day:day-12",
"at": "anchor-12d+00:00:00",
"value": 5.9,
"secondary": null
},
{
"id": "day:day-11",
"at": "anchor-11d+00:00:00",
"value": 5.9,
"secondary": null
},
{
"id": "day:day-10",
"at": "anchor-10d+00:00:00",
"value": 5.8,
"secondary": null
},
{
"id": "day:day-9",
"at": "anchor-9d+00:00:00",
"value": 5.8,
"secondary": null
},
{
"id": "day:day-8",
"at": "anchor-8d+00:00:00",
"value": 5.7,
"secondary": null
},
{
"id": "day:day-6",
"at": "anchor-6d+00:00:00",
"value": 5.6,
"secondary": null
},
{
"id": "day:day-5",
"at": "anchor-5d+00:00:00",
"value": 5.5,
"secondary": null
},
{
"id": "day:day-4",
"at": "anchor-4d+00:00:00",
"value": 5.5,
"secondary": null
},
{
"id": "day:day-3",
"at": "anchor-3d+00:00:00",
"value": 5.4,
"secondary": null
},
{
"id": "day:day-2",
"at": "anchor-2d+00:00:00",
"value": 5.4,
"secondary": null
},
{
"id": "day:day-1",
"at": "anchor-1d+00:00:00",
"value": 5.3,
"secondary": null
}
],
"direction": {
"direction": "up-bad",
"targetPosition": null,
"basis": "metric_default"
},
"change": {
"from": 6.1,
"to": 5.3,
"delta": -0.8,
"sentiment": "positive"
},
"referenceLines": [
{
"value": 3.9,
"kind": "lower",
"source": "clinical",
"appliesTo": "value",
"labelKey": "series.reference.clinicalLower",
"label": "Clinical lower limit"
},
{
"value": 10,
"kind": "upper",
"source": "clinical",
"appliesTo": "value",
"labelKey": "series.reference.clinicalUpper",
"label": "Clinical upper limit"
}
],
"stats": {
"mean": 6,
"min": 5.3,
"max": 6.4,
"stdDev": 0.3,
"count": 29
}
},
"error": null
},
"events": [
{
"status": 200,
"action": {
"name": "measurements.series"
},
"meta": {
"kind": "glucose",
"days": 365,
"count": 29
},
"warnings": null,
"error": null
}
]
}
@@ -0,0 +1,56 @@
{
"status": 422,
"body": {
"data": null,
"error": "Validation failed",
"details": {
"issues": [
{
"path": "kind",
"code": "invalid_value",
"message": "Invalid option: expected one of \"weight\"|\"bloodPressure\"|\"pulse\"|\"bodyFat\"|\"glucose\"|\"sleep\"|\"steps\"|\"totalBodyWater\"|\"boneMass\"|\"oxygenSaturation\"|\"restingHeartRate\"|\"heartRateVariability\"|\"vo2Max\""
},
{
"path": "days",
"code": "too_small",
"message": "Too small: expected number to be >=1"
}
]
},
"meta": {
"errorCode": "http.invalid",
"requestId": "<requestId>"
}
},
"events": [
{
"status": 422,
"action": {
"name": "measurements.series.validation-failed"
},
"meta": {
"issue_count": 2,
"received_keys": [
"kind",
"days"
],
"received_shape_excerpt": "{\"kind\":\"temperature\",\"days\":\"0\"}",
"zod_issues": [
{
"path": "kind",
"code": "invalid_value",
"message": "Invalid option: expected one of \"weight\"|\"bloodPressure\"|\"pulse\"|\"bodyFat\"|\"glucose\"|\"sleep\"|\"steps\"|\"totalBodyWater\"|\"boneMass\"|\"oxygenSaturation\"|\"restingHeartRate\"|\"heartRateVariability\"|\"vo2Max\""
},
{
"path": "days",
"code": "too_small",
"message": "Too small: expected number to be >=1"
}
],
"rejected_attempt": "measurements.series.validation-failed"
},
"warnings": null,
"error": null
}
]
}
@@ -0,0 +1,115 @@
{
"status": 200,
"body": {
"data": {
"kind": "bloodPressure",
"unit": "mmHg",
"points": [
{
"id": "<id:1>",
"at": "anchor-6d+07:01:00",
"value": 127,
"secondary": 81
},
{
"id": "<id:2>",
"at": "anchor-5d+07:01:00",
"value": 126,
"secondary": 83
},
{
"id": "<id:3>",
"at": "anchor-4d+07:01:00",
"value": 130,
"secondary": 82
},
{
"id": "<id:4>",
"at": "anchor-3d+07:01:00",
"value": 129,
"secondary": 81
},
{
"id": "<id:5>",
"at": "anchor-2d+07:01:00",
"value": 128,
"secondary": 83
},
{
"id": "<id:6>",
"at": "anchor-1d+07:01:00",
"value": 127,
"secondary": 82
}
],
"direction": {
"direction": "hold",
"targetPosition": "inside",
"basis": "profile_default"
},
"change": {
"from": 127,
"to": 127,
"delta": 0,
"sentiment": "positive"
},
"referenceLines": [
{
"value": 120,
"kind": "lower",
"source": "personal",
"appliesTo": "value",
"labelKey": "series.reference.targetLower",
"label": "Dein Ziel, Untergrenze"
},
{
"value": 129,
"kind": "upper",
"source": "personal",
"appliesTo": "value",
"labelKey": "series.reference.targetUpper",
"label": "Dein Ziel, Obergrenze"
},
{
"value": 70,
"kind": "lower",
"source": "personal",
"appliesTo": "secondary",
"labelKey": "series.reference.targetLower",
"label": "Dein Ziel, Untergrenze"
},
{
"value": 79,
"kind": "upper",
"source": "personal",
"appliesTo": "secondary",
"labelKey": "series.reference.targetUpper",
"label": "Dein Ziel, Obergrenze"
}
],
"stats": {
"mean": 127.83,
"min": 126,
"max": 130,
"stdDev": 1.34,
"count": 6
}
},
"error": null
},
"events": [
{
"status": 200,
"action": {
"name": "measurements.series"
},
"meta": {
"kind": "bloodPressure",
"days": 7,
"count": 6
},
"warnings": null,
"error": null
}
]
}
@@ -0,0 +1,308 @@
/**
* Parity for `GET /api/measurements/series` while its readers move into
* `src/lib/measurements/series/`.
*
* One account holds weight, blood pressure from two sources (with a corrupt
* systolic row), pulse with hourly spreads, a stream of per-minute pulse
* dense enough to be bucketed per hour, glucose in mmol/L preference, sleep
* stages and steps. The walk asks for each kind it reads differently: the
* plain readers, the paired blood pressure, the sleep nights, a dense kind
* inside the raw window, the hour buckets, the day buckets of a long window,
* a refused query, and a delegate reading the record in another language.
*
* The clock is NOT pinned here: the series readers cut their windows in SQL
* against the database's own `now()`, which a pinned `Date` would disagree
* with. The record is laid out relative to the start of the current UTC day
* instead, no reading sits on a window's edge day, and every instant and day
* key in the snapshot is written relative to that start, so the snapshot is
* the same on every day it runs.
*/
import { beforeAll, describe, expect, it, vi } from "vitest";
import { GET } from "@/app/api/measurements/series/route";
import type { Prisma } from "@/generated/prisma/client";
import { callRoute, type RouteAuth } from "../../../kit/call-route";
import { makeGrant, makeSession, makeUser } from "../../../kit/factories";
import {
eventFacts,
expectParity,
stable,
wideEventRecorder,
} from "../../../kit/parity";
import {
getPrismaClient,
switchSessionTo,
truncateAllTables,
} from "../../setup";
vi.mock("@/lib/logging/transports", async (importOriginal) => {
const actual =
await importOriginal<typeof import("@/lib/logging/transports")>();
const { wideEventRecorder: recorder } = await import("../../../kit/parity");
return {
...actual,
emitIfSampled: (event: (typeof recorder.events)[number]) => {
recorder.events.push(event);
},
};
});
const prisma = getPrismaClient();
const DAY = 86_400_000;
/** The start of the current UTC day: the anchor the record is laid out from. */
const ANCHOR = new Date(Math.floor(Date.now() / DAY) * DAY);
/**
* Days back the daily readings sit. The windows asked for are 7, 14, 30 and
* 365 days; a reading on exactly that many days back would fall in or out of
* the window with the time of day the test runs, so those days are skipped.
*/
const READING_DAYS = Array.from({ length: 20 }, (_, i) => i + 1).filter(
(d) => d !== 7 && d !== 14,
);
let userId: string;
let owner: RouteAuth;
let delegate: { auth: RouteAuth & { sessionId: string } };
function at(daysAgo: number, hour: number, minute = 0): Date {
return new Date(
ANCHOR.getTime() - daysAgo * DAY + (hour * 60 + minute) * 60_000,
);
}
const ISO_INSTANT = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z/g;
const DAY_KEY = /\b\d{4}-\d{2}-\d{2}\b/g;
/** An instant as days and time from the anchor: `anchor-13d+06:30`. */
function relativeInstant(iso: string): string {
const ms = Date.parse(iso) - ANCHOR.getTime();
const days = Math.floor(ms / DAY);
const rest = new Date(ms - days * DAY).toISOString().slice(11, 19);
return `anchor${days >= 0 ? "+" : ""}${days}d+${rest}`;
}
/** A day key as days from the anchor's day: `day-13`. */
function relativeDay(key: string): string {
const days = Math.round(
(Date.parse(`${key}T00:00:00Z`) - ANCHOR.getTime()) / DAY,
);
return `day${days >= 0 ? "+" : ""}${days}`;
}
function relativeDates(value: unknown): unknown {
if (typeof value === "string") {
return value
.replace(ISO_INSTANT, relativeInstant)
.replace(DAY_KEY, relativeDay);
}
if (Array.isArray(value)) return value.map(relativeDates);
if (value && typeof value === "object") {
return Object.fromEntries(
Object.entries(value).map(([k, v]) => [k, relativeDates(v)]),
);
}
return value;
}
function seedRows(): Prisma.MeasurementCreateManyInput[] {
const rows: Prisma.MeasurementCreateManyInput[] = [];
const add = (row: Omit<Prisma.MeasurementCreateManyInput, "userId">) =>
rows.push({ userId, ...row });
for (const d of READING_DAYS) {
add({
type: "WEIGHT",
value: 80 - d * 0.1,
unit: "kg",
measuredAt: at(d, 6, 30),
source: d % 2 ? "MANUAL" : "WITHINGS",
externalId: d % 2 ? null : `w-${d}`,
});
for (const [source, offset] of [
["MANUAL", 0],
["WITHINGS", 1],
] as const) {
add({
type: "BLOOD_PRESSURE_SYS",
value: 125 + (d % 5) + offset,
unit: "mmHg",
measuredAt: at(d, 7, offset),
source,
externalId: source === "WITHINGS" ? `bp-s-${d}` : null,
});
add({
type: "BLOOD_PRESSURE_DIA",
value: 80 + (d % 3) + offset,
unit: "mmHg",
measuredAt: at(d, 7, offset + 2),
source,
externalId: source === "WITHINGS" ? `bp-d-${d}` : null,
});
}
add({
type: "BLOOD_GLUCOSE",
value: 95 + d,
unit: "mg/dL",
measuredAt: at(d, 8),
source: "MANUAL",
});
add({
type: "ACTIVITY_STEPS",
value: 6_000 + d * 100,
unit: "steps",
measuredAt: at(d, 12),
source: "APPLE_HEALTH",
externalId: `stats:steps:${d}`,
aggregationProvenance: "HEALTHKIT_STATISTICS",
});
for (const [stage, start, minutes] of [
["CORE", 23, 180],
["DEEP", 2, 60],
["REM", 3, 90],
["AWAKE", 4, 10],
] as const) {
const begin = at(d, start);
if (start === 23) begin.setTime(begin.getTime() - DAY);
add({
type: "SLEEP_DURATION",
value: minutes,
unit: "min",
measuredAt: new Date(begin.getTime() + minutes * 60_000),
source: "APPLE_HEALTH",
sleepStage: stage,
externalId: `sleep-${d}-${stage}`,
});
}
for (const hour of [9, 14, 20]) {
add({
type: "PULSE",
value: 60 + hour,
valueMin: 55 + hour,
valueMax: 70 + hour,
unit: "bpm",
measuredAt: at(d, hour),
source: "APPLE_HEALTH",
externalId: `stats:hr:${d}:${hour}`,
aggregationProvenance: "HEALTHKIT_STATISTICS",
});
}
}
// A seed-era corrupt systolic the series must never surface.
add({
type: "BLOOD_PRESSURE_SYS",
value: 0,
unit: "mmHg",
measuredAt: at(2, 9),
source: "MANUAL",
});
// Two months back, so a long window has more than the raw window holds.
for (let d = 60; d <= 70; d++) {
add({
type: "BLOOD_GLUCOSE",
value: 110,
unit: "mg/dL",
measuredAt: at(d, 8),
source: "MANUAL",
});
}
return rows;
}
/** A per-minute heart-rate stream dense enough to be bucketed per hour. */
function denseHeartRate(): Prisma.MeasurementCreateManyInput[] {
return Array.from({ length: 10_200 }, (_, i) => ({
userId,
type: "HEART_RATE_VARIABILITY",
value: 40 + (i % 17),
unit: "ms",
measuredAt: new Date(ANCHOR.getTime() - (i + 1) * 60_000),
source: "APPLE_HEALTH",
externalId: `hrv-${i}`,
}));
}
async function step(
name: string,
query: Record<string, string | number>,
auth: RouteAuth = owner,
): Promise<void> {
wideEventRecorder.take();
const res = await callRoute(GET, {
method: "GET",
route: "/api/measurements/series",
query,
auth,
});
const events = wideEventRecorder.take();
expect(events.length).toBeGreaterThan(0);
await expectParity(
`measurement-series.${name}`,
stable(
relativeDates({
status: res.status,
body: res.body,
events: events.map(eventFacts),
}),
{ since: new Date() },
),
);
}
beforeAll(async () => {
await truncateAllTables(prisma);
const user = await makeUser({
// UTC, so a daylight-saving change between two runs cannot move a local
// day boundary relative to the anchor.
timezone: "UTC",
glucoseUnit: "mmol/L",
dateOfBirth: new Date("1970-01-01T00:00:00.000Z"),
locale: "en",
});
userId = user.id;
owner = await makeSession(user);
await prisma.measurement.createMany({ data: seedRows() });
await prisma.measurement.createMany({ data: denseHeartRate() });
const reader = await makeUser({ timezone: "UTC", locale: "de" });
await makeGrant(user, reader);
const session = await makeSession(reader);
delegate = { auth: session };
});
describe("GET /api/measurements/series parity", () => {
it("the plain readers", async () => {
await step("01-weight", { kind: "weight" });
await step("02-steps", { kind: "steps", days: 14 });
await step("03-pulse-spread", { kind: "pulse", days: 7 });
});
it("the paired blood pressure and the sleep nights", async () => {
await step("04-blood-pressure", { kind: "bloodPressure" });
await step("05-sleep", { kind: "sleep", days: 14 });
});
it("glucose raw, and day-bucketed past the raw window", async () => {
await step("06-glucose-raw", { kind: "glucose" });
await step("07-glucose-long", { kind: "glucose", days: 365 });
});
it("a dense stream bucketed per hour", async () => {
await step("08-hrv-hour-buckets", {
kind: "heartRateVariability",
days: 30,
});
});
it("a refused query", async () => {
await step("09-invalid", { kind: "temperature", days: 0 });
});
it("a delegate reading the record in another language", async () => {
await switchSessionTo(delegate.auth.sessionId, userId);
await step(
"10-delegated",
{ kind: "bloodPressure", days: 7 },
delegate.auth,
);
await switchSessionTo(delegate.auth.sessionId, null);
});
});