test(01-12): add failing Apple ECG import contracts

- pin bounded path-safe archive and parser behavior
- require encrypted idempotent fail-soft persistence and WHOOP regressions
This commit is contained in:
Marc-André Bombeck committed 2026-07-29 14:20:56 +02:00
1 parent d5f473d672
commit 6cc3de8248
5 files changed
+890

No files matched your search

@@ -0,0 +1,63 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { APPLE_HEALTH_IMPORT_PARSER_REVISION } from "../apple-health-import-worker";
const workerSource = readFileSync(
join(process.cwd(), "src/lib/jobs/apple-health-import-worker.ts"),
"utf8",
);
const parserSource = readFileSync(
join(process.cwd(), "src/lib/measurements/import-apple-health-export.ts"),
"utf8",
);
describe("Apple Health import worker — ECG auxiliary branch contract", () => {
it("bumps the parser revision so completed revision-2 archives reprocess", () => {
expect(APPLE_HEALTH_IMPORT_PARSER_REVISION).toBeGreaterThan(2);
});
it("runs bounded ECG archive ingestion beside the established XML parser", () => {
expect(workerSource).toMatch(/streamAppleHealthEcgMembers/);
expect(workerSource).toMatch(/parseAppleHealthEcgCsv/);
expect(workerSource).toMatch(/importAppleHealthEcg/);
expect(workerSource).toMatch(/streamParseExportXml/);
});
it("catches per-ECG failures without routing the valid XML result to the fatal job catch", () => {
expect(workerSource).toMatch(
/for await[\s\S]*streamAppleHealthEcgMembers[\s\S]*try[\s\S]*parseAppleHealthEcgCsv[\s\S]*catch[\s\S]*ecg\.failed/,
);
expect(workerSource).toMatch(
/result[\s\S]*ecg:\s*\{[\s\S]*discovered[\s\S]*imported[\s\S]*updated[\s\S]*skipped[\s\S]*failed/,
);
});
it("keeps progress bounded to scalar ECG counts", () => {
expect(parserSource).toMatch(
/ecg:\s*\{[\s\S]*discovered:\s*number[\s\S]*imported:\s*number[\s\S]*updated:\s*number[\s\S]*skipped:\s*number[\s\S]*failed:\s*number/,
);
expect(parserSource).not.toMatch(
/ecg:\s*\{[^}]*?(?:samples|waveform|filename|sourceName|healthValue):/,
);
});
it("does not log ECG filenames, source labels, samples, or raw parser errors", () => {
const loggingCalls =
workerSource.match(/console\.(?:log|warn|error)\([\s\S]{0,250}\)/g) ?? [];
const ecgLogging = loggingCalls.filter((call) =>
/ecg|electrocardiogram/i.test(call),
);
expect(ecgLogging.join("\n")).not.toMatch(
/member|filename|sourceName|sample|waveform|\$\{(?:err|error)\}/i,
);
});
it("does not claim to diagnose or reclassify a waveform", () => {
expect(workerSource).not.toMatch(
/(?:diagnose|infer|classify)Ecg|analy[sz]eWaveform|detectA(?:fib|trial)/i,
);
});
});
@@ -0,0 +1,373 @@
import { afterEach, describe, expect, it, vi, type MockInstance } from "vitest";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Readable } from "node:stream";
import { strToU8, zipSync } from "fflate";
type ArchiveLimits = {
maxMembers: number;
maxEcgMembers: number;
maxMemberBytes: number;
maxTotalEcgBytes: number;
maxCompressionRatio: number;
};
type ArchiveMember = {
name: string;
stream: Readable;
};
type NormalizedEcg = {
recordedAt: Date;
samplingFrequency: number;
samples: number[];
lead: string | null;
averageHeartRate: number | null;
rhythmClassification: "IRREGULAR" | "NOT_DETECTED" | "INCONCLUSIVE" | null;
};
type ArchiveSubject = {
streamAppleHealthEcgMembers(input: {
archivePath: string;
limits: ArchiveLimits;
}): AsyncIterable<ArchiveMember>;
};
type ParserSubject = {
parseAppleHealthEcgCsv(input: {
memberName: string;
stream: Readable;
maxSamples: number;
}): Promise<NormalizedEcg>;
};
const tempDirs: string[] = [];
const LIMITS: ArchiveLimits = {
maxMembers: 8,
maxEcgMembers: 2,
maxMemberBytes: 2_048,
maxTotalEcgBytes: 3_000,
maxCompressionRatio: 100,
};
function loadArchiveSubject(): Promise<ArchiveSubject> {
return vi.importActual<ArchiveSubject>("@/lib/apple-health/archive-stream");
}
function loadParserSubject(): Promise<ParserSubject> {
return vi.importActual<ParserSubject>("@/lib/apple-health/ecg-csv");
}
function archivePath(files: Record<string, string>): string {
const dir = mkdtempSync(join(tmpdir(), "healthlog-ecg-contract-"));
tempDirs.push(dir);
const path = join(dir, "export.zip");
writeFileSync(
path,
zipSync(
Object.fromEntries(
Object.entries(files).map(([name, text]) => [name, strToU8(text)]),
),
{ level: 6 },
),
);
return path;
}
function replaceSameLengthName(
archive: Buffer,
from: string,
to: string,
): Buffer {
expect(Buffer.byteLength(from)).toBe(Buffer.byteLength(to));
const out = Buffer.from(archive);
const needle = Buffer.from(from);
const replacement = Buffer.from(to);
let cursor = 0;
while ((cursor = out.indexOf(needle, cursor)) !== -1) {
replacement.copy(out, cursor);
cursor += replacement.length;
}
return out;
}
function mutateZipHeaders(
archive: Buffer,
memberName: string,
mutate: (header: Buffer, central: boolean) => void,
): Buffer {
const out = Buffer.from(archive);
for (let cursor = 0; cursor <= out.length - 46; cursor += 1) {
const signature = out.readUInt32LE(cursor);
const central = signature === 0x02014b50;
const local = signature === 0x04034b50;
if (!central && !local) continue;
const nameLength = out.readUInt16LE(cursor + (central ? 28 : 26));
const nameStart = cursor + (central ? 46 : 30);
if (
out.subarray(nameStart, nameStart + nameLength).toString("utf8") ===
memberName
) {
mutate(out.subarray(cursor), central);
}
}
return out;
}
function validCsv(overrides: Partial<Record<string, string>> = {}): string {
const metadata = {
"Recorded Date": "2026-07-18 08:14:03 +0200",
Classification: "Sinus Rhythm",
"Average Heart Rate": "64 bpm",
"Sample Rate": "512 Hz",
...overrides,
};
return [
"Name,Private Patient Name",
...Object.entries(metadata).map(([key, value]) => `${key},${value}`),
"Lead,Voltage",
"I,0.001",
"I,-0.002",
"I,0.003",
].join("\n");
}
async function collectMembers(path: string, limits = LIMITS) {
const subject = await loadArchiveSubject();
const members: ArchiveMember[] = [];
for await (const member of subject.streamAppleHealthEcgMembers({
archivePath: path,
limits,
})) {
members.push(member);
}
return members;
}
afterEach(() => {
for (const dir of tempDirs.splice(0)) {
rmSync(dir, { recursive: true, force: true });
}
vi.restoreAllMocks();
});
describe("Apple Health ECG archive stream — path and resource bounds", () => {
it("streams only recognized HKElectrocardiogram CSV members", async () => {
const path = archivePath({
"apple_health_export/export.xml": "<HealthData/>",
"apple_health_export/electrocardiograms/ecg_2026-07-18.csv": validCsv(),
"apple_health_export/clinical-records/private.json": "{}",
});
const members = await collectMembers(path);
expect(members.map((member) => member.name)).toEqual([
"apple_health_export/electrocardiograms/ecg_2026-07-18.csv",
]);
expect(members[0].stream).toBeInstanceOf(Readable);
expect(members[0]).not.toHaveProperty("path");
});
it.each([
"../electrocardiograms/ecg.csv",
"apple_health_export/electrocardiograms/../../ecg.csv",
"/apple_health_export/electrocardiograms/ecg.csv",
"C:\\apple_health_export\\electrocardiograms\\ecg.csv",
])("rejects traversal or absolute member name %s", async (name) => {
await expect(
collectMembers(
archivePath({
"apple_health_export/export.xml": "<HealthData/>",
[name]: validCsv(),
}),
),
).rejects.toThrow(/unsafe|path|member/i);
});
it("rejects duplicate normalized ECG member names", async () => {
const first = "apple_health_export/electrocardiograms/ecg-recording-a.csv";
const second = "apple_health_export/electrocardiograms/ecg-recording-b.csv";
const path = archivePath({
"apple_health_export/export.xml": "<HealthData/>",
[first]: validCsv(),
[second]: validCsv(),
});
const duplicated = replaceSameLengthName(
Buffer.from(await import("node:fs").then((fs) => fs.readFileSync(path))),
second,
first,
);
writeFileSync(path, duplicated);
await expect(collectMembers(path)).rejects.toThrow(/duplicate/i);
});
it.each([
[
"encrypted",
(header: Buffer, central: boolean) =>
header.writeUInt16LE(1, central ? 8 : 6),
],
[
"unsupported compression",
(header: Buffer, central: boolean) =>
header.writeUInt16LE(99, central ? 10 : 8),
],
] as const)("rejects %s ECG entries", async (_label, mutate) => {
const member = "apple_health_export/electrocardiograms/ecg_2026-07-18.csv";
const path = archivePath({
"apple_health_export/export.xml": "<HealthData/>",
[member]: validCsv(),
});
const fs = await import("node:fs");
writeFileSync(
path,
mutateZipHeaders(fs.readFileSync(path), member, mutate),
);
await expect(collectMembers(path)).rejects.toThrow(
/encrypted|compression|unsupported/i,
);
});
it("enforces member-count and ECG-count caps before parsing", async () => {
const files: Record<string, string> = {
"apple_health_export/export.xml": "<HealthData/>",
};
for (let index = 0; index < 4; index += 1) {
files[
`apple_health_export/electrocardiograms/ecg_2026-07-${18 + index}.csv`
] = validCsv();
}
await expect(
collectMembers(archivePath(files), {
...LIMITS,
maxMembers: 3,
maxEcgMembers: 2,
}),
).rejects.toThrow(/member|count|limit/i);
});
it("rejects declared compression-ratio bombs", async () => {
const repetitive = `${"0,".repeat(2_000)}\n`;
await expect(
collectMembers(
archivePath({
"apple_health_export/export.xml": "<HealthData/>",
"apple_health_export/electrocardiograms/ecg_bomb.csv": repetitive,
}),
{ ...LIMITS, maxMemberBytes: 10_000, maxCompressionRatio: 2 },
),
).rejects.toThrow(/compression|ratio|bomb/i);
});
it("enforces actual decompressed member bytes even when metadata lies", async () => {
const member =
"apple_health_export/electrocardiograms/ecg_actual_bytes.csv";
const path = archivePath({
"apple_health_export/export.xml": "<HealthData/>",
[member]: `${"1,0.001\n".repeat(100)}`,
});
const fs = await import("node:fs");
const lied = mutateZipHeaders(
fs.readFileSync(path),
member,
(header, central) => {
if (central) header.writeUInt32LE(1, 24);
},
);
writeFileSync(path, lied);
await expect(
collectMembers(path, { ...LIMITS, maxMemberBytes: 64 }),
).rejects.toThrow(/byte|size|limit/i);
});
it("enforces the running total across otherwise valid ECG members", async () => {
await expect(
collectMembers(
archivePath({
"apple_health_export/export.xml": "<HealthData/>",
"apple_health_export/electrocardiograms/ecg_a.csv": validCsv(),
"apple_health_export/electrocardiograms/ecg_b.csv": validCsv(),
}),
{ ...LIMITS, maxTotalEcgBytes: validCsv().length + 5 },
),
).rejects.toThrow(/total|byte|limit/i);
});
});
describe("Apple Health HKElectrocardiogram CSV parser", () => {
async function parse(csv: string, maxSamples = 8) {
const subject = await loadParserSubject();
return subject.parseAppleHealthEcgCsv({
memberName: "apple_health_export/electrocardiograms/ecg_2026-07-18.csv",
stream: Readable.from([Buffer.from(csv)]),
maxSamples,
});
}
it("normalizes a valid recording without returning patient/source labels", async () => {
const parsed = await parse(validCsv());
expect(parsed).toMatchObject({
recordedAt: new Date("2026-07-18T06:14:03.000Z"),
samplingFrequency: 512,
samples: [1, -2, 3],
lead: "I",
averageHeartRate: 64,
rhythmClassification: "NOT_DETECTED",
});
expect(JSON.stringify(parsed)).not.toMatch(/Private Patient|Apple|WHOOP/i);
});
it.each([
["Sinus Rhythm", "NOT_DETECTED"],
["Atrial Fibrillation", "IRREGULAR"],
["Inconclusive", "INCONCLUSIVE"],
["Localized or unknown verdict", null],
])("passes through device classification %s only", async (raw, expected) => {
await expect(
parse(validCsv({ Classification: raw })),
).resolves.toMatchObject({ rhythmClassification: expected });
});
it.each([
["bad timestamp", validCsv({ "Recorded Date": "not-a-date" })],
["bad frequency", validCsv({ "Sample Rate": "not-a-rate" })],
["non-finite voltage", validCsv().replace("I,-0.002", "I,Infinity")],
["missing samples", validCsv().split("Lead,Voltage")[0]],
])("rejects malformed %s", async (_label, csv) => {
await expect(parse(csv)).rejects.toThrow(/invalid|malformed|sample|date/i);
});
it("stops reading once the sample cap is exceeded", async () => {
await expect(parse(validCsv(), 2)).rejects.toThrow(/sample|limit/i);
});
it("never logs or returns plaintext health values on failure", async () => {
const subject = await loadParserSubject();
const spies: MockInstance[] = [
vi.spyOn(console, "log").mockImplementation(() => {}),
vi.spyOn(console, "warn").mockImplementation(() => {}),
vi.spyOn(console, "error").mockImplementation(() => {}),
];
const secret = "patient-secret-waveform-0.123456";
let message = "";
try {
await subject.parseAppleHealthEcgCsv({
memberName: "apple_health_export/electrocardiograms/ecg_2026-07-18.csv",
stream: Readable.from([Buffer.from(`${validCsv()}\nI,${secret}`)]),
maxSamples: 8,
});
} catch (error) {
message = error instanceof Error ? error.message : String(error);
}
expect(message).not.toContain(secret);
expect(spies.every((spy) => spy.mock.calls.length === 0)).toBe(true);
});
});
@@ -10,9 +10,17 @@
* summary), keep a real scale ahead of WHOOP's body-measurement estimate for
* weight, and rank WHOOP's device-native Recovery above the COMPUTED proxy.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { pickCanonicalSourceRows } from "@/lib/analytics/source-priority";
import { mapAppleHealthEntry } from "@/lib/measurements/apple-health-mapping";
const appleImporterSource = readFileSync(
join(process.cwd(), "src/lib/measurements/import-apple-health-export.ts"),
"utf8",
);
function isoDayKey(d: Date): string {
return d.toISOString().slice(0, 10);
@@ -119,3 +127,45 @@ describe("cross-source priority — WHOOP + Apple Health", () => {
expect(out.canonicalRows[0].value).toBe(71);
});
});
describe("WHOOP-written HealthKit records remain source-agnostic", () => {
it("maps a supported HealthKit type independently of its writer label", () => {
const input = {
hkIdentifier: "HKQuantityTypeIdentifierHeartRate",
value: 64,
unit: "count/min",
startDate: "2026-07-18 08:14:03 +0200",
endDate: "2026-07-18 08:14:04 +0200",
};
const whoopWritten = mapAppleHealthEntry(input);
const appleWritten = mapAppleHealthEntry(input);
expect(whoopWritten).toEqual(appleWritten);
expect(whoopWritten).toMatchObject({
type: "PULSE",
value: 64,
});
expect(whoopWritten).not.toHaveProperty("sourceName");
expect(whoopWritten).not.toHaveProperty("writer");
});
it("persists supported export.xml rows as APPLE_HEALTH, never as a raw writer label", () => {
expect(appleImporterSource).toMatch(
/measurement\.createManyAndReturn\([\s\S]*source:\s*"APPLE_HEALTH"/,
);
expect(appleImporterSource).not.toMatch(
/source:\s*attrs\.(?:sourceName|device)/,
);
});
it("does not treat WHOOP writer metadata as evidence of a WHOOP ECG export", () => {
const ecgMapping = mapAppleHealthEntry({
hkIdentifier: "HKElectrocardiogram",
value: 1,
unit: "count",
startDate: "2026-07-18 08:14:03 +0200",
endDate: "2026-07-18 08:14:04 +0200",
});
expect(ecgMapping).toBeNull();
});
});
@@ -10,6 +10,8 @@
* enabled resource soft-skipped and nothing imported.
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
import { readFileSync } from "node:fs";
import { join } from "node:path";
const {
recordSyncSuccess,
@@ -80,6 +82,11 @@ import {
import { syncUserWhoop } from "../sync";
import { WhoopApiError } from "../response-classifier";
const directWhoopSyncSource = readFileSync(
join(process.cwd(), "src/lib/whoop/sync.ts"),
"utf8",
);
/** A resource sync that 403s its collection and soft-skips, like the real one. */
async function softSkip403(...args: unknown[]): Promise<number> {
return handleCollectionFetchError(
@@ -225,3 +232,35 @@ describe("syncWhoopResourceWithStatus — per-resource lastSuccessAt stamp", ()
expect(recordSyncSuccess).not.toHaveBeenCalled();
});
});
describe("direct WHOOP API regression beside Apple Health ECG support", () => {
it("keeps every established direct WHOOP resource in the orchestration gate", async () => {
syncUserRecovery.mockResolvedValue(1);
syncUserSleep.mockResolvedValue(1);
syncUserCycle.mockResolvedValue(1);
syncUserWorkout.mockResolvedValue(1);
syncUserBody.mockResolvedValue(1);
await expect(syncUserWhoop("user-direct-whoop")).resolves.toMatchObject({
imported: 5,
});
for (const resource of [
syncUserRecovery,
syncUserSleep,
syncUserCycle,
syncUserWorkout,
syncUserBody,
]) {
expect(resource).toHaveBeenCalledWith(
"user-direct-whoop",
expect.any(Object),
);
}
});
it("makes no direct WHOOP ECG support claim", () => {
expect(directWhoopSyncSource).not.toMatch(
/syncUser(?:Ecg|Electrocardiogram)|sync-(?:ecg|electrocardiogram)/i,
);
});
});
@@ -0,0 +1,365 @@
import { existsSync, mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createHash } from "node:crypto";
import type { Job } from "pg-boss";
import { strToU8, zipSync } from "fflate";
import {
afterAll,
afterEach,
beforeEach,
describe,
expect,
it,
vi,
} from "vitest";
import { decryptWaveformFromBytes } from "@/lib/withings/ecg-waveform-codec";
import {
APPLE_HEALTH_IMPORT_PARSER_REVISION,
_setWorkerPrismaForTests,
handleAppleHealthImport,
type AppleHealthImportPayload,
} from "@/lib/jobs/apple-health-import-worker";
import { getPrismaClient, truncateAllTables } from "./setup";
vi.mock("@/lib/db-compat", () => ({
ensureDbCompatibility: vi.fn().mockResolvedValue(undefined),
}));
vi.mock("@/lib/jobs/boss-instance", () => ({
getGlobalBoss: vi.fn(() => null),
}));
vi.mock("@/lib/rollups/measurement-rollups", () => ({
recomputeUserRollups: vi.fn(async () => {}),
}));
const USER_ID = "user-apple-ecg-contract";
let jobSequence = 0;
function supportedWhoopXml(): string {
return `<?xml version="1.0" encoding="UTF-8"?>
<HealthData locale="en_US">
<Record type="HKQuantityTypeIdentifierHeartRate"
unit="count/min"
startDate="2026-07-18 08:14:03 +0200"
endDate="2026-07-18 08:14:04 +0200"
value="64"
sourceName="WHOOP"
sourceVersion="5.2.1"
device="WHOOP MG"/>
</HealthData>`;
}
function ecgCsv(
input: {
recordedAt?: string;
classification?: string;
samples?: number[];
marker?: string;
} = {},
): string {
const samples = input.samples ?? [0.001, -0.002, 0.003];
return [
`Name,${input.marker ?? "patient-name-must-not-persist"}`,
`Recorded Date,${input.recordedAt ?? "2026-07-18 08:14:03 +0200"}`,
`Classification,${input.classification ?? "Sinus Rhythm"}`,
"Average Heart Rate,64 bpm",
"Sample Rate,512 Hz",
"Lead,Voltage",
...samples.map((sample) => `I,${sample}`),
].join("\n");
}
function createArchive(ecgs: Array<{ filename: string; csv: string }>): {
path: string;
bytes: Buffer;
} {
const dir = mkdtempSync(join(tmpdir(), "healthlog-ecg-integration-"));
const path = join(dir, "export.zip");
const files: Record<string, Uint8Array> = {
"apple_health_export/export.xml": strToU8(supportedWhoopXml()),
};
for (const ecg of ecgs) {
files[`apple_health_export/electrocardiograms/${ecg.filename}`] = strToU8(
ecg.csv,
);
}
const bytes = Buffer.from(zipSync(files, { level: 6 }));
writeFileSync(path, bytes);
return { path, bytes };
}
async function runArchive(
ecgs: Array<{ filename: string; csv: string }>,
): Promise<{
outcome: Awaited<ReturnType<typeof handleAppleHealthImport>>;
mirror: Awaited<
ReturnType<ReturnType<typeof getPrismaClient>["importJob"]["findUnique"]>
>;
uploadPath: string;
}> {
const prisma = getPrismaClient();
const archive = createArchive(ecgs);
jobSequence += 1;
const jobId = `apple-ecg-job-${jobSequence}`;
const mirror = await prisma.importJob.create({
data: {
userId: USER_ID,
pgBossJobId: jobId,
status: "queued",
uploadBytes: archive.bytes.length,
uploadSha256: createHash("sha256").update(archive.bytes).digest("hex"),
parserRevision: APPLE_HEALTH_IMPORT_PARSER_REVISION,
},
});
const data: AppleHealthImportPayload = {
userId: USER_ID,
uploadPath: archive.path,
uploadBytes: archive.bytes.length,
enqueuedAt: "2026-07-18T06:14:03.000Z",
};
const outcome = await handleAppleHealthImport({
id: jobId,
data,
} as unknown as Job<AppleHealthImportPayload>);
return {
outcome,
mirror: await prisma.importJob.findUnique({ where: { id: mirror.id } }),
uploadPath: archive.path,
};
}
beforeEach(async () => {
await truncateAllTables(getPrismaClient());
_setWorkerPrismaForTests(getPrismaClient());
await getPrismaClient().user.create({
data: {
id: USER_ID,
username: "apple-ecg-contract",
email: "apple-ecg-contract@example.test",
timezone: "Europe/Berlin",
},
});
});
afterEach(() => {
vi.restoreAllMocks();
});
afterAll(() => {
_setWorkerPrismaForTests(null);
});
describe("Apple Health auxiliary ECG import — real Postgres", () => {
it("persists an encrypted APPLE_HEALTH recording and decrypts through the normal codec", async () => {
const marker = "plaintext-waveform-marker-0.001--0.002";
const run = await runArchive([
{ filename: "ecg_2026-07-18.csv", csv: ecgCsv({ marker }) },
]);
expect(run.outcome.ok).toBe(true);
const rows = await getPrismaClient().ecgRecording.findMany({
where: { userId: USER_ID },
});
expect(rows).toHaveLength(1);
expect(rows[0]).toMatchObject({
source: "APPLE_HEALTH",
samplingFrequency: 512,
sampleCount: 3,
averageHeartRate: 64,
lead: "I",
rhythmClassification: "NOT_DETECTED",
});
expect(decryptWaveformFromBytes(rows[0].waveformEncrypted)).toEqual([
1, -2, 3,
]);
expect(Buffer.from(rows[0].waveformEncrypted).toString("utf8")).not.toMatch(
/0\.001|-0\.002|plaintext-waveform-marker/,
);
expect(JSON.stringify(run.mirror)).not.toContain(marker);
expect(existsSync(run.uploadPath)).toBe(false);
});
it("deduplicates identical content across filenames and keeps distinct recordings distinct", async () => {
const same = ecgCsv();
await runArchive([{ filename: "first-name.csv", csv: same }]);
await runArchive([{ filename: "renamed-copy.csv", csv: same }]);
expect(
await getPrismaClient().ecgRecording.count({
where: { userId: USER_ID },
}),
).toBe(1);
await runArchive([
{
filename: "different.csv",
csv: ecgCsv({
recordedAt: "2026-07-18 09:14:03 +0200",
samples: [0.004, -0.005, 0.006],
}),
},
]);
const rows = await getPrismaClient().ecgRecording.findMany({
where: { userId: USER_ID },
orderBy: { recordedAt: "asc" },
});
expect(rows).toHaveLength(2);
expect(new Set(rows.map((row) => row.externalRecordingId)).size).toBe(2);
});
it("keeps content identity tenant-scoped", async () => {
const same = ecgCsv();
await runArchive([{ filename: "same.csv", csv: same }]);
const other = await getPrismaClient().user.create({
data: {
username: "apple-ecg-other",
email: "apple-ecg-other@example.test",
},
});
const archive = createArchive([{ filename: "same.csv", csv: same }]);
jobSequence += 1;
const jobId = `apple-ecg-job-${jobSequence}`;
await getPrismaClient().importJob.create({
data: {
userId: other.id,
pgBossJobId: jobId,
status: "queued",
uploadBytes: archive.bytes.length,
parserRevision: APPLE_HEALTH_IMPORT_PARSER_REVISION,
},
});
await handleAppleHealthImport({
id: jobId,
data: {
userId: other.id,
uploadPath: archive.path,
uploadBytes: archive.bytes.length,
enqueuedAt: "2026-07-18T06:14:03.000Z",
},
} as unknown as Job<AppleHealthImportPayload>);
const rows = await getPrismaClient().ecgRecording.findMany({
orderBy: { userId: "asc" },
});
expect(rows).toHaveLength(2);
expect(rows[0].externalRecordingId).toBe(rows[1].externalRecordingId);
expect(rows[0].userId).not.toBe(rows[1].userId);
});
it("keeps valid WHOOP-written export.xml data when an ECG is malformed", async () => {
const secret = "malformed-private-waveform-secret";
const warning = vi.spyOn(console, "warn").mockImplementation(() => {});
const error = vi.spyOn(console, "error").mockImplementation(() => {});
const run = await runArchive([
{
filename: "malformed.csv",
csv: ecgCsv().replace("I,-0.002", `I,${secret}`),
},
]);
expect(run.outcome.ok).toBe(true);
expect(run.mirror?.status).toBe("done");
const measurements = await getPrismaClient().measurement.findMany({
where: { userId: USER_ID },
});
expect(measurements).toHaveLength(1);
expect(measurements[0]).toMatchObject({
source: "APPLE_HEALTH",
type: "PULSE",
value: 64,
});
expect(JSON.stringify(measurements)).not.toContain("WHOOP");
expect(await getPrismaClient().ecgRecording.count()).toBe(0);
expect(run.mirror?.result).toMatchObject({
ecg: {
discovered: 1,
imported: 0,
updated: 0,
skipped: 0,
failed: 1,
},
});
expect(JSON.stringify(run.mirror)).not.toContain(secret);
expect(
JSON.stringify([...warning.mock.calls, ...error.mock.calls]),
).not.toContain(secret);
});
it("fails an unsafe auxiliary member softly while valid XML survives", async () => {
const run = await runArchive([
{ filename: "../escaped-ecg.csv", csv: ecgCsv() },
]);
expect(run.outcome.ok).toBe(true);
expect(run.mirror?.status).toBe("done");
expect(
await getPrismaClient().measurement.count({ where: { userId: USER_ID } }),
).toBe(1);
expect(await getPrismaClient().ecgRecording.count()).toBe(0);
expect(run.mirror?.result).toMatchObject({
ecg: { discovered: 1, imported: 0, failed: 1 },
});
});
it("rolls back a failed ECG write while preserving the valid XML transaction", async () => {
const prisma = getPrismaClient();
await prisma.$executeRawUnsafe(`
CREATE OR REPLACE FUNCTION reject_apple_health_ecg_contract()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
IF NEW.source = 'APPLE_HEALTH' THEN
RAISE EXCEPTION 'synthetic ECG write failure';
END IF;
RETURN NEW;
END;
$$;
CREATE TRIGGER reject_apple_health_ecg_contract
BEFORE INSERT OR UPDATE ON ecg_recordings
FOR EACH ROW EXECUTE FUNCTION reject_apple_health_ecg_contract();
`);
try {
const run = await runArchive([
{ filename: "mid-write.csv", csv: ecgCsv() },
]);
expect(run.outcome.ok).toBe(true);
expect(
await prisma.measurement.count({ where: { userId: USER_ID } }),
).toBe(1);
expect(
await prisma.ecgRecording.count({ where: { userId: USER_ID } }),
).toBe(0);
expect(run.mirror?.result).toMatchObject({
ecg: { discovered: 1, failed: 1 },
});
expect(JSON.stringify(run.mirror)).not.toContain(
"synthetic ECG write failure",
);
} finally {
await prisma.$executeRawUnsafe(
`DROP TRIGGER IF EXISTS reject_apple_health_ecg_contract ON ecg_recordings`,
);
await prisma.$executeRawUnsafe(
`DROP FUNCTION IF EXISTS reject_apple_health_ecg_contract()`,
);
}
});
it("stores unknown device classifications as null without waveform diagnosis", async () => {
await runArchive([
{
filename: "unknown-classification.csv",
csv: ecgCsv({ classification: "Localized Future Verdict" }),
},
]);
const row = await getPrismaClient().ecgRecording.findFirstOrThrow({
where: { userId: USER_ID },
});
expect(row.rhythmClassification).toBeNull();
});
});