test(import): pin the JSON import before moving its writers

This commit is contained in:
Marc-André Bombeck committed 2026-10-23 14:48:51 +02:00
1 parent 7a21796b63
commit ed12625ab6
5 files changed
+1053

No files matched your search

@@ -0,0 +1,205 @@
{
"status": 200,
"body": {
"data": {
"measurements": 4,
"moodEntries": 4,
"skipped": 2
},
"error": null
},
"events": [
{
"status": 200,
"action": {
"name": "import.upload"
},
"meta": {
"import_stats": {
"measurements": 4,
"moodEntries": 4,
"skipped": 2
},
"external_id_rejected": 2,
"external_id_shapes": "object_description",
"external_id_surface": "import.json"
},
"warnings": null,
"error": null
}
],
"effects": [
{
"effect": "emitDataArrival",
"args": [
{
"userId": "<id:1>",
"kind": "weight",
"newestSampleAt": "2026-10-11T06:30:00.000Z",
"insertedCount": 2,
"refId": "<id:2>",
"source": "json_import"
}
]
},
{
"effect": "maybeEnqueueMorningRefresh",
"args": [
"<id:1>",
[
"2026-10-12T06:00:00.000Z"
]
]
}
],
"stored": {
"measurements": [
{
"type": "WEIGHT",
"value": 81.4,
"unit": "kg",
"source": "IMPORT",
"externalId": "exp-w-1",
"measuredAt": "2026-10-10T06:30:00.000Z",
"notes": null,
"notesEncrypted": "<bytes:59>",
"glucoseContext": null
},
{
"type": "WEIGHT",
"value": 81.9,
"unit": "kg",
"source": "IMPORT",
"externalId": null,
"measuredAt": "2026-10-11T06:30:00.000Z",
"notes": null,
"notesEncrypted": null,
"glucoseContext": null
},
{
"type": "BLOOD_GLUCOSE",
"value": 104,
"unit": "mg/dL",
"source": "IMPORT",
"externalId": "exp-g-1",
"measuredAt": "2026-10-11T08:00:00.000Z",
"notes": null,
"notesEncrypted": null,
"glucoseContext": "FASTING"
},
{
"type": "SLEEP_DURATION",
"value": 412,
"unit": "min",
"source": "IMPORT",
"externalId": "exp-s-1",
"measuredAt": "2026-10-12T06:00:00.000Z",
"notes": null,
"notesEncrypted": null,
"glucoseContext": null
}
],
"mood": [
{
"date": "2026-10-10",
"mood": "GUT",
"score": 4,
"moodA1": 7,
"tags": "slept well",
"source": "IMPORT",
"externalId": "daylio-1",
"moodLoggedAt": "2026-10-10T07:00:00.000Z"
},
{
"date": "2026-10-11",
"mood": "OKAY",
"score": 3,
"moodA1": 5,
"tags": null,
"source": "IMPORT",
"externalId": null,
"moodLoggedAt": "2026-10-10T22:02:08.803Z"
},
{
"date": "2026-10-11",
"mood": "OKAY",
"score": 3,
"moodA1": 5,
"tags": null,
"source": "IMPORT",
"externalId": null,
"moodLoggedAt": "2026-10-10T22:02:32.701Z"
},
{
"date": "2026-10-12",
"mood": "SCHLECHT",
"score": 2,
"moodA1": 3,
"tags": null,
"source": "IMPORT",
"externalId": null,
"moodLoggedAt": "2026-10-12T20:00:00.000Z"
}
],
"measurementRollups": [
{
"type": "WEIGHT",
"granularity": "DAY",
"bucketStart": "2026-10-10T00:00:00.000Z",
"source": "IMPORT",
"count": 1,
"mean": 81.4
},
{
"type": "WEIGHT",
"granularity": "DAY",
"bucketStart": "2026-10-11T00:00:00.000Z",
"source": "IMPORT",
"count": 1,
"mean": 81.9
},
{
"type": "SLEEP_DURATION",
"granularity": "DAY",
"bucketStart": "2026-10-12T00:00:00.000Z",
"source": "IMPORT",
"count": 1,
"mean": 412
},
{
"type": "BLOOD_GLUCOSE",
"granularity": "DAY",
"bucketStart": "2026-10-11T00:00:00.000Z",
"source": "IMPORT",
"count": 1,
"mean": 104
}
],
"moodRollups": [
{
"granularity": "DAY",
"bucketStart": "2026-10-10T00:00:00.000Z",
"count": 1,
"mean": 4
},
{
"granularity": "DAY",
"bucketStart": "2026-10-11T00:00:00.000Z",
"count": 2,
"mean": 3
},
{
"granularity": "DAY",
"bucketStart": "2026-10-12T00:00:00.000Z",
"count": 1,
"mean": 2
}
],
"audit": [
{
"action": "import.upload",
"details": "{\"measurements\":4,\"moodEntries\":4,\"skipped\":2}"
}
]
}
}
@@ -0,0 +1,194 @@
{
"status": 200,
"body": {
"data": {
"measurements": 3,
"moodEntries": 1,
"skipped": 6
},
"error": null
},
"events": [
{
"status": 200,
"action": {
"name": "import.upload"
},
"meta": {
"import_stats": {
"measurements": 3,
"moodEntries": 1,
"skipped": 6
},
"external_id_rejected": 2,
"external_id_shapes": "object_description",
"external_id_surface": "import.json"
},
"warnings": null,
"error": null
}
],
"effects": [
{
"effect": "maybeEnqueueMorningRefresh",
"args": [
"<id:1>",
[]
]
}
],
"stored": {
"measurements": [
{
"type": "WEIGHT",
"value": 81.4,
"unit": "kg",
"source": "IMPORT",
"externalId": "exp-w-1",
"measuredAt": "2026-10-10T06:30:00.000Z",
"notes": null,
"notesEncrypted": "<bytes:59>",
"glucoseContext": null
},
{
"type": "WEIGHT",
"value": 81.9,
"unit": "kg",
"source": "IMPORT",
"externalId": null,
"measuredAt": "2026-10-11T06:30:00.000Z",
"notes": null,
"notesEncrypted": null,
"glucoseContext": null
},
{
"type": "BLOOD_GLUCOSE",
"value": 104,
"unit": "mg/dL",
"source": "IMPORT",
"externalId": "exp-g-1",
"measuredAt": "2026-10-11T08:00:00.000Z",
"notes": null,
"notesEncrypted": null,
"glucoseContext": "FASTING"
},
{
"type": "SLEEP_DURATION",
"value": 412,
"unit": "min",
"source": "IMPORT",
"externalId": "exp-s-1",
"measuredAt": "2026-10-12T06:00:00.000Z",
"notes": null,
"notesEncrypted": null,
"glucoseContext": null
}
],
"mood": [
{
"date": "2026-10-10",
"mood": "GUT",
"score": 4,
"moodA1": 7,
"tags": "slept well",
"source": "IMPORT",
"externalId": "daylio-1",
"moodLoggedAt": "2026-10-10T07:00:00.000Z"
},
{
"date": "2026-10-11",
"mood": "OKAY",
"score": 3,
"moodA1": 5,
"tags": null,
"source": "IMPORT",
"externalId": null,
"moodLoggedAt": "2026-10-10T22:02:08.803Z"
},
{
"date": "2026-10-11",
"mood": "OKAY",
"score": 3,
"moodA1": 5,
"tags": null,
"source": "IMPORT",
"externalId": null,
"moodLoggedAt": "2026-10-10T22:02:32.701Z"
},
{
"date": "2026-10-12",
"mood": "SCHLECHT",
"score": 2,
"moodA1": 3,
"tags": null,
"source": "IMPORT",
"externalId": null,
"moodLoggedAt": "2026-10-12T20:00:00.000Z"
}
],
"measurementRollups": [
{
"type": "WEIGHT",
"granularity": "DAY",
"bucketStart": "2026-10-10T00:00:00.000Z",
"source": "IMPORT",
"count": 1,
"mean": 81.4
},
{
"type": "WEIGHT",
"granularity": "DAY",
"bucketStart": "2026-10-11T00:00:00.000Z",
"source": "IMPORT",
"count": 1,
"mean": 81.9
},
{
"type": "SLEEP_DURATION",
"granularity": "DAY",
"bucketStart": "2026-10-12T00:00:00.000Z",
"source": "IMPORT",
"count": 1,
"mean": 412
},
{
"type": "BLOOD_GLUCOSE",
"granularity": "DAY",
"bucketStart": "2026-10-11T00:00:00.000Z",
"source": "IMPORT",
"count": 1,
"mean": 104
}
],
"moodRollups": [
{
"granularity": "DAY",
"bucketStart": "2026-10-10T00:00:00.000Z",
"count": 1,
"mean": 4
},
{
"granularity": "DAY",
"bucketStart": "2026-10-11T00:00:00.000Z",
"count": 2,
"mean": 3
},
{
"granularity": "DAY",
"bucketStart": "2026-10-12T00:00:00.000Z",
"count": 1,
"mean": 2
}
],
"audit": [
{
"action": "import.upload",
"details": "{\"measurements\":4,\"moodEntries\":4,\"skipped\":2}"
},
{
"action": "import.upload",
"details": "{\"measurements\":3,\"moodEntries\":1,\"skipped\":6}"
}
]
}
}
@@ -0,0 +1,186 @@
{
"status": 422,
"body": {
"data": null,
"error": "Wert ausserhalb des plausiblen Bereichs",
"details": {
"issues": [
{
"path": "measurements.0",
"code": "custom",
"message": "Wert ausserhalb des plausiblen Bereichs"
}
]
},
"meta": {
"errorCode": "http.invalid",
"requestId": "<requestId>"
}
},
"events": [
{
"status": 422,
"action": {
"name": "import.upload"
},
"meta": null,
"warnings": null,
"error": null
}
],
"effects": [],
"stored": {
"measurements": [
{
"type": "WEIGHT",
"value": 81.4,
"unit": "kg",
"source": "IMPORT",
"externalId": "exp-w-1",
"measuredAt": "2026-10-10T06:30:00.000Z",
"notes": null,
"notesEncrypted": "<bytes:59>",
"glucoseContext": null
},
{
"type": "WEIGHT",
"value": 81.9,
"unit": "kg",
"source": "IMPORT",
"externalId": null,
"measuredAt": "2026-10-11T06:30:00.000Z",
"notes": null,
"notesEncrypted": null,
"glucoseContext": null
},
{
"type": "BLOOD_GLUCOSE",
"value": 104,
"unit": "mg/dL",
"source": "IMPORT",
"externalId": "exp-g-1",
"measuredAt": "2026-10-11T08:00:00.000Z",
"notes": null,
"notesEncrypted": null,
"glucoseContext": "FASTING"
},
{
"type": "SLEEP_DURATION",
"value": 412,
"unit": "min",
"source": "IMPORT",
"externalId": "exp-s-1",
"measuredAt": "2026-10-12T06:00:00.000Z",
"notes": null,
"notesEncrypted": null,
"glucoseContext": null
}
],
"mood": [
{
"date": "2026-10-10",
"mood": "GUT",
"score": 4,
"moodA1": 7,
"tags": "slept well",
"source": "IMPORT",
"externalId": "daylio-1",
"moodLoggedAt": "2026-10-10T07:00:00.000Z"
},
{
"date": "2026-10-11",
"mood": "OKAY",
"score": 3,
"moodA1": 5,
"tags": null,
"source": "IMPORT",
"externalId": null,
"moodLoggedAt": "2026-10-10T22:02:08.803Z"
},
{
"date": "2026-10-11",
"mood": "OKAY",
"score": 3,
"moodA1": 5,
"tags": null,
"source": "IMPORT",
"externalId": null,
"moodLoggedAt": "2026-10-10T22:02:32.701Z"
},
{
"date": "2026-10-12",
"mood": "SCHLECHT",
"score": 2,
"moodA1": 3,
"tags": null,
"source": "IMPORT",
"externalId": null,
"moodLoggedAt": "2026-10-12T20:00:00.000Z"
}
],
"measurementRollups": [
{
"type": "WEIGHT",
"granularity": "DAY",
"bucketStart": "2026-10-10T00:00:00.000Z",
"source": "IMPORT",
"count": 1,
"mean": 81.4
},
{
"type": "WEIGHT",
"granularity": "DAY",
"bucketStart": "2026-10-11T00:00:00.000Z",
"source": "IMPORT",
"count": 1,
"mean": 81.9
},
{
"type": "SLEEP_DURATION",
"granularity": "DAY",
"bucketStart": "2026-10-12T00:00:00.000Z",
"source": "IMPORT",
"count": 1,
"mean": 412
},
{
"type": "BLOOD_GLUCOSE",
"granularity": "DAY",
"bucketStart": "2026-10-11T00:00:00.000Z",
"source": "IMPORT",
"count": 1,
"mean": 104
}
],
"moodRollups": [
{
"granularity": "DAY",
"bucketStart": "2026-10-10T00:00:00.000Z",
"count": 1,
"mean": 4
},
{
"granularity": "DAY",
"bucketStart": "2026-10-11T00:00:00.000Z",
"count": 2,
"mean": 3
},
{
"granularity": "DAY",
"bucketStart": "2026-10-12T00:00:00.000Z",
"count": 1,
"mean": 2
}
],
"audit": [
{
"action": "import.upload",
"details": "{\"measurements\":4,\"moodEntries\":4,\"skipped\":2}"
},
{
"action": "import.upload",
"details": "{\"measurements\":3,\"moodEntries\":1,\"skipped\":6}"
}
]
}
}
@@ -0,0 +1,186 @@
{
"status": 422,
"body": {
"data": null,
"error": "Timestamp must not be in the future",
"details": {
"issues": [
{
"path": "moodEntries.0.loggedAt",
"code": "custom",
"message": "Timestamp must not be in the future"
}
]
},
"meta": {
"errorCode": "http.invalid",
"requestId": "<requestId>"
}
},
"events": [
{
"status": 422,
"action": {
"name": "import.upload"
},
"meta": null,
"warnings": null,
"error": null
}
],
"effects": [],
"stored": {
"measurements": [
{
"type": "WEIGHT",
"value": 81.4,
"unit": "kg",
"source": "IMPORT",
"externalId": "exp-w-1",
"measuredAt": "2026-10-10T06:30:00.000Z",
"notes": null,
"notesEncrypted": "<bytes:59>",
"glucoseContext": null
},
{
"type": "WEIGHT",
"value": 81.9,
"unit": "kg",
"source": "IMPORT",
"externalId": null,
"measuredAt": "2026-10-11T06:30:00.000Z",
"notes": null,
"notesEncrypted": null,
"glucoseContext": null
},
{
"type": "BLOOD_GLUCOSE",
"value": 104,
"unit": "mg/dL",
"source": "IMPORT",
"externalId": "exp-g-1",
"measuredAt": "2026-10-11T08:00:00.000Z",
"notes": null,
"notesEncrypted": null,
"glucoseContext": "FASTING"
},
{
"type": "SLEEP_DURATION",
"value": 412,
"unit": "min",
"source": "IMPORT",
"externalId": "exp-s-1",
"measuredAt": "2026-10-12T06:00:00.000Z",
"notes": null,
"notesEncrypted": null,
"glucoseContext": null
}
],
"mood": [
{
"date": "2026-10-10",
"mood": "GUT",
"score": 4,
"moodA1": 7,
"tags": "slept well",
"source": "IMPORT",
"externalId": "daylio-1",
"moodLoggedAt": "2026-10-10T07:00:00.000Z"
},
{
"date": "2026-10-11",
"mood": "OKAY",
"score": 3,
"moodA1": 5,
"tags": null,
"source": "IMPORT",
"externalId": null,
"moodLoggedAt": "2026-10-10T22:02:08.803Z"
},
{
"date": "2026-10-11",
"mood": "OKAY",
"score": 3,
"moodA1": 5,
"tags": null,
"source": "IMPORT",
"externalId": null,
"moodLoggedAt": "2026-10-10T22:02:32.701Z"
},
{
"date": "2026-10-12",
"mood": "SCHLECHT",
"score": 2,
"moodA1": 3,
"tags": null,
"source": "IMPORT",
"externalId": null,
"moodLoggedAt": "2026-10-12T20:00:00.000Z"
}
],
"measurementRollups": [
{
"type": "WEIGHT",
"granularity": "DAY",
"bucketStart": "2026-10-10T00:00:00.000Z",
"source": "IMPORT",
"count": 1,
"mean": 81.4
},
{
"type": "WEIGHT",
"granularity": "DAY",
"bucketStart": "2026-10-11T00:00:00.000Z",
"source": "IMPORT",
"count": 1,
"mean": 81.9
},
{
"type": "SLEEP_DURATION",
"granularity": "DAY",
"bucketStart": "2026-10-12T00:00:00.000Z",
"source": "IMPORT",
"count": 1,
"mean": 412
},
{
"type": "BLOOD_GLUCOSE",
"granularity": "DAY",
"bucketStart": "2026-10-11T00:00:00.000Z",
"source": "IMPORT",
"count": 1,
"mean": 104
}
],
"moodRollups": [
{
"granularity": "DAY",
"bucketStart": "2026-10-10T00:00:00.000Z",
"count": 1,
"mean": 4
},
{
"granularity": "DAY",
"bucketStart": "2026-10-11T00:00:00.000Z",
"count": 2,
"mean": 3
},
{
"granularity": "DAY",
"bucketStart": "2026-10-12T00:00:00.000Z",
"count": 1,
"mean": 2
}
],
"audit": [
{
"action": "import.upload",
"details": "{\"measurements\":4,\"moodEntries\":4,\"skipped\":2}"
},
{
"action": "import.upload",
"details": "{\"measurements\":3,\"moodEntries\":1,\"skipped\":6}"
}
]
}
}
@@ -0,0 +1,282 @@
/**
* Parity for `POST /api/import` while its writers move into
* `src/lib/import/json/`.
*
* A first file of mixed rows (measurements with and without an external id,
* with a note and a glucose context, an unstable id; mood entries with and
* without a logged instant, a twin pair that has to land on two distinct
* fallback instants, an external id, an unstable id), the same file again,
* and the whole-file refusals. After each request the snapshot holds the
* response, the wide event, the recorded arrival and refresh tails, and the
* stored measurements, mood entries, rollups and audit rows.
*/
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import { POST } from "@/app/api/import/route";
import { callRoute, type RouteAuth } from "../../../kit/call-route";
import { makeSession, makeUser } from "../../../kit/factories";
import {
effectRecorder,
eventFacts,
expectParity,
stable,
wideEventRecorder,
} from "../../../kit/parity";
import { getPrismaClient, truncateAllTables } from "../../setup";
vi.mock("@/lib/logging/transports", async (importOriginal) => {
const actual =
await importOriginal<typeof import("@/lib/logging/transports")>();
const { wideEventRecorder: recorder } = await import("../../../kit/parity");
return {
...actual,
emitIfSampled: (event: (typeof recorder.events)[number]) => {
recorder.events.push(event);
},
};
});
vi.mock("@/lib/arrivals/emit-shared", async (importOriginal) => {
const actual =
await importOriginal<typeof import("@/lib/arrivals/emit-shared")>();
const { effectRecorder } = await import("../../../kit/parity");
return {
...actual,
emitDataArrival: async (...args: unknown[]) => {
effectRecorder.record("emitDataArrival", args);
},
};
});
vi.mock("@/lib/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/pr-detection", async (importOriginal) => {
const actual =
await importOriginal<typeof import("@/lib/jobs/pr-detection")>();
const { effectRecorder } = await import("../../../kit/parity");
return {
...actual,
enqueuePrDetection: async (...args: unknown[]) => {
effectRecorder.record("enqueuePrDetection", args);
},
};
});
const prisma = getPrismaClient();
const NOW = new Date("2026-10-15T12:00:00.000Z");
let userId: string;
let owner: RouteAuth;
async function stored() {
const [measurements, mood, measurementRollups, moodRollups, audit] =
await Promise.all([
prisma.measurement.findMany({
where: { userId },
orderBy: [{ measuredAt: "asc" }, { type: "asc" }, { value: "asc" }],
select: {
type: true,
value: true,
unit: true,
source: true,
externalId: true,
measuredAt: true,
notes: true,
notesEncrypted: true,
glucoseContext: true,
},
}),
prisma.moodEntry.findMany({
where: { userId },
orderBy: [{ moodLoggedAt: "asc" }, { mood: "asc" }],
select: {
date: true,
mood: true,
score: true,
moodA1: true,
tags: true,
source: true,
externalId: true,
moodLoggedAt: true,
},
}),
prisma.measurementRollup.findMany({
where: { userId },
orderBy: [
{ type: "asc" },
{ granularity: "asc" },
{ bucketStart: "asc" },
],
select: {
type: true,
granularity: true,
bucketStart: true,
source: true,
count: true,
mean: true,
},
}),
prisma.moodEntryRollup.findMany({
where: { userId },
orderBy: [{ granularity: "asc" }, { bucketStart: "asc" }],
select: {
granularity: true,
bucketStart: true,
count: true,
mean: true,
},
}),
prisma.auditLog.findMany({
where: { userId },
orderBy: { createdAt: "asc" },
select: { action: true, details: true },
}),
]);
return { measurements, mood, measurementRollups, moodRollups, audit };
}
async function step(name: string, body: unknown): Promise<void> {
wideEventRecorder.take();
effectRecorder.take();
const res = await callRoute(POST, {
method: "POST",
route: "/api/import",
body,
auth: owner,
});
const events = wideEventRecorder.take();
expect(events.length).toBeGreaterThan(0);
await expectParity(
`import-json.${name}`,
stable(
{
status: res.status,
body: res.body,
events: events.map(eventFacts),
effects: effectRecorder.take(),
stored: await stored(),
},
{ since: NOW },
),
);
}
const FILE = {
measurements: [
{
type: "WEIGHT",
value: 81.4,
unit: "kg",
measuredAt: "2026-10-10T06:30:00.000Z",
notes: "after the run",
externalId: "exp-w-1",
},
{
type: "WEIGHT",
value: 81.9,
unit: "kg",
measuredAt: "2026-10-11T06:30:00.000Z",
},
{
type: "BLOOD_GLUCOSE",
value: 104,
unit: "mg/dL",
measuredAt: "2026-10-11T08:00:00.000Z",
glucoseContext: "FASTING",
externalId: "exp-g-1",
},
{
type: "SLEEP_DURATION",
value: 412,
unit: "min",
measuredAt: "2026-10-12T06:00:00.000Z",
externalId: "exp-s-1",
},
{
type: "PULSE",
value: 64,
unit: "bpm",
measuredAt: "2026-10-12T07:00:00.000Z",
externalId: "<HKQuantitySample: 0x600001b4c2a0>",
},
],
moodEntries: [
{
date: "2026-10-10",
mood: "GUT",
score: 4,
tags: "slept well",
loggedAt: "2026-10-10T07:00:00.000Z",
externalId: "daylio-1",
},
{ date: "2026-10-11", mood: "OKAY", score: 3 },
{ date: "2026-10-11", mood: "OKAY", score: 3 },
{
date: "2026-10-12",
mood: "SCHLECHT",
score: 2,
loggedAt: "2026-10-12T20:00:00.000Z",
},
{
date: "2026-10-13",
mood: "GUT",
score: 5,
externalId: "<MoodEntry: 0x600001b4c2a0>",
},
],
};
beforeAll(async () => {
vi.useFakeTimers({ toFake: ["Date"], now: NOW });
await truncateAllTables(prisma);
const user = await makeUser({ timezone: "Europe/Berlin" });
userId = user.id;
owner = await makeSession(user);
});
afterAll(() => {
vi.useRealTimers();
});
describe("POST /api/import parity", () => {
it("01 a first file", async () => {
await step("01-first-file", FILE);
});
it("02 the same file again", async () => {
await step("02-same-file", FILE);
});
it("03 the whole-file refusals", async () => {
await step("03-out-of-range", {
measurements: [
{
type: "WEIGHT",
value: 4_000,
unit: "kg",
measuredAt: "2026-10-10T06:30:00.000Z",
},
],
});
await step("04-forward-dated", {
moodEntries: [
{
date: "2026-10-20",
mood: "GUT",
score: 4,
loggedAt: "2026-10-20T07:00:00.000Z",
},
],
});
});
});