chore: align hardening tests and formatting

This commit is contained in:
Marc-André Bombeck committed 2026-07-20 12:24:15 +02:00
1 parent 5d784a639c
commit 2716f302c7
35 files changed
+1044 -999

No files matched your search

@@ -39,6 +39,7 @@ vi.mock("@/lib/crypto", () => ({ decrypt: vi.fn(() => "{}") }));
vi.mock("@/lib/crypto/note-cipher", () => ({ encryptNote: vi.fn() }));
vi.mock("@/lib/validations/backup", () => ({
parseBackupPayload: vi.fn(),
isCompatibleSchemaVersion: vi.fn(() => true),
summarizeBackup: vi.fn(() => ({})),
}));
vi.mock("@/lib/auth/audit", () => ({ auditLog: vi.fn() }));
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -85,8 +85,8 @@ beforeEach(() => {
vi.mocked(prisma.device.deleteMany).mockResolvedValue({ count: 0 });
vi.mocked(prisma.refreshToken.updateMany).mockResolvedValue({ count: 0 });
vi.mocked(prisma.$queryRaw).mockResolvedValue([] as never);
vi.mocked(prisma.$transaction).mockImplementation(
async (callback) => callback(prisma as never),
vi.mocked(prisma.$transaction).mockImplementation(async (callback) =>
callback(prisma as never),
);
vi.mocked(prisma.notificationChannel.upsert).mockResolvedValue({
id: "ch-1",
+1 -5
View File
@@ -234,11 +234,7 @@ export const POST = apiHandler(async (request: NextRequest) => {
// the APNs row with the newly presented metadata.
let canonicalDevice = apnsDevice ?? legacyDevice;
let duplicateDevice: typeof legacyDevice = null;
if (
apnsDevice &&
legacyDevice &&
apnsDevice.id !== legacyDevice.id
) {
if (apnsDevice && legacyDevice && apnsDevice.id !== legacyDevice.id) {
duplicateDevice = legacyDevice;
await tx.device.deleteMany({
where: { id: legacyDevice.id, userId: user.id },
@@ -77,7 +77,9 @@ vi.mock("@/lib/measurements/reconcile-external-measurement", () => ({
});
return { status: "updated", row: { ...exact, ...input } };
}
const [inserted] = await tx.measurement.createManyAndReturn({ data: input });
const [inserted] = await tx.measurement.createManyAndReturn({
data: input,
});
if (inserted) return { status: "inserted", row: inserted };
if (options.exactExternalMatch === "update") {
await tx.measurement.updateMany({ where: {}, data: input });
@@ -58,7 +58,9 @@ vi.mock("@/lib/measurements/reconcile-external-measurement", () => ({
await tx.measurement.updateMany({ where: { id: exact.id }, data: input });
return { status: "updated", row: { ...exact, ...input } };
}
const [inserted] = await tx.measurement.createManyAndReturn({ data: input });
const [inserted] = await tx.measurement.createManyAndReturn({
data: input,
});
if (inserted) return { status: "inserted", row: inserted };
if (options.exactExternalMatch === "update") {
await tx.measurement.updateMany({ where: {}, data: input });
@@ -38,7 +38,9 @@ vi.mock("@/lib/measurements/reconcile-external-measurement", () => ({
) => {
const override = await reconcileOverrideMock(tx, input);
if (override !== undefined) return override;
const [inserted] = await tx.measurement.createManyAndReturn({ data: input });
const [inserted] = await tx.measurement.createManyAndReturn({
data: input,
});
return inserted
? { status: "inserted", row: inserted }
: { status: "duplicate" };
+90 -97
View File
@@ -177,12 +177,7 @@ type BatchEntry = z.infer<typeof batchEntrySchema>;
* sample is a canonical, immutable reading. `skipped` is a validation no-op;
* `failed` is a hard database verdict and must not be checkpointed as landed.
*/
type EntryStatus =
| "inserted"
| "updated"
| "duplicate"
| "skipped"
| "failed";
type EntryStatus = "inserted" | "updated" | "duplicate" | "skipped" | "failed";
// v1.5.0 issue #213 — `stats:*` externalIds carry per-day cumulative
// totals that the iOS HealthKit observer re-posts as the day progresses.
@@ -475,103 +470,101 @@ async function postBatch(request: NextRequest): Promise<Response> {
}
for (const p of prepared) {
const externalId = p.row.externalId as string;
const statsRow = isStatsExternalId(externalId);
const key = `${p.row.type}::${p.row.source}::${externalId}`;
if (statsRow && lastStatsIndexByKey.get(key) !== p.index) {
results[p.index] = {
index: p.index,
status: "duplicate",
reason: "superseded_in_batch",
};
duplicateCount++;
continue;
}
const externalId = p.row.externalId as string;
const statsRow = isStatsExternalId(externalId);
const key = `${p.row.type}::${p.row.source}::${externalId}`;
if (statsRow && lastStatsIndexByKey.get(key) !== p.index) {
results[p.index] = {
index: p.index,
status: "duplicate",
reason: "superseded_in_batch",
};
duplicateCount++;
continue;
}
if (!statsRow && hasSameReadingSibling(p)) {
results[p.index] = {
index: p.index,
status: "duplicate",
reason: "cross_source_merge",
};
duplicateCount++;
crossSourceMergedCount++;
continue;
}
if (!statsRow && hasSameReadingSibling(p)) {
results[p.index] = {
index: p.index,
status: "duplicate",
reason: "cross_source_merge",
};
duplicateCount++;
crossSourceMergedCount++;
continue;
}
const verdict = await prisma.$transaction((tx) =>
reconcileExternalMeasurement(
tx,
{
userId: user.id,
type: p.row.type as MeasurementType,
value: p.row.value as number,
valueMin: p.row.valueMin as number | null,
valueMax: p.row.valueMax as number | null,
unit: p.row.unit as string,
source:
p.row.source as Prisma.MeasurementUncheckedCreateInput["source"],
measuredAt: p.row.measuredAt as Date,
externalId,
externalSourceVersion: p.row
.externalSourceVersion as string | null,
glucoseContext:
p.row.glucoseContext as Prisma.MeasurementUncheckedCreateInput["glucoseContext"],
sleepStage:
p.row.sleepStage as Prisma.MeasurementUncheckedCreateInput["sleepStage"],
rhythmClassification:
p.row
.rhythmClassification as Prisma.MeasurementUncheckedCreateInput["rhythmClassification"],
deviceType: p.row.deviceType as string | null,
},
{ exactExternalMatch: statsRow ? "update" : "duplicate" },
),
);
switch (verdict.status) {
case "inserted":
results[p.index] = { index: p.index, status: "inserted" };
insertedCount++;
insertedPrepared.push(p);
writtenIdentities.push({
type: p.row.type as MeasurementType,
measuredAt: p.row.measuredAt as Date,
});
break;
case "updated":
case "resurrected":
results[p.index] = { index: p.index, status: "updated" };
updatedCount++;
writtenIdentities.push(
{
type: p.row.type as MeasurementType,
measuredAt: p.row.measuredAt as Date,
},
...(verdict.dirtyIdentities ?? []),
);
break;
case "duplicate":
results[p.index] = { index: p.index, status: "duplicate" };
duplicateCount++;
break;
case "failed":
results[p.index] = {
index: p.index,
status: "failed",
reason: verdict.error.code ?? "write_failed",
};
failedCount++;
continue;
}
if (isMergeableSource(p.row.source as string)) {
inBatchCandidates.push({
const verdict = await prisma.$transaction((tx) =>
reconcileExternalMeasurement(
tx,
{
userId: user.id,
type: p.row.type as MeasurementType,
source: p.row.source as string,
value: p.row.value as number,
valueMin: p.row.valueMin as number | null,
valueMax: p.row.valueMax as number | null,
unit: p.row.unit as string,
source: p.row
.source as Prisma.MeasurementUncheckedCreateInput["source"],
measuredAt: p.row.measuredAt as Date,
externalId,
externalSourceVersion: p.row.externalSourceVersion as string | null,
glucoseContext: p.row
.glucoseContext as Prisma.MeasurementUncheckedCreateInput["glucoseContext"],
sleepStage: p.row
.sleepStage as Prisma.MeasurementUncheckedCreateInput["sleepStage"],
rhythmClassification: p.row
.rhythmClassification as Prisma.MeasurementUncheckedCreateInput["rhythmClassification"],
deviceType: p.row.deviceType as string | null,
},
{ exactExternalMatch: statsRow ? "update" : "duplicate" },
),
);
switch (verdict.status) {
case "inserted":
results[p.index] = { index: p.index, status: "inserted" };
insertedCount++;
insertedPrepared.push(p);
writtenIdentities.push({
type: p.row.type as MeasurementType,
measuredAt: p.row.measuredAt as Date,
});
}
break;
case "updated":
case "resurrected":
results[p.index] = { index: p.index, status: "updated" };
updatedCount++;
writtenIdentities.push(
{
type: p.row.type as MeasurementType,
measuredAt: p.row.measuredAt as Date,
},
...(verdict.dirtyIdentities ?? []),
);
break;
case "duplicate":
results[p.index] = { index: p.index, status: "duplicate" };
duplicateCount++;
break;
case "failed":
results[p.index] = {
index: p.index,
status: "failed",
reason: verdict.error.code ?? "write_failed",
};
failedCount++;
continue;
}
if (isMergeableSource(p.row.source as string)) {
inBatchCandidates.push({
type: p.row.type as MeasurementType,
source: p.row.source as string,
value: p.row.value as number,
measuredAt: p.row.measuredAt as Date,
});
}
}
}
@@ -13,7 +13,6 @@ const txClient = vi.hoisted(() => ({
},
}));
vi.mock("@/lib/db", () => ({
prisma: {
moodEntry: {
@@ -222,9 +221,7 @@ describe("PUT /api/mood-entries/[id] — split tag replacement", () => {
error: null;
};
expect(body.data.tagKeys).toEqual(["happy"]);
expect(body.data.ratedFactors).toEqual([
{ key: "factor_work", rating: 4 },
]);
expect(body.data.ratedFactors).toEqual([{ key: "factor_work", rating: 4 }]);
expect(body.error).toBeNull();
});
@@ -254,9 +251,7 @@ describe("PUT /api/mood-entries/[id] — split tag replacement", () => {
};
};
expect(body.data.tagKeys).toEqual(["happy"]);
expect(body.data.ratedFactors).toEqual([
{ key: "factor_work", rating: 5 },
]);
expect(body.data.ratedFactors).toEqual([{ key: "factor_work", rating: 5 }]);
});
it("returns 422 when a factor score is outside its catalog scale", async () => {
@@ -333,18 +328,18 @@ describe("PUT /api/mood-entries/[id] — split tag replacement", () => {
});
});
it.each([
{ ratedFactors: null },
{ ratedFactors: [] },
])("clears RATED links for an explicit $ratedFactors", async (payload) => {
const res = await PUT(putReq(payload), ROUTE_CTX);
it.each([{ ratedFactors: null }, { ratedFactors: [] }])(
"clears RATED links for an explicit $ratedFactors",
async (payload) => {
const res = await PUT(putReq(payload), ROUTE_CTX);
expect(res.status).toBe(200);
expect(replaceRatedFactorLinks).toHaveBeenCalledWith(
"me1",
"user-1",
[],
txClient,
);
});
expect(res.status).toBe(200);
expect(replaceRatedFactorLinks).toHaveBeenCalledWith(
"me1",
"user-1",
[],
txClient,
);
},
);
});
@@ -46,9 +46,7 @@ function makePrisma() {
externalId: "moodlog-42",
moodLoggedAt: new Date("2026-07-19T08:00:00.000Z"),
deletedAt,
tagLinks: [
{ rating: 4, moodTag: { key: "sleep_quality" } },
],
tagLinks: [{ rating: 4, moodTag: { key: "sleep_quality" } }],
},
]),
},
@@ -94,10 +92,14 @@ describe("buildFullBackupPayload disaster-recovery mode", () => {
installSectionMocks();
const prisma = makePrisma();
const { payload } = await buildFullBackupPayload(prisma as never, "user-1", {
purpose: "disaster-recovery",
exportedAt: new Date("2026-07-20T00:00:00.000Z"),
});
const { payload } = await buildFullBackupPayload(
prisma as never,
"user-1",
{
purpose: "disaster-recovery",
exportedAt: new Date("2026-07-20T00:00:00.000Z"),
},
);
expect(mocks.findMeasurementsPaged).toHaveBeenCalledWith(
prisma,
+2 -5
View File
@@ -368,9 +368,7 @@ export async function buildRecordsBackupSection(
mimeType: d.mimeType,
byteSize: d.byteSize,
status: d.status,
reportDate: d.reportDate
? d.reportDate.toISOString().slice(0, 10)
: null,
reportDate: d.reportDate ? d.reportDate.toISOString().slice(0, 10) : null,
documentDate: d.documentDate
? d.documentDate.toISOString().slice(0, 10)
: null,
@@ -415,8 +413,7 @@ export async function buildRecordsBackupSection(
documents: disasterRecovery
? {
included: "encrypted-content",
note:
"Document metadata and encrypted stored bytes are included for disaster recovery.",
note: "Document metadata and encrypted stored bytes are included for disaster recovery.",
}
: { included: "metadata-only", note: DOCUMENTS_MANIFEST_NOTE },
workouts: { included: "summary-only", note: WORKOUTS_MANIFEST_NOTE },
@@ -3,12 +3,13 @@ import type { Job } from "pg-boss";
import { readFileSync } from "node:fs";
import { join } from "node:path";
const { syncUserEcg } = vi.hoisted(() => ({
syncUserEcg: vi.fn<
(
userId: string,
options?: { startdate?: number; enddate?: number },
) => Promise<number>
>(),
syncUserEcg:
vi.fn<
(
userId: string,
options?: { startdate?: number; enddate?: number },
) => Promise<number>
>(),
}));
vi.mock("@/lib/withings/sync-ecg", () => ({
@@ -70,10 +71,7 @@ beforeEach(() => {
vi.clearAllMocks();
});
const registrarSource = readFileSync(
join(
process.cwd(),
"src/lib/jobs/reminder/register-integration-sync.ts",
),
join(process.cwd(), "src/lib/jobs/reminder/register-integration-sync.ts"),
"utf8",
);
@@ -38,9 +38,9 @@ const documentCiphertext = Buffer.from([1, 2, 3, 4]).toString("base64");
function buildPrismaMock() {
return {
user: {
findMany: vi.fn().mockResolvedValue([
{ id: "user-dr", username: "backup-owner" },
]),
findMany: vi
.fn()
.mockResolvedValue([{ id: "user-dr", username: "backup-owner" }]),
},
dataBackup: { upsert: mocks.upsert },
};
+3 -5
View File
@@ -58,11 +58,9 @@ export async function handleDataBackup(jobs: Job<DataBackupPayload>[]) {
let backed = 0;
for (const user of users) {
try {
const { payload } = await buildFullBackupPayload(
prisma,
user.id,
{ purpose: "disaster-recovery" },
);
const { payload } = await buildFullBackupPayload(prisma, user.id, {
purpose: "disaster-recovery",
});
const backupJson = JSON.stringify(payload);
// Encrypt the backup data (contains sensitive health information)
-1
View File
@@ -17,7 +17,6 @@ import { enqueueReminderSatisfy } from "@/lib/jobs/reminder-satisfy";
import { getWorkerPrisma } from "./shared";
export type { WithingsEcgSyncPayload } from "@/lib/jobs/withings-ecg-queue";
export interface WithingsSyncPayload {
triggeredAt: string;
}
+1 -3
View File
@@ -49,9 +49,7 @@ export async function enqueueWithingsEcgSync(
userId: identity.userId,
eventId,
triggeredAt: new Date().toISOString(),
...(parsedStartdate?.success
? { startdate: parsedStartdate.data }
: {}),
...(parsedStartdate?.success ? { startdate: parsedStartdate.data } : {}),
...(parsedEnddate?.success ? { enddate: parsedEnddate.data } : {}),
} satisfies WithingsEcgSyncPayload,
{
@@ -24,9 +24,7 @@ function desired(
};
}
function row(
overrides: Record<string, unknown> = {},
): Record<string, unknown> {
function row(overrides: Record<string, unknown> = {}): Record<string, unknown> {
return {
id: "row-1",
userId: "user-1",
@@ -36,10 +36,7 @@ export type ReconciledMeasurementRow = Pick<
"id" | "type" | "measuredAt" | "externalId"
>;
export type DirtyMeasurementIdentity = Pick<
Measurement,
"type" | "measuredAt"
>;
export type DirtyMeasurementIdentity = Pick<Measurement, "type" | "measuredAt">;
export type MeasurementReconciliationVerdict =
| {
@@ -126,7 +123,9 @@ async function lockUser(
async function lockAndReloadCandidates(
tx: PrismaTypes.TransactionClient,
desired: ExternalMeasurementWrite,
): Promise<Array<PrismaTypes.MeasurementGetPayload<{ select: typeof rowSelect }>>> {
): Promise<
Array<PrismaTypes.MeasurementGetPayload<{ select: typeof rowSelect }>>
> {
const where = {
userId: desired.userId,
OR: [
@@ -262,6 +261,7 @@ export async function reconcileExternalMeasurement(
}
const { userId: _ownerId, ...mutable } = desired;
void _ownerId;
const updated = await tx.measurement.update({
where: { id: canonical.id },
data: {
-1
View File
@@ -272,7 +272,6 @@ describe("replaceTagLinks — archived-tag links survive an entry edit", () => {
skipDuplicates: true,
});
});
});
describe("entry-ownership guard", () => {
+16 -11
View File
@@ -142,17 +142,22 @@ beforeEach(() => {
transactionMock
.mockReset()
.mockImplementation(async (run: (tx: unknown) => unknown) => run({}));
reconcileMock.mockReset().mockImplementation(
async (_tx: unknown, input: { type: string; measuredAt: Date; externalId: string }) => ({
status: "inserted",
row: {
id: `inserted:${input.externalId}`,
type: input.type,
measuredAt: input.measuredAt,
externalId: input.externalId,
},
}),
);
reconcileMock
.mockReset()
.mockImplementation(
async (
_tx: unknown,
input: { type: string; measuredAt: Date; externalId: string },
) => ({
status: "inserted",
row: {
id: `inserted:${input.externalId}`,
type: input.type,
measuredAt: input.measuredAt,
externalId: input.externalId,
},
}),
);
createManyAndReturnMock.mockReset().mockImplementation(
async ({
data,
+26 -23
View File
@@ -461,30 +461,33 @@ export async function upsertOuraMeasurements(
InsertedMeasurementArrivalRow & { externalId: string | null }
> = [];
const verdicts = await prisma.$transaction(async (tx) => {
const outcomes = [];
for (const r of readings) {
const verdict = await reconcileExternalMeasurement(
tx,
{
userId,
type: r.type as MeasurementType,
source: "OURA",
value: r.value,
unit: r.unit,
measuredAt: r.measuredAt,
externalId: r.externalId,
sleepStage: r.sleepStage ?? null,
},
{ exactExternalMatch: "update" },
);
if (verdict.status === "failed") {
throw new MeasurementReconciliationError(verdict);
const verdicts = await prisma.$transaction(
async (tx) => {
const outcomes = [];
for (const r of readings) {
const verdict = await reconcileExternalMeasurement(
tx,
{
userId,
type: r.type as MeasurementType,
source: "OURA",
value: r.value,
unit: r.unit,
measuredAt: r.measuredAt,
externalId: r.externalId,
sleepStage: r.sleepStage ?? null,
},
{ exactExternalMatch: "update" },
);
if (verdict.status === "failed") {
throw new MeasurementReconciliationError(verdict);
}
outcomes.push(verdict);
}
outcomes.push(verdict);
}
return outcomes;
}, { timeout: 60_000 });
return outcomes;
},
{ timeout: 60_000 },
);
for (let index = 0; index < readings.length; index++) {
const reading = readings[index]!;
+16 -11
View File
@@ -106,17 +106,22 @@ beforeEach(() => {
transactionMock
.mockReset()
.mockImplementation(async (run: (tx: unknown) => unknown) => run({}));
reconcileMock.mockReset().mockImplementation(
async (_tx: unknown, input: { type: string; measuredAt: Date; externalId: string }) => ({
status: "inserted",
row: {
id: `inserted:${input.externalId}`,
type: input.type,
measuredAt: input.measuredAt,
externalId: input.externalId,
},
}),
);
reconcileMock
.mockReset()
.mockImplementation(
async (
_tx: unknown,
input: { type: string; measuredAt: Date; externalId: string },
) => ({
status: "inserted",
row: {
id: `inserted:${input.externalId}`,
type: input.type,
measuredAt: input.measuredAt,
externalId: input.externalId,
},
}),
);
createManyAndReturnMock.mockReset().mockImplementation(
async ({
data,
+26 -23
View File
@@ -210,30 +210,33 @@ export async function upsertPolarMeasurements(
InsertedMeasurementArrivalRow & { externalId: string | null }
> = [];
const verdicts = await prisma.$transaction(async (tx) => {
const outcomes = [];
for (const r of readings) {
const verdict = await reconcileExternalMeasurement(
tx,
{
userId,
type: r.type as MeasurementType,
source: "POLAR",
value: r.value,
unit: r.unit,
measuredAt: r.measuredAt,
externalId: r.externalId,
sleepStage: r.sleepStage ?? null,
},
{ exactExternalMatch: "update" },
);
if (verdict.status === "failed") {
throw new MeasurementReconciliationError(verdict);
const verdicts = await prisma.$transaction(
async (tx) => {
const outcomes = [];
for (const r of readings) {
const verdict = await reconcileExternalMeasurement(
tx,
{
userId,
type: r.type as MeasurementType,
source: "POLAR",
value: r.value,
unit: r.unit,
measuredAt: r.measuredAt,
externalId: r.externalId,
sleepStage: r.sleepStage ?? null,
},
{ exactExternalMatch: "update" },
);
if (verdict.status === "failed") {
throw new MeasurementReconciliationError(verdict);
}
outcomes.push(verdict);
}
outcomes.push(verdict);
}
return outcomes;
}, { timeout: 60_000 });
return outcomes;
},
{ timeout: 60_000 },
);
for (let index = 0; index < readings.length; index++) {
const reading = readings[index]!;
+4 -1
View File
@@ -68,7 +68,10 @@ export function classifyStravaFailure(err: unknown): FailureKind {
return toFailureKind(classifyStravaError(err));
}
async function recordStravaFailure(userId: string, err: unknown): Promise<void> {
async function recordStravaFailure(
userId: string,
err: unknown,
): Promise<void> {
await recordSyncFailure({
userId,
integration: "strava",
+1 -3
View File
@@ -46,7 +46,6 @@ import {
SecondarySymptom,
} from "@/generated/prisma/enums";
export const BACKUP_SCHEMA_VERSION = "1" as const;
const isoDateTime = z
@@ -351,8 +350,7 @@ const base64BytesSchema = z
.string()
.min(1)
.refine(
(value) =>
value.length % 4 === 0 && /^[A-Za-z0-9+/]+={0,2}$/.test(value),
(value) => value.length % 4 === 0 && /^[A-Za-z0-9+/]+={0,2}$/.test(value),
{ message: "Expected base64-encoded encrypted bytes" },
);
+8 -3
View File
@@ -118,7 +118,10 @@ beforeEach(() => {
async (run: (tx: unknown) => unknown) => run(prismaMock),
);
reconcileMock.mockImplementation(
async (_tx: unknown, input: { type: string; measuredAt: Date; externalId: string }) => ({
async (
_tx: unknown,
input: { type: string; measuredAt: Date; externalId: string },
) => ({
status: "inserted",
row: {
id: `inserted:${input.externalId}`,
@@ -167,7 +170,6 @@ describe("getValidToken — rotating refresh", () => {
expect(prismaMock.$queryRaw).toHaveBeenCalledTimes(1);
});
it("re-reads token state under an advisory lock and reuses a peer winner", async () => {
prismaMock.whoopConnection.findUnique
.mockResolvedValueOnce({
@@ -549,7 +551,10 @@ describe("syncUserRecovery — durable cursor ordering", () => {
});
fetchRecoveriesMock.mockResolvedValue([recovery]);
reconcileMock.mockImplementation(
async (_tx: unknown, input: { type: string; measuredAt: Date; externalId: string }) => ({
async (
_tx: unknown,
input: { type: string; measuredAt: Date; externalId: string },
) => ({
status: "updated",
row: {
id: `existing:${input.externalId}`,
@@ -107,11 +107,14 @@ function installFetchMock(segments: FakeSegment[]) {
beforeEach(() => {
vi.resetAllMocks();
transactionMock.mockImplementation(
async (run: (tx: unknown) => unknown) => run({}),
transactionMock.mockImplementation(async (run: (tx: unknown) => unknown) =>
run({}),
);
reconcileMock.mockImplementation(
async (_tx: unknown, input: { type: string; measuredAt: Date; externalId: string }) => ({
async (
_tx: unknown,
input: { type: string; measuredAt: Date; externalId: string },
) => ({
status: "inserted",
row: {
id: `inserted:${input.externalId}`,
@@ -502,10 +505,7 @@ describe("syncUserSleep — segment writes + idempotency", () => {
externalId: string;
value: number;
};
return [
row.type,
{ externalId: row.externalId, value: row.value },
];
return [row.type, { externalId: row.externalId, value: row.value }];
}),
);
expect(byType.get("RESTING_HEART_RATE")).toEqual({
-1
View File
@@ -496,7 +496,6 @@ export async function syncUserEcg(
}
}
annotate({
action: { name: "withings.ecg.sync" },
meta: {
+72 -66
View File
@@ -412,48 +412,51 @@ export async function syncUserSleep(
// and are excluded (their `no-id` prefix would span every id-less night in
// history, not just this fetch).
const freshBySession = new Map<number, string[]>();
await prisma.$transaction(async (tx) => {
for (const segment of segments) {
const stage = mapWithingsSleepState(segment.state);
if (!stage) continue;
await prisma.$transaction(
async (tx) => {
for (const segment of segments) {
const stage = mapWithingsSleepState(segment.state);
if (!stage) continue;
const measuredAt = new Date(segment.enddate * 1000);
const durationSec = Math.max(0, segment.enddate - segment.startdate);
const minutes = Math.round(durationSec / 60);
const externalId = `withings:sleep:${userId}:${segment.id ?? "no-id"}:${segment.startdate}`;
if (typeof segment.id === "number") {
const fresh = freshBySession.get(segment.id) ?? [];
fresh.push(externalId);
freshBySession.set(segment.id, fresh);
}
const measuredAt = new Date(segment.enddate * 1000);
const durationSec = Math.max(0, segment.enddate - segment.startdate);
const minutes = Math.round(durationSec / 60);
const externalId = `withings:sleep:${userId}:${segment.id ?? "no-id"}:${segment.startdate}`;
if (typeof segment.id === "number") {
const fresh = freshBySession.get(segment.id) ?? [];
fresh.push(externalId);
freshBySession.set(segment.id, fresh);
}
const verdict = await reconcileExternalMeasurement(
tx,
{
userId,
type: SLEEP_TYPE,
value: minutes,
unit: getUnitForType(SLEEP_TYPE),
measuredAt,
source: "WITHINGS",
sleepStage: stage,
externalId,
},
{ exactExternalMatch: "update" },
);
if (verdict.status === "failed") {
throw new MeasurementReconciliationError(verdict);
const verdict = await reconcileExternalMeasurement(
tx,
{
userId,
type: SLEEP_TYPE,
value: minutes,
unit: getUnitForType(SLEEP_TYPE),
measuredAt,
source: "WITHINGS",
sleepStage: stage,
externalId,
},
{ exactExternalMatch: "update" },
);
if (verdict.status === "failed") {
throw new MeasurementReconciliationError(verdict);
}
for (const dirty of verdict.dirtyIdentities ?? []) {
touched.push(dirty);
}
if (verdict.status === "inserted") {
insertedSleepMeasuredAts.push(measuredAt);
}
touched.push({ type: SLEEP_TYPE, measuredAt });
imported++;
}
for (const dirty of verdict.dirtyIdentities ?? []) {
touched.push(dirty);
}
if (verdict.status === "inserted") {
insertedSleepMeasuredAts.push(measuredAt);
}
touched.push({ type: SLEEP_TYPE, measuredAt });
imported++;
}
}, { timeout: 60_000 });
},
{ timeout: 60_000 },
);
// Session-scoped sweep (mirrors Google Health's replace-by-window): for each
// session this fetch re-produced, soft-delete any live WITHINGS
@@ -486,35 +489,38 @@ export async function syncUserSleep(
);
}
await prisma.$transaction(async (tx) => {
for (const summary of summaries) {
const measuredAt = new Date(summary.enddate * 1000);
const sessionId = summary.id ?? "no-id";
for (const vital of mapWithingsSleepSummary(summary.data)) {
const verdict = await reconcileExternalMeasurement(
tx,
{
userId,
type: vital.type,
value: vital.value,
unit: getUnitForType(vital.type),
measuredAt,
source: "WITHINGS",
externalId: `withings:sleep:${userId}:${sessionId}:${vital.fieldTag}`,
},
{ exactExternalMatch: "update" },
);
if (verdict.status === "failed") {
throw new MeasurementReconciliationError(verdict);
await prisma.$transaction(
async (tx) => {
for (const summary of summaries) {
const measuredAt = new Date(summary.enddate * 1000);
const sessionId = summary.id ?? "no-id";
for (const vital of mapWithingsSleepSummary(summary.data)) {
const verdict = await reconcileExternalMeasurement(
tx,
{
userId,
type: vital.type,
value: vital.value,
unit: getUnitForType(vital.type),
measuredAt,
source: "WITHINGS",
externalId: `withings:sleep:${userId}:${sessionId}:${vital.fieldTag}`,
},
{ exactExternalMatch: "update" },
);
if (verdict.status === "failed") {
throw new MeasurementReconciliationError(verdict);
}
for (const dirty of verdict.dirtyIdentities ?? []) {
touched.push(dirty);
}
touched.push({ type: vital.type, measuredAt });
imported++;
}
for (const dirty of verdict.dirtyIdentities ?? []) {
touched.push(dirty);
}
touched.push({ type: vital.type, measuredAt });
imported++;
}
}
}, { timeout: 60_000 });
},
{ timeout: 60_000 },
);
// v1.4.39.1 — refresh the persistent rollup table for every distinct
// (type, day) the sync touched. Sleep segments collapse heavily —
@@ -12,7 +12,6 @@ process.env.BACKUP_S3_ACCESS_KEY = "test-access";
process.env.BACKUP_S3_SECRET_KEY = "test-secret";
process.env.BACKUP_ENCRYPTION_KEY = OFFHOST_ENCRYPTION_KEY;
import { encrypt, encryptBytes } from "@/lib/crypto";
import { encryptToBytes, decryptFromBytes } from "@/lib/ai/coach/bytes-codec";
import {
@@ -337,15 +336,10 @@ describe("canonical disaster-recovery backup round-trip", () => {
failed: 0,
totalUsers: 1,
});
const offhostObject = s3.objects.get(
`2026-07-02/user-${ownerId}.json.enc`,
);
const offhostObject = s3.objects.get(`2026-07-02/user-${ownerId}.json.enc`);
expect(offhostObject).toBeDefined();
const payload = parseBackupPayload(
decryptBackup(
offhostObject!,
Buffer.from(OFFHOST_ENCRYPTION_KEY, "hex"),
),
decryptBackup(offhostObject!, Buffer.from(OFFHOST_ENCRYPTION_KEY, "hex")),
);
expect(payload).toMatchObject({
schemaVersion: "1",
@@ -379,14 +373,26 @@ describe("canonical disaster-recovery backup round-trip", () => {
expect((await restore()).status).toBe(200);
expect(await prisma.labResult.count({ where: { userId: ownerId } })).toBe(1);
expect(await prisma.biomarker.count({ where: { userId: ownerId } })).toBe(1);
expect(await prisma.illnessEpisode.count({ where: { userId: ownerId } })).toBe(1);
expect(await prisma.illnessDayLog.count({ where: { userId: ownerId } })).toBe(1);
expect(await prisma.labResult.count({ where: { userId: ownerId } })).toBe(
1,
);
expect(await prisma.biomarker.count({ where: { userId: ownerId } })).toBe(
1,
);
expect(
await prisma.illnessEpisode.count({ where: { userId: ownerId } }),
).toBe(1);
expect(
await prisma.illnessDayLog.count({ where: { userId: ownerId } }),
).toBe(1);
expect(await prisma.allergy.count({ where: { userId: ownerId } })).toBe(1);
expect(await prisma.familyHistoryEntry.count({ where: { userId: ownerId } })).toBe(1);
expect(
await prisma.familyHistoryEntry.count({ where: { userId: ownerId } }),
).toBe(1);
expect(await prisma.workout.count({ where: { userId: ownerId } })).toBe(1);
expect(await prisma.inboundDocument.count({ where: { userId: ownerId } })).toBe(1);
expect(
await prisma.inboundDocument.count({ where: { userId: ownerId } }),
).toBe(1);
expect(
await prisma.measurement.findUniqueOrThrow({
where: { id: measurement.id },
@@ -466,7 +472,9 @@ describe("canonical disaster-recovery backup round-trip", () => {
where: { id: illnessDay.id },
include: { symptomLinks: true },
});
expect(decryptFromBytes(restoredIllnessDay.noteEncrypted!)).toBe("Rest day");
expect(decryptFromBytes(restoredIllnessDay.noteEncrypted!)).toBe(
"Rest day",
);
expect(restoredIllnessDay.symptomLinks).toEqual([
expect.objectContaining({ symptomId: symptom.id, severity: 2 }),
]);
@@ -625,55 +633,57 @@ describe("canonical disaster-recovery backup round-trip", () => {
payload.schemaVersion = "999";
},
},
])("rejects $label with 422 before writes or invalidation", async ({ mutate }) => {
const prisma = getPrismaClient();
const admin = await seedAdminSession();
const existing = await prisma.measurement.create({
data: {
id: "pre-validation-measurement",
])(
"rejects $label with 422 before writes or invalidation",
async ({ mutate }) => {
const prisma = getPrismaClient();
const admin = await seedAdminSession();
const existing = await prisma.measurement.create({
data: {
id: "pre-validation-measurement",
userId: admin.id,
type: "WEIGHT",
value: 91,
unit: "kg",
source: "MANUAL",
measuredAt: new Date("2026-07-03T06:00:00.000Z"),
},
});
const payload: Record<string, unknown> = {
schemaVersion: "1",
exportedAt: "2026-07-03T08:00:00.000Z",
userId: admin.id,
type: "WEIGHT",
value: 91,
unit: "kg",
source: "MANUAL",
measuredAt: new Date("2026-07-03T06:00:00.000Z"),
},
});
const payload: Record<string, unknown> = {
schemaVersion: "1",
exportedAt: "2026-07-03T08:00:00.000Z",
userId: admin.id,
measurements: [],
moodEntries: [],
};
mutate(payload);
const backup = await prisma.dataBackup.create({
data: {
userId: admin.id,
type: `INVALID_BOUNDARY_${String(payload.schemaVersion)}`,
data: encrypt(JSON.stringify(payload)),
},
});
measurements: [],
moodEntries: [],
};
mutate(payload);
const backup = await prisma.dataBackup.create({
data: {
userId: admin.id,
type: `INVALID_BOUNDARY_${String(payload.schemaVersion)}`,
data: encrypt(JSON.stringify(payload)),
},
});
const response = await POST(
makeRequest(backup.id) as unknown as Parameters<typeof POST>[0],
{ params: Promise.resolve({ id: backup.id }) },
);
expect(response.status).toBe(422);
expect(await response.json()).toMatchObject({
data: null,
error: expect.any(String),
});
expect(
await prisma.measurement.findMany({
where: { userId: admin.id },
select: { id: true },
}),
).toEqual([{ id: existing.id }]);
expect(invalidateUserData).not.toHaveBeenCalled();
});
const response = await POST(
makeRequest(backup.id) as unknown as Parameters<typeof POST>[0],
{ params: Promise.resolve({ id: backup.id }) },
);
expect(response.status).toBe(422);
expect(await response.json()).toMatchObject({
data: null,
error: expect.any(String),
});
expect(
await prisma.measurement.findMany({
where: { userId: admin.id },
select: { id: true },
}),
).toEqual([{ id: existing.id }]);
expect(invalidateUserData).not.toHaveBeenCalled();
},
);
it("rejects metadata-only documents before mutation or invalidation", async () => {
const prisma = getPrismaClient();
@@ -784,9 +794,9 @@ describe("canonical disaster-recovery backup round-trip", () => {
select: { id: true },
}),
).toEqual([{ id: measurement.id }]);
expect(
await prisma.moodEntry.count({ where: { userId: admin.id } }),
).toBe(0);
expect(await prisma.moodEntry.count({ where: { userId: admin.id } })).toBe(
0,
);
expect(invalidateUserData).not.toHaveBeenCalled();
});
});
+35 -26
View File
@@ -239,34 +239,43 @@ describe("POST /api/admin/backups/upload", () => {
});
it.each([
["measurement type", (payload: UploadPayloadFixture) => {
payload.measurements[0]!.type = "NOT_A_MEASUREMENT";
}],
["measurement source", (payload: UploadPayloadFixture) => {
payload.measurements[0]!.source = "NOT_A_SOURCE";
}],
])("rejects an invalid %s with 422 and zero backup writes", async (_label, mutate) => {
const prisma = getPrismaClient();
const admin = await seedAdminSession();
const payload = buildPayload(admin.id);
mutate(payload);
const file = new File([JSON.stringify(payload)], "invalid-enum.json", {
type: "application/json",
});
[
"measurement type",
(payload: UploadPayloadFixture) => {
payload.measurements[0]!.type = "NOT_A_MEASUREMENT";
},
],
[
"measurement source",
(payload: UploadPayloadFixture) => {
payload.measurements[0]!.source = "NOT_A_SOURCE";
},
],
])(
"rejects an invalid %s with 422 and zero backup writes",
async (_label, mutate) => {
const prisma = getPrismaClient();
const admin = await seedAdminSession();
const payload = buildPayload(admin.id);
mutate(payload);
const file = new File([JSON.stringify(payload)], "invalid-enum.json", {
type: "application/json",
});
const { POST } = await import("@/app/api/admin/backups/upload/route");
const res = await POST(
buildMultipart(file) as unknown as Parameters<typeof POST>[0],
);
const { POST } = await import("@/app/api/admin/backups/upload/route");
const res = await POST(
buildMultipart(file) as unknown as Parameters<typeof POST>[0],
);
expect(res.status).toBe(422);
expect(await res.json()).toMatchObject({
data: null,
error: "Backup payload failed schema validation",
meta: { issues: expect.any(Array) },
});
expect(await prisma.dataBackup.count()).toBe(0);
});
expect(res.status).toBe(422);
expect(await res.json()).toMatchObject({
data: null,
error: "Backup payload failed schema validation",
meta: { issues: expect.any(Array) },
});
expect(await prisma.dataBackup.count()).toBe(0);
},
);
it("rejects an incompatible schemaVersion with 422", async () => {
const prisma = getPrismaClient();
+7 -3
View File
@@ -175,7 +175,9 @@ describe("POST /api/devices", () => {
const secondBody = (await second.json()) as { data: { id: string } };
expect(secondBody.data.id).toBe(firstBody.data.id);
const devices = await prisma.device.findMany({ where: { userId: user.id } });
const devices = await prisma.device.findMany({
where: { userId: user.id },
});
expect(devices).toHaveLength(1);
expect(devices[0]).toMatchObject({
id: firstBody.data.id,
@@ -214,7 +216,7 @@ describe("POST /api/devices", () => {
liveActivityPushToken: "feedface".repeat(8),
},
});
const legacyDevice = await prisma.device.create({
await prisma.device.create({
data: {
userId: user.id,
platform: "ios",
@@ -252,7 +254,9 @@ describe("POST /api/devices", () => {
const body = (await response.json()) as { data: { id: string } };
expect(body.data.id).toBe(apnsDevice.id);
const devices = await prisma.device.findMany({ where: { userId: user.id } });
const devices = await prisma.device.findMany({
where: { userId: user.id },
});
expect(devices).toHaveLength(1);
expect(devices[0]).toMatchObject({
id: apnsDevice.id,
@@ -110,9 +110,9 @@ describe("external measurement identity reconciliation (real Postgres)", () => {
status: "updated",
row: { id: occupied.id, externalId: "sleep:new" },
});
expect(
await prisma.measurement.count({ where: { userId: user.id } }),
).toBe(1);
expect(await prisma.measurement.count({ where: { userId: user.id } })).toBe(
1,
);
});
it("resurrects and re-keys a tombstone occupying the natural key", async () => {
@@ -347,7 +347,9 @@ describe("MoodEntry — external-id re-import idempotency (v1.12.1 / 0122)", ()
mood: "OKAY",
score: 3,
});
expect(await prisma.moodEntry.count({ where: { userId: user.id } })).toBe(1);
expect(await prisma.moodEntry.count({ where: { userId: user.id } })).toBe(
1,
);
});
it("entries without an id still dedup on the legacy wall-clock key", async () => {
@@ -79,10 +79,7 @@ async function seedProviderUser(provider: "whoop" | "withings") {
return userId;
}
async function runContenders(
provider: "whoop" | "withings",
userId: string,
) {
async function runContenders(provider: "whoop" | "withings", userId: string) {
const getToken =
provider === "whoop" ? getValidWhoopToken : getValidWithingsToken;
return Promise.all(
@@ -98,62 +95,73 @@ beforeEach(async () => {
describe.each([
["whoop", whoopRefresh],
["withings", withingsRefresh],
] as const)("%s rotating token refresh serialization (real Postgres)", (provider, refresh) => {
it("lets exactly one contender refresh a stale token and shares the winner", async () => {
const userId = await seedProviderUser(provider);
refresh.mockImplementation(async (refreshToken: string) => {
expect(refreshToken).toBe("stale-refresh");
return {
access_token: "winner-access",
refresh_token: "winner-refresh",
expires_in: 3600,
};
});
const results = await runContenders(provider, userId);
expect(refresh).toHaveBeenCalledTimes(1);
expect(results).toHaveLength(CONTENDERS);
expect(results.every((result) => result?.accessToken === "winner-access")).toBe(
true,
);
const prisma = getPrismaClient();
const row =
provider === "whoop"
? await prisma.whoopConnection.findUniqueOrThrow({ where: { userId } })
: await prisma.withingsConnection.findUniqueOrThrow({ where: { userId } });
expect(decrypt(row.accessToken)).toBe("winner-access");
expect(decrypt(row.refreshToken)).toBe("winner-refresh");
});
it("rolls back a failed winner so a later contender can retry", async () => {
const userId = await seedProviderUser(provider);
refresh
.mockRejectedValueOnce(new Error("injected refresh failure"))
.mockResolvedValue({
access_token: "retry-access",
refresh_token: "retry-refresh",
expires_in: 3600,
] as const)(
"%s rotating token refresh serialization (real Postgres)",
(provider, refresh) => {
it("lets exactly one contender refresh a stale token and shares the winner", async () => {
const userId = await seedProviderUser(provider);
refresh.mockImplementation(async (refreshToken: string) => {
expect(refreshToken).toBe("stale-refresh");
return {
access_token: "winner-access",
refresh_token: "winner-refresh",
expires_in: 3600,
};
});
const results = await runContenders(provider, userId);
const results = await runContenders(provider, userId);
expect(refresh).toHaveBeenCalledTimes(2);
expect(results.filter((result) => result === null)).toHaveLength(1);
expect(
results.filter((result) => result?.accessToken === "retry-access"),
).toHaveLength(CONTENDERS - 1);
expect(refresh).toHaveBeenCalledTimes(1);
expect(results).toHaveLength(CONTENDERS);
expect(
results.every((result) => result?.accessToken === "winner-access"),
).toBe(true);
const prisma = getPrismaClient();
const row =
provider === "whoop"
? await prisma.whoopConnection.findUniqueOrThrow({ where: { userId } })
: await prisma.withingsConnection.findUniqueOrThrow({ where: { userId } });
expect(decrypt(row.accessToken)).toBe("retry-access");
expect(decrypt(row.refreshToken)).toBe("retry-refresh");
});
});
const prisma = getPrismaClient();
const row =
provider === "whoop"
? await prisma.whoopConnection.findUniqueOrThrow({
where: { userId },
})
: await prisma.withingsConnection.findUniqueOrThrow({
where: { userId },
});
expect(decrypt(row.accessToken)).toBe("winner-access");
expect(decrypt(row.refreshToken)).toBe("winner-refresh");
});
it("rolls back a failed winner so a later contender can retry", async () => {
const userId = await seedProviderUser(provider);
refresh
.mockRejectedValueOnce(new Error("injected refresh failure"))
.mockResolvedValue({
access_token: "retry-access",
refresh_token: "retry-refresh",
expires_in: 3600,
});
const results = await runContenders(provider, userId);
expect(refresh).toHaveBeenCalledTimes(2);
expect(results.filter((result) => result === null)).toHaveLength(1);
expect(
results.filter((result) => result?.accessToken === "retry-access"),
).toHaveLength(CONTENDERS - 1);
const prisma = getPrismaClient();
const row =
provider === "whoop"
? await prisma.whoopConnection.findUniqueOrThrow({
where: { userId },
})
: await prisma.withingsConnection.findUniqueOrThrow({
where: { userId },
});
expect(decrypt(row.accessToken)).toBe("retry-access");
expect(decrypt(row.refreshToken)).toBe("retry-refresh");
});
},
);
describe("WHOOP recovery cursor durability (real Postgres)", () => {
const oldCursor = "2026-06-01T00:00:00.000Z";