mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
fix(ecg): let a whole sweep of recordings through the ingest limit
The iPhone app posts every ECG its sweep has not yet consumed and, on a 429, stops and restarts the same sweep on its next wake. With a limit of 60 per minute a sweep of 61 or more could never finish: the same last recording was refused on every sync, so the newest ECG never arrived. The limit is 600 per minute now, enough for more than a decade of weekly strips in one sweep. Refs #1060
This commit is contained in:
1 parent
fc47006c88
commit
82ccac2fd0
2 files changed
+27
-4
No files matched your search
@@ -64,10 +64,15 @@ const MAX_SAMPLES = 32_768;
|
||||
// any real recording. A DoS ceiling, not a tight bound.
|
||||
const MAX_BODY_BYTES = 2 * 1024 * 1024;
|
||||
|
||||
// One recording per request, so the limit is per recording. A person records
|
||||
// a handful of strips a week; 60/min still drains a full watch history in
|
||||
// minutes and stops a leaked wildcard token from saturating the write path.
|
||||
const INGEST_RATE_LIMIT_MAX = 60;
|
||||
// One recording per request, so the limit is per recording. The iPhone app
|
||||
// sends every recording its sweep has not yet consumed in one go and, on a
|
||||
// 429, stops and starts the same sweep again on its next wake. A window that
|
||||
// held more than the limit could therefore never finish: with 60 per minute
|
||||
// the 61st recording was refused on every sync and the newest ECG never
|
||||
// arrived (#1060). 600 covers more than a decade of weekly strips in one
|
||||
// sweep and still stops a leaked token from saturating the write path; a
|
||||
// request is at most 2 MB and usually about 100 KB.
|
||||
const INGEST_RATE_LIMIT_MAX = 600;
|
||||
const INGEST_RATE_LIMIT_WINDOW_MS = 60 * 1000;
|
||||
|
||||
// Only the three ECG verdicts. `RhythmClassification` in the database has six
|
||||
|
||||
@@ -205,6 +205,24 @@ describe("POST /api/insights/ecg — round trip through the real routes", () =>
|
||||
expect(posted.data.durationSeconds).toBeCloseTo(SAMPLES.length / 512, 10);
|
||||
});
|
||||
|
||||
it("takes a whole sweep of recordings in one go (#1060)", async () => {
|
||||
// The app posts every recording its sweep has not consumed and restarts
|
||||
// the sweep after a 429; a limit below the sweep's size refused the same
|
||||
// last recording on every sync. Seventy distinct strips must all land.
|
||||
const statuses: number[] = [];
|
||||
for (let i = 0; i < 70; i += 1) {
|
||||
const posted = await ingest(
|
||||
ingestPayload({
|
||||
externalRecordingId: `sweep-${i}`,
|
||||
recordedAt: new Date(Date.UTC(2026, 5, 1, 8, i)).toISOString(),
|
||||
}),
|
||||
);
|
||||
statuses.push(posted.status);
|
||||
}
|
||||
expect(statuses.filter((s) => s === 429)).toHaveLength(0);
|
||||
expect(await getPrismaClient().ecgRecording.count()).toBe(70);
|
||||
});
|
||||
|
||||
it("never writes the waveform as plaintext", async () => {
|
||||
const posted = await ingest(ingestPayload());
|
||||
const row = await getPrismaClient().ecgRecording.findUniqueOrThrow({
|
||||
|
||||
Reference in new issue
Block a user