test(backup): fill the parity account by key order and match audit rows by copy

A table read without an order comes back in whatever order its last
updates left on disk, so the column fill could pick a different row of a
two-row table from run to run. The audit rows of a restore are now found
by the copy's id instead of by time, since the database clock and the
test's clock do not agree to the millisecond. The golden copies are
written again from the code before the restore and export moved.
This commit is contained in:
Marc-André Bombeck committed 2026-10-22 07:45:19 +02:00
1 parent e7b31fe8e0
commit 508e19f190
7 files changed
+85 -76

No files matched your search

@@ -249,20 +249,20 @@
"id": "<id:4>",
"type": "WEIGHT",
"value": 74.2,
"valueMin": null,
"valueMax": null,
"valueMin": 2.5,
"valueMax": 2.5,
"unit": "kg",
"measuredAt": "2026-07-01T07:00:00.000Z",
"source": "MANUAL",
"notes": null,
"notesEncrypted": null,
"notes": "rt-Measurement-notes",
"notesEncrypted": "<sealed:rt-Measurement-notesEncrypted>",
"externalId": null,
"externalSourceVersion": null,
"aggregationProvenance": null,
"externalSourceVersion": "rt-Measurement-externalSourceVersion",
"aggregationProvenance": "HEALTHKIT_STATISTICS",
"glucoseContext": null,
"sleepStage": null,
"rhythmClassification": null,
"deviceType": null,
"deviceType": "rt-Measurement-deviceType",
"syncVersion": 1,
"deletedAt": null,
"createdAt": "<now>",
@@ -272,20 +272,20 @@
"id": "<id:5>",
"type": "IRREGULAR_RHYTHM_NOTIFICATION",
"value": 1,
"valueMin": 2.5,
"valueMax": 2.5,
"valueMin": null,
"valueMax": null,
"unit": "event",
"measuredAt": "2026-06-15T14:22:00.000Z",
"source": "WITHINGS",
"notes": "rt-Measurement-notes",
"notesEncrypted": "<sealed:rt-Measurement-notesEncrypted>",
"notes": null,
"notesEncrypted": null,
"externalId": null,
"externalSourceVersion": "rt-Measurement-externalSourceVersion",
"aggregationProvenance": "HEALTHKIT_STATISTICS",
"externalSourceVersion": null,
"aggregationProvenance": null,
"glucoseContext": null,
"sleepStage": null,
"rhythmClassification": "IRREGULAR",
"deviceType": "rt-Measurement-deviceType",
"deviceType": null,
"syncVersion": 1,
"deletedAt": null,
"createdAt": "<now>",
@@ -2268,20 +2268,20 @@
"id": "<id:4>",
"type": "WEIGHT",
"value": 74.2,
"valueMin": null,
"valueMax": null,
"valueMin": 2.5,
"valueMax": 2.5,
"unit": "kg",
"measuredAt": "2026-07-01T07:00:00.000Z",
"source": "MANUAL",
"notes": null,
"notesEncrypted": null,
"notes": "rt-Measurement-notes",
"notesEncrypted": "<sealed:rt-Measurement-notesEncrypted>",
"externalId": null,
"externalSourceVersion": null,
"aggregationProvenance": null,
"externalSourceVersion": "rt-Measurement-externalSourceVersion",
"aggregationProvenance": "HEALTHKIT_STATISTICS",
"glucoseContext": null,
"sleepStage": null,
"rhythmClassification": null,
"deviceType": null,
"deviceType": "rt-Measurement-deviceType",
"syncVersion": 1,
"deletedAt": null,
"createdAt": "<now>",
@@ -2291,20 +2291,20 @@
"id": "<id:5>",
"type": "IRREGULAR_RHYTHM_NOTIFICATION",
"value": 1,
"valueMin": 2.5,
"valueMax": 2.5,
"valueMin": null,
"valueMax": null,
"unit": "event",
"measuredAt": "2026-06-15T14:22:00.000Z",
"source": "WITHINGS",
"notes": "rt-Measurement-notes",
"notesEncrypted": "<sealed:rt-Measurement-notesEncrypted>",
"notes": null,
"notesEncrypted": null,
"externalId": null,
"externalSourceVersion": "rt-Measurement-externalSourceVersion",
"aggregationProvenance": "HEALTHKIT_STATISTICS",
"externalSourceVersion": null,
"aggregationProvenance": null,
"glucoseContext": null,
"sleepStage": null,
"rhythmClassification": "IRREGULAR",
"deviceType": "rt-Measurement-deviceType",
"deviceType": null,
"syncVersion": 1,
"deletedAt": null,
"createdAt": "<now>",
@@ -142,7 +142,7 @@
"unit": "kg",
"measuredAt": "2026-07-01T07:00:00.000Z",
"source": "MANUAL",
"notes": null,
"notes": "rt-Measurement-notesEncrypted",
"deletedAt": null
},
{
@@ -152,7 +152,7 @@
"unit": "event",
"measuredAt": "2026-06-15T14:22:00.000Z",
"source": "WITHINGS",
"notes": "rt-Measurement-notesEncrypted",
"notes": null,
"deletedAt": null
}
],
@@ -1763,7 +1763,7 @@
"unit": "kg",
"measuredAt": "2026-07-01T07:00:00.000Z",
"source": "MANUAL",
"notes": null,
"notes": "rt-Measurement-notesEncrypted",
"deletedAt": null
},
{
@@ -1773,7 +1773,7 @@
"unit": "event",
"measuredAt": "2026-06-15T14:22:00.000Z",
"source": "WITHINGS",
"notes": "rt-Measurement-notesEncrypted",
"notes": null,
"deletedAt": null
}
],
@@ -435,20 +435,20 @@
"userId": "round-trip-owner",
"type": "IRREGULAR_RHYTHM_NOTIFICATION",
"value": 1,
"valueMin": 2.5,
"valueMax": 2.5,
"valueMin": null,
"valueMax": null,
"unit": "event",
"source": "WITHINGS",
"measuredAt": "2026-06-15T14:22:00.000Z",
"notes": null,
"notesEncrypted": "<sealed:rt-Measurement-notesEncrypted>",
"notesEncrypted": null,
"externalId": null,
"externalSourceVersion": "rt-Measurement-externalSourceVersion",
"aggregationProvenance": "HEALTHKIT_STATISTICS",
"externalSourceVersion": null,
"aggregationProvenance": null,
"glucoseContext": null,
"sleepStage": null,
"rhythmClassification": "IRREGULAR",
"deviceType": "rt-Measurement-deviceType",
"deviceType": null,
"syncVersion": 1,
"deletedAt": null,
"createdAt": "<now>",
@@ -507,20 +507,20 @@
"userId": "round-trip-owner",
"type": "WEIGHT",
"value": 74.2,
"valueMin": null,
"valueMax": null,
"valueMin": 2.5,
"valueMax": 2.5,
"unit": "kg",
"source": "MANUAL",
"measuredAt": "2026-07-01T07:00:00.000Z",
"notes": null,
"notesEncrypted": null,
"notesEncrypted": "<sealed:rt-Measurement-notesEncrypted>",
"externalId": null,
"externalSourceVersion": null,
"aggregationProvenance": null,
"externalSourceVersion": "rt-Measurement-externalSourceVersion",
"aggregationProvenance": "HEALTHKIT_STATISTICS",
"glucoseContext": null,
"sleepStage": null,
"rhythmClassification": null,
"deviceType": null,
"deviceType": "rt-Measurement-deviceType",
"syncVersion": 1,
"deletedAt": null,
"createdAt": "<now>",
@@ -2755,20 +2755,20 @@
"id": "<id:4>",
"type": "WEIGHT",
"value": 74.2,
"valueMin": null,
"valueMax": null,
"valueMin": 2.5,
"valueMax": 2.5,
"unit": "kg",
"measuredAt": "2026-07-01T07:00:00.000Z",
"source": "MANUAL",
"notes": null,
"notesEncrypted": null,
"notesEncrypted": "<sealed:rt-Measurement-notesEncrypted>",
"externalId": null,
"externalSourceVersion": null,
"aggregationProvenance": null,
"externalSourceVersion": "rt-Measurement-externalSourceVersion",
"aggregationProvenance": "HEALTHKIT_STATISTICS",
"glucoseContext": null,
"sleepStage": null,
"rhythmClassification": null,
"deviceType": null,
"deviceType": "rt-Measurement-deviceType",
"syncVersion": 1,
"deletedAt": null,
"createdAt": "<now>",
@@ -2778,20 +2778,20 @@
"id": "<id:5>",
"type": "IRREGULAR_RHYTHM_NOTIFICATION",
"value": 1,
"valueMin": 2.5,
"valueMax": 2.5,
"valueMin": null,
"valueMax": null,
"unit": "event",
"measuredAt": "2026-06-15T14:22:00.000Z",
"source": "WITHINGS",
"notes": null,
"notesEncrypted": "<sealed:rt-Measurement-notesEncrypted>",
"notesEncrypted": null,
"externalId": null,
"externalSourceVersion": "rt-Measurement-externalSourceVersion",
"aggregationProvenance": "HEALTHKIT_STATISTICS",
"externalSourceVersion": null,
"aggregationProvenance": null,
"glucoseContext": null,
"sleepStage": null,
"rhythmClassification": "IRREGULAR",
"deviceType": "rt-Measurement-deviceType",
"deviceType": null,
"syncVersion": 1,
"deletedAt": null,
"createdAt": "<now>",
@@ -533,7 +533,7 @@
"source": "WITHINGS",
"measuredAt": "2026-06-15T14:22:00.000Z",
"notes": null,
"notesEncrypted": "<sealed:rt-Measurement-notesEncrypted>",
"notesEncrypted": null,
"externalId": null,
"externalSourceVersion": null,
"aggregationProvenance": null,
@@ -581,7 +581,7 @@
"source": "MANUAL",
"measuredAt": "2026-07-01T07:00:00.000Z",
"notes": null,
"notesEncrypted": null,
"notesEncrypted": "<sealed:rt-Measurement-notesEncrypted>",
"externalId": null,
"externalSourceVersion": null,
"aggregationProvenance": null,
@@ -2519,7 +2519,7 @@
"unit": "kg",
"measuredAt": "2026-07-01T07:00:00.000Z",
"source": "MANUAL",
"notes": null,
"notes": "rt-Measurement-notesEncrypted",
"deletedAt": null
},
{
@@ -2529,7 +2529,7 @@
"unit": "event",
"measuredAt": "2026-06-15T14:22:00.000Z",
"source": "WITHINGS",
"notes": "rt-Measurement-notesEncrypted",
"notes": null,
"deletedAt": null
}
],
@@ -33,20 +33,20 @@
"userId": "round-trip-owner",
"type": "IRREGULAR_RHYTHM_NOTIFICATION",
"value": 1,
"valueMin": 2.5,
"valueMax": 2.5,
"valueMin": null,
"valueMax": null,
"unit": "event",
"source": "WITHINGS",
"measuredAt": "2026-06-15T14:22:00.000Z",
"notes": "rt-Measurement-notes",
"notesEncrypted": "<sealed:rt-Measurement-notesEncrypted>",
"notes": null,
"notesEncrypted": null,
"externalId": null,
"externalSourceVersion": "rt-Measurement-externalSourceVersion",
"aggregationProvenance": "HEALTHKIT_STATISTICS",
"externalSourceVersion": null,
"aggregationProvenance": null,
"glucoseContext": null,
"sleepStage": null,
"rhythmClassification": "IRREGULAR",
"deviceType": "rt-Measurement-deviceType",
"deviceType": null,
"syncVersion": 1,
"deletedAt": null,
"createdAt": "<now>",
@@ -105,20 +105,20 @@
"userId": "round-trip-owner",
"type": "WEIGHT",
"value": 74.2,
"valueMin": null,
"valueMax": null,
"valueMin": 2.5,
"valueMax": 2.5,
"unit": "kg",
"source": "MANUAL",
"measuredAt": "2026-07-01T07:00:00.000Z",
"notes": null,
"notesEncrypted": null,
"notes": "rt-Measurement-notes",
"notesEncrypted": "<sealed:rt-Measurement-notesEncrypted>",
"externalId": null,
"externalSourceVersion": null,
"aggregationProvenance": null,
"externalSourceVersion": "rt-Measurement-externalSourceVersion",
"aggregationProvenance": "HEALTHKIT_STATISTICS",
"glucoseContext": null,
"sleepStage": null,
"rhythmClassification": null,
"deviceType": null,
"deviceType": "rt-Measurement-deviceType",
"syncVersion": 1,
"deletedAt": null,
"createdAt": "<now>",
+1
View File
@@ -1484,6 +1484,7 @@ export async function seedColumnShapes(prisma: PrismaClient): Promise<void> {
export type Delegate = {
findMany(args: {
where: Record<string, unknown>;
orderBy?: Record<string, "asc" | "desc">[];
}): Promise<Record<string, unknown>[]>;
update(args: {
where: Record<string, unknown>;
+10 -2
View File
@@ -397,8 +397,12 @@ async function fillDefaultedColumns(prisma: PrismaClient): Promise<string[]> {
const enumValues = enumValuesFromSchema();
const unfillable: string[] = [];
for (const model of models) {
// In key order, so the row that takes the fill is the same on every run:
// a table read without an order comes back in whatever order its last
// updates left on disk.
const rows = await delegateOf(prisma, model).findMany({
where: ownerWhere(byName, prisma, model.name, OWNER_ID),
orderBy: model.primaryKey.map((key) => ({ [key]: "asc" as const })),
});
if (rows.length === 0) continue;
for (const column of model.columns) {
@@ -624,7 +628,6 @@ async function runRestore(
effects.unitRefresh.length = 0;
effects.foldRepair.length = 0;
effects.annotations.length = 0;
const auditFrom = new Date();
const progress: Array<Record<string, unknown>> = [];
const raw = await restoreBackup({
backup,
@@ -645,7 +648,12 @@ async function runRestore(
},
});
const audit = await prisma.auditLog.findMany({
where: { createdAt: { gte: auditFrom }, action: { startsWith: "admin." } },
// By the copy's id rather than by time: the database clock and this
// process's clock do not agree to the millisecond.
where: {
action: { startsWith: "admin." },
details: { contains: backup.id },
},
orderBy: [{ createdAt: "asc" }, { id: "asc" }],
select: { action: true, userId: true, details: true },
});