From ae294573ef8787e228ac4dbc7b0d9e3055b8f155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Mon, 27 Jul 2026 23:23:53 +0200 Subject: [PATCH] test(jobs): follow the queue guards onto the new binding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thirty-three suites read a registrar as source text and matched `boss.work` next to a queue constant. They now match `createAndWork`, and the seven schedule tuples that grew a `cronIsTheRetry` third element assert that element rather than accepting any arity — a guard that accepts more than it did before is a guard that notices less. Two negative assertions were tightened while they were open: the "has no cron" checks on the arrival and workout-insight queues ended at a literal `]`, so a cron added WITH send options would have slipped past. The behavioural repairs carry the real change. Handlers that resolved to undefined now resolve to their outcome, so the assertions name the counts instead of asserting nothing. The reconcile tick's test said it swallows a failure instead of throwing; it reports a failed outcome now, and the test says that, with the comment pointing at the retryLimit that carries the old anti-retry-loop concern. --- .../apple-health-import-reconcile.test.ts | 40 ++++++++++++------- .../apple-health-import-send-policy.test.ts | 7 +++- .../coach-memory-refresh-queue.test.ts | 6 +-- .../coach-message-cleanup-queue.test.ts | 8 ++-- .../cumulative-pr-rederive-queue.test.ts | 6 +-- .../__tests__/cycle-reminder-queue.test.ts | 6 +-- src/lib/jobs/__tests__/daily-briefing.test.ts | 4 +- .../jobs/__tests__/data-arrival-queue.test.ts | 14 ++++--- .../__tests__/drain-cumulative-queue.test.ts | 6 +-- src/lib/jobs/__tests__/fitbit-queues.test.ts | 12 +++--- .../__tests__/google-health-queues.test.ts | 10 ++--- .../__tests__/insight-pregenerate.test.ts | 4 +- .../__tests__/insight-status-generate.test.ts | 6 +-- .../__tests__/intake-slot-dedup-queue.test.ts | 6 +-- .../__tests__/mcp-token-cleanup-queue.test.ts | 8 ++-- .../mean-consolidation-queue.test.ts | 6 +-- .../measurement-reminder-queue.test.ts | 12 +++--- .../measurement-tombstone-cleanup.test.ts | 6 +-- .../jobs/__tests__/nightscout-queues.test.ts | 10 ++--- .../__tests__/period-narrative-warm.test.ts | 6 ++- .../__tests__/recovery-score-queue.test.ts | 6 +-- .../__tests__/sleep-timeline-queues.test.ts | 6 +-- .../__tests__/status-cron-candidates.test.ts | 4 +- .../step-consolidation-repair-queue.test.ts | 6 +-- .../stress-strain-retention-queue.test.ts | 18 ++++----- .../__tests__/tls-pin-monitor-queue.test.ts | 6 +-- src/lib/jobs/__tests__/whoop-queues.test.ts | 14 +++---- .../__tests__/whoop-resource-sync.test.ts | 13 +++++- .../__tests__/withings-ecg-worker.test.ts | 28 ++++++++++--- .../jobs/__tests__/withings-queues.test.ts | 6 +-- .../withings-subscription-retry.test.ts | 25 +++++++++++- .../__tests__/workout-insight-queue.test.ts | 8 ++-- .../reminder/__tests__/poll-cohort.test.ts | 12 +++++- 33 files changed, 208 insertions(+), 127 deletions(-) diff --git a/src/lib/jobs/__tests__/apple-health-import-reconcile.test.ts b/src/lib/jobs/__tests__/apple-health-import-reconcile.test.ts index 13e240628..219978aae 100644 --- a/src/lib/jobs/__tests__/apple-health-import-reconcile.test.ts +++ b/src/lib/jobs/__tests__/apple-health-import-reconcile.test.ts @@ -73,12 +73,16 @@ describe("periodic reconcile — wiring", () => { /const schedules[\s\S]*?=\s*\[([\s\S]*?)\];/, ); expect(schedules).not.toBeNull(); + // The third element pins the retry policy. It is load-bearing now that + // the handler reports a failed outcome instead of swallowing: without + // `cronIsTheRetry` a database outage would run the same doomed sweep + // three times per tick, and the next tick is already the retry. expect(schedules![1]).toMatch( - /\[IMPORT_JOB_RECONCILE_QUEUE,\s*IMPORT_JOB_RECONCILE_CRON\]/, + /\[IMPORT_JOB_RECONCILE_QUEUE,\s*IMPORT_JOB_RECONCILE_CRON,\s*cronIsTheRetry\]/, ); expect(maintenanceSource).toMatch( - /boss\.work\(\s*IMPORT_JOB_RECONCILE_QUEUE[\s\S]{0,120}handleImportJobReconcileTick/, + /createAndWork\(\s*boss,\s*IMPORT_JOB_RECONCILE_QUEUE[\s\S]{0,120}handleImportJobReconcileTick/, ); }); }); @@ -163,24 +167,30 @@ describe("reconcileOrphanImportJobs", () => { }); describe("handleImportJobReconcileTick", () => { - it("delegates to reconcileOrphanImportJobs and resolves on success", async () => { + it("delegates to reconcileOrphanImportJobs and reports a done outcome", async () => { mocks.findMany.mockResolvedValue([]); - await expect( - handleImportJobReconcileTick([] as never), - ).resolves.toBeUndefined(); + await expect(handleImportJobReconcileTick([] as never)).resolves.toEqual({ + ok: true, + did: {}, + }); expect(mocks.findMany).toHaveBeenCalledTimes(1); }); - it("swallows a reconcile failure instead of throwing", async () => { - mocks.findMany.mockRejectedValue(new Error("db unavailable")); + it("reports a failed outcome when the reconcile pass throws", async () => { + const cause = new Error("db unavailable"); + mocks.findMany.mockRejectedValue(cause); - // A background sweep this cheap must never spam pg-boss's - // retry/backoff machinery — it just re-runs on the next 15-minute - // tick. The handler must resolve even when the underlying reconcile - // pass throws. - await expect( - handleImportJobReconcileTick([] as never), - ).resolves.toBeUndefined(); + // The handler used to swallow this and resolve, which left a sweep + // that had not run for hours indistinguishable from one that found + // nothing to do. It now names the failure, `runJob` rethrows it, and + // pg-boss records a failed job. The queue carries `cronIsTheRetry` + // (retryLimit 0) because the next 15-minute tick is the retry — a + // database that is down stays down for an immediate re-run. + await expect(handleImportJobReconcileTick([] as never)).resolves.toEqual({ + ok: false, + reason: "apple health import reconcile failed", + cause, + }); }); }); diff --git a/src/lib/jobs/__tests__/apple-health-import-send-policy.test.ts b/src/lib/jobs/__tests__/apple-health-import-send-policy.test.ts index 5e5de5c6c..d28594276 100644 --- a/src/lib/jobs/__tests__/apple-health-import-send-policy.test.ts +++ b/src/lib/jobs/__tests__/apple-health-import-send-policy.test.ts @@ -87,11 +87,14 @@ describe("apple health import — parser revision boundary", () => { expect(allQueues).not.toBeNull(); expect(allQueues![1]).toMatch(/\bAPPLE_HEALTH_IMPORT_V2_QUEUE\b/); expect(allQueues![1]).toMatch(/\bAPPLE_HEALTH_IMPORT_LEGACY_QUEUE\b/); + // The v2 window is the wider of the two: its binding wraps the batch in + // a loop that keeps the first failed outcome, so the handler call sits + // further from the queue name than the legacy bridge's does. expect(maintenanceSource).toMatch( - /boss\.work[\s\S]{0,200}APPLE_HEALTH_IMPORT_V2_QUEUE[\s\S]{0,400}handleAppleHealthImport/, + /createAndWork[\s\S]{0,200}APPLE_HEALTH_IMPORT_V2_QUEUE[\s\S]{0,600}handleAppleHealthImport/, ); expect(maintenanceSource).toMatch( - /boss\.work[\s\S]{0,200}APPLE_HEALTH_IMPORT_LEGACY_QUEUE[\s\S]{0,400}migrateLegacyAppleHealthImport/, + /createAndWork[\s\S]{0,200}APPLE_HEALTH_IMPORT_LEGACY_QUEUE[\s\S]{0,400}migrateLegacyAppleHealthImport/, ); }); diff --git a/src/lib/jobs/__tests__/coach-memory-refresh-queue.test.ts b/src/lib/jobs/__tests__/coach-memory-refresh-queue.test.ts index a8ad973da..bb6147eab 100644 --- a/src/lib/jobs/__tests__/coach-memory-refresh-queue.test.ts +++ b/src/lib/jobs/__tests__/coach-memory-refresh-queue.test.ts @@ -4,7 +4,7 @@ * * Same source-text-grep approach as the period-narrative / stress-strain * guards: assert the queue is imported, registered in `allQueues`, and wired - * to a `boss.work` handler that runs the refresh pipeline — without booting + * to a `createAndWork` handler that runs the refresh pipeline — without booting * pg-boss + Prisma. An unregistered queue silently never runs (the recurring * past bug this guards against; v1.4.37 W10 caught exactly this class). */ @@ -41,9 +41,9 @@ describe("reminder-worker — coach-memory-refresh wiring", () => { expect(allQueuesBlock()).toMatch(/\bCOACH_MEMORY_REFRESH_QUEUE\b/); }); - it("registers a boss.work handler that runs the refresh pipeline", () => { + it("registers a createAndWork handler that runs the refresh pipeline", () => { expect(source).toMatch( - /boss\.work[\s\S]{0,200}COACH_MEMORY_REFRESH_QUEUE[\s\S]{0,400}runCoachMemoryRefresh/, + /createAndWork[\s\S]{0,200}COACH_MEMORY_REFRESH_QUEUE[\s\S]{0,600}runCoachMemoryRefresh/, ); }); }); diff --git a/src/lib/jobs/__tests__/coach-message-cleanup-queue.test.ts b/src/lib/jobs/__tests__/coach-message-cleanup-queue.test.ts index e00d24206..0567b4e40 100644 --- a/src/lib/jobs/__tests__/coach-message-cleanup-queue.test.ts +++ b/src/lib/jobs/__tests__/coach-message-cleanup-queue.test.ts @@ -2,7 +2,7 @@ * v1.18.7 — coach-message-cleanup queue registration guard. * * Same source-text-grep approach as the other queue-wiring guards: assert the - * queue is registered in `allQueues`, scheduled, and wired to a `boss.work` + * queue is registered in `allQueues`, scheduled, and wired to a `createAndWork` * handler — without booting pg-boss + Prisma. An unregistered queue silently * never drains (the recurring v1.4.37 dead-queue bug), which would leave the * encrypted Coach history growing unbounded with the retention job dark. @@ -41,13 +41,13 @@ describe("reminder-worker — coach-message-cleanup wiring", () => { it("schedules the coach-message-cleanup cron", () => { expect(workerSource).toMatch( - /\[COACH_MESSAGE_CLEANUP_QUEUE,\s*COACH_MESSAGE_CLEANUP_CRON\]/, + /\[COACH_MESSAGE_CLEANUP_QUEUE,\s*COACH_MESSAGE_CLEANUP_CRON,\s*cronIsTheRetry\]/, ); }); - it("registers a boss.work handler for the queue", () => { + it("registers a createAndWork handler for the queue", () => { expect(workerSource).toMatch( - /boss\.work[\s\S]{0,200}COACH_MESSAGE_CLEANUP_QUEUE[\s\S]{0,200}handleCoachMessageCleanup/, + /createAndWork[\s\S]{0,200}COACH_MESSAGE_CLEANUP_QUEUE[\s\S]{0,200}handleCoachMessageCleanup/, ); }); diff --git a/src/lib/jobs/__tests__/cumulative-pr-rederive-queue.test.ts b/src/lib/jobs/__tests__/cumulative-pr-rederive-queue.test.ts index 61aa8c699..18a28299b 100644 --- a/src/lib/jobs/__tests__/cumulative-pr-rederive-queue.test.ts +++ b/src/lib/jobs/__tests__/cumulative-pr-rederive-queue.test.ts @@ -3,7 +3,7 @@ * registration guard. * * Same source-text-grep approach as the step-consolidation-repair guard: - * assert the queue is registered in `allQueues`, a `boss.work` handler is + * assert the queue is registered in `allQueues`, a `createAndWork` handler is * wired against it, and the boot-discovery enqueue helper fires — without * booting pg-boss + Prisma. An unregistered queue silently never drains * (the v1.4.37 dead-queue class). @@ -34,9 +34,9 @@ describe("reminder-worker — cumulative-pr-rederive wiring", () => { expect(allQueuesMatch![1]).toMatch(/\bCUMULATIVE_PR_REDERIVE_QUEUE\b/); }); - it("registers a boss.work handler against the queue", () => { + it("registers a createAndWork handler against the queue", () => { expect(source).toMatch( - /boss\.work[\s\S]{0,200}CUMULATIVE_PR_REDERIVE_QUEUE[\s\S]{0,400}runCumulativePrRederivationForUser/, + /createAndWork[\s\S]{0,200}CUMULATIVE_PR_REDERIVE_QUEUE[\s\S]{0,400}runCumulativePrRederivationForUser/, ); }); diff --git a/src/lib/jobs/__tests__/cycle-reminder-queue.test.ts b/src/lib/jobs/__tests__/cycle-reminder-queue.test.ts index e060711e6..8c9158af2 100644 --- a/src/lib/jobs/__tests__/cycle-reminder-queue.test.ts +++ b/src/lib/jobs/__tests__/cycle-reminder-queue.test.ts @@ -5,7 +5,7 @@ * consolidation registration guards: assert the cycle-reminder queue is * declared, listed in `allQueues` (the v1.4.37 dead-queue lesson — an * unregistered queue is never provisioned and the schedule silently - * no-ops), scheduled on its cron, and wired to a `boss.work` handler that + * no-ops), scheduled on its cron, and wired to a `createAndWork` handler that * delegates to `runCycleReminderTick`. No pg-boss / Prisma boot. */ import { readFileSync } from "node:fs"; @@ -57,9 +57,9 @@ describe("reminder-worker — cycle-reminder wiring", () => { expect(source).toMatch(/\[CYCLE_REMINDER_QUEUE,\s*CYCLE_REMINDER_CRON\]/); }); - it("registers a boss.work handler that runs the cycle-reminder tick", () => { + it("registers a createAndWork handler that runs the cycle-reminder tick", () => { expect(source).toMatch( - /boss\.work[\s\S]{0,200}CYCLE_REMINDER_QUEUE[\s\S]{0,200}handleCycleReminderCheck/, + /createAndWork[\s\S]{0,200}CYCLE_REMINDER_QUEUE[\s\S]{0,200}handleCycleReminderCheck/, ); expect(source).toMatch( /handleCycleReminderCheck[\s\S]{0,400}runCycleReminderTick/, diff --git a/src/lib/jobs/__tests__/daily-briefing.test.ts b/src/lib/jobs/__tests__/daily-briefing.test.ts index 8ae057493..ed34641db 100644 --- a/src/lib/jobs/__tests__/daily-briefing.test.ts +++ b/src/lib/jobs/__tests__/daily-briefing.test.ts @@ -98,9 +98,9 @@ describe("reminder-worker — daily-briefing wiring", () => { ); }); - it("wires a boss.work handler that runs the tick", () => { + it("wires a createAndWork handler that runs the tick", () => { expect(registrar).toMatch( - /boss\.work[\s\S]{0,200}DAILY_BRIEFING_QUEUE[\s\S]{0,400}runDailyBriefingTick/, + /createAndWork[\s\S]{0,200}DAILY_BRIEFING_QUEUE[\s\S]{0,400}runDailyBriefingTick/, ); }); diff --git a/src/lib/jobs/__tests__/data-arrival-queue.test.ts b/src/lib/jobs/__tests__/data-arrival-queue.test.ts index 9deeb634e..a86f8057f 100644 --- a/src/lib/jobs/__tests__/data-arrival-queue.test.ts +++ b/src/lib/jobs/__tests__/data-arrival-queue.test.ts @@ -39,9 +39,9 @@ describe("data-arrival queue wiring", () => { expect(allQueues![1]).toMatch(/\bDATA_ARRIVAL_QUEUE\b/); }); - it("binds a boss.work handler that drains it", () => { + it("binds a createAndWork handler that drains it", () => { expect(statusRegistrar).toMatch( - /boss\.work[\s\S]{0,200}DATA_ARRIVAL_QUEUE[\s\S]{0,200}handleDataArrival/, + /createAndWork[\s\S]{0,200}DATA_ARRIVAL_QUEUE[\s\S]{0,200}handleDataArrival/, ); }); @@ -61,7 +61,11 @@ describe("data-arrival queue wiring", () => { }); it("has no cron schedule — it is send-only, driven by ingest", () => { - expect(statusRegistrar).not.toMatch(/\[DATA_ARRIVAL_QUEUE,\s*\w+_CRON\]/); + // Tolerates the optional third schedule element, so a cron sneaking in + // with send options still trips this. + expect(statusRegistrar).not.toMatch( + /\[\s*DATA_ARRIVAL_QUEUE,\s*\w+_CRON\s*[,\]]/, + ); }); it("registers the retention job that prunes the markers", () => { @@ -71,10 +75,10 @@ describe("data-arrival queue wiring", () => { expect(allQueues).not.toBeNull(); expect(allQueues![1]).toMatch(/\bARRIVAL_REACTION_CLEANUP_QUEUE\b/); expect(maintenanceSource).toMatch( - /\[ARRIVAL_REACTION_CLEANUP_QUEUE,\s*ARRIVAL_REACTION_CLEANUP_CRON\]/, + /\[\s*ARRIVAL_REACTION_CLEANUP_QUEUE,\s*ARRIVAL_REACTION_CLEANUP_CRON,\s*cronIsTheRetry,?\s*\]/, ); expect(maintenanceSource).toMatch( - /boss\.work[\s\S]{0,200}ARRIVAL_REACTION_CLEANUP_QUEUE[\s\S]{0,200}handleArrivalReactionCleanup/, + /createAndWork[\s\S]{0,200}ARRIVAL_REACTION_CLEANUP_QUEUE[\s\S]{0,200}handleArrivalReactionCleanup/, ); }); diff --git a/src/lib/jobs/__tests__/drain-cumulative-queue.test.ts b/src/lib/jobs/__tests__/drain-cumulative-queue.test.ts index 4bfc499cb..b904a30dd 100644 --- a/src/lib/jobs/__tests__/drain-cumulative-queue.test.ts +++ b/src/lib/jobs/__tests__/drain-cumulative-queue.test.ts @@ -50,15 +50,15 @@ describe("reminder-worker — drainPerSampleCumulative nightly schedule", () => expect(source).toMatch(/\bDRAIN_CUMULATIVE_CUTOFF_HOURS\b/); }); - it("registers a boss.work handler against the drain queue", () => { + it("registers a createAndWork handler against the drain queue", () => { expect(source).toMatch( - /boss\.work[\s\S]{0,200}DRAIN_CUMULATIVE_QUEUE[\s\S]{0,400}drainPerSampleCumulative/, + /createAndWork[\s\S]{0,200}DRAIN_CUMULATIVE_QUEUE[\s\S]{0,900}drainPerSampleCumulative/, ); }); it("schedules the drain cron via boss.schedule (allQueues + schedules)", () => { expect(source).toMatch( - /\[DRAIN_CUMULATIVE_QUEUE,\s*DRAIN_CUMULATIVE_CRON\]/, + /\[DRAIN_CUMULATIVE_QUEUE,\s*DRAIN_CUMULATIVE_CRON,\s*cronIsTheRetry\]/, ); }); diff --git a/src/lib/jobs/__tests__/fitbit-queues.test.ts b/src/lib/jobs/__tests__/fitbit-queues.test.ts index 3e6145540..a9e9fbbc6 100644 --- a/src/lib/jobs/__tests__/fitbit-queues.test.ts +++ b/src/lib/jobs/__tests__/fitbit-queues.test.ts @@ -58,22 +58,22 @@ describe("reminder-worker — Fitbit sync queues", () => { it("schedules the hourly poll cron + the daily oauth-state sweep", () => { expect(source).toMatch(/\[FITBIT_SYNC_QUEUE,\s*FITBIT_SYNC_CRON\]/); expect(source).toMatch( - /\[FITBIT_OAUTH_STATE_CLEANUP_QUEUE,\s*FITBIT_OAUTH_STATE_CLEANUP_CRON\]/, + /\[\s*FITBIT_OAUTH_STATE_CLEANUP_QUEUE,\s*FITBIT_OAUTH_STATE_CLEANUP_CRON,\s*cronIsTheRetry,?\s*\]/, ); }); - it("registers a boss.work handler for every Fitbit queue", () => { + it("registers a createAndWork handler for every Fitbit queue", () => { expect(source).toMatch( - /boss\.work[\s\S]{0,160}FITBIT_SYNC_QUEUE[\s\S]{0,160}handleFitbitSync/, + /createAndWork[\s\S]{0,160}FITBIT_SYNC_QUEUE[\s\S]{0,160}handleFitbitSync/, ); expect(source).toMatch( - /boss\.work[\s\S]{0,200}FITBIT_BACKFILL_QUEUE[\s\S]{0,260}enqueueIntegrationBackfillAdmission/, + /createAndWork[\s\S]{0,200}FITBIT_BACKFILL_QUEUE[\s\S]{0,260}enqueueIntegrationBackfillAdmission/, ); expect(source).toMatch( - /boss\.work[\s\S]{0,200}FITBIT_SLEEP_REPAIR_QUEUE[\s\S]{0,260}enqueueIntegrationBackfillAdmission/, + /createAndWork[\s\S]{0,200}FITBIT_SLEEP_REPAIR_QUEUE[\s\S]{0,260}enqueueIntegrationBackfillAdmission/, ); expect(source).toMatch( - /boss\.work[\s\S]{0,160}FITBIT_OAUTH_STATE_CLEANUP_QUEUE[\s\S]{0,160}handleFitbitOAuthStateCleanup/, + /createAndWork[\s\S]{0,160}FITBIT_OAUTH_STATE_CLEANUP_QUEUE[\s\S]{0,160}handleFitbitOAuthStateCleanup/, ); }); diff --git a/src/lib/jobs/__tests__/google-health-queues.test.ts b/src/lib/jobs/__tests__/google-health-queues.test.ts index 179091be9..783593da4 100644 --- a/src/lib/jobs/__tests__/google-health-queues.test.ts +++ b/src/lib/jobs/__tests__/google-health-queues.test.ts @@ -61,19 +61,19 @@ describe("reminder-worker — Google Health sync queues", () => { /\[GOOGLE_HEALTH_SYNC_QUEUE,\s*GOOGLE_HEALTH_SYNC_CRON\]/, ); expect(source).toMatch( - /\[\s*GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_QUEUE,\s*GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_CRON,?\s*\]/, + /\[\s*GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_QUEUE,\s*GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_CRON,\s*cronIsTheRetry,?\s*\]/, ); }); - it("registers a boss.work handler for every Google Health queue", () => { + it("registers a createAndWork handler for every Google Health queue", () => { expect(source).toMatch( - /boss\.work[\s\S]{0,200}GOOGLE_HEALTH_SYNC_QUEUE[\s\S]{0,200}handleGoogleHealthSync/, + /createAndWork[\s\S]{0,200}GOOGLE_HEALTH_SYNC_QUEUE[\s\S]{0,200}handleGoogleHealthSync/, ); expect(source).toMatch( - /boss\.work[\s\S]{0,320}GOOGLE_HEALTH_BACKFILL_QUEUE[\s\S]{0,320}enqueueIntegrationBackfillAdmission/, + /createAndWork[\s\S]{0,320}GOOGLE_HEALTH_BACKFILL_QUEUE[\s\S]{0,320}enqueueIntegrationBackfillAdmission/, ); expect(source).toMatch( - /boss\.work[\s\S]{0,200}GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_QUEUE[\s\S]{0,200}handleGoogleHealthOAuthStateCleanup/, + /createAndWork[\s\S]{0,200}GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_QUEUE[\s\S]{0,200}handleGoogleHealthOAuthStateCleanup/, ); }); diff --git a/src/lib/jobs/__tests__/insight-pregenerate.test.ts b/src/lib/jobs/__tests__/insight-pregenerate.test.ts index e8bb06131..38c92003e 100644 --- a/src/lib/jobs/__tests__/insight-pregenerate.test.ts +++ b/src/lib/jobs/__tests__/insight-pregenerate.test.ts @@ -1181,9 +1181,9 @@ describe("queue registration", () => { ); }); - it("registers a boss.work handler for the queue", () => { + it("registers a createAndWork handler for the queue", () => { expect(workerSrc).toMatch( - /boss\.work[\s\S]{0,80}INSIGHT_PREGENERATE_QUEUE/, + /createAndWork[\s\S]{0,80}INSIGHT_PREGENERATE_QUEUE/, ); }); diff --git a/src/lib/jobs/__tests__/insight-status-generate.test.ts b/src/lib/jobs/__tests__/insight-status-generate.test.ts index fe4a9b116..06ab5ed4e 100644 --- a/src/lib/jobs/__tests__/insight-status-generate.test.ts +++ b/src/lib/jobs/__tests__/insight-status-generate.test.ts @@ -6,7 +6,7 @@ * - an unknown metric is annotated and skipped, not thrown; * - the enqueue helper no-ops without a global boss instance and de-dupes * with a singletonKey when one is present; - * - the queue is registered in `allQueues` AND wired to a `boss.work` + * - the queue is registered in `allQueues` AND wired to a `createAndWork` * handler in reminder-worker.ts (the v1.4.37 W10 unregistered-queue * catch — a queue built but never registered silently never drains). */ @@ -171,9 +171,9 @@ describe("queue registration", () => { expect(match![1]).toMatch(/\bINSIGHT_STATUS_GENERATE_QUEUE\b/); }); - it("registers a boss.work handler for the queue", () => { + it("registers a createAndWork handler for the queue", () => { expect(workerSrc).toMatch( - /boss\.work[\s\S]{0,120}INSIGHT_STATUS_GENERATE_QUEUE/, + /createAndWork[\s\S]{0,120}INSIGHT_STATUS_GENERATE_QUEUE/, ); }); diff --git a/src/lib/jobs/__tests__/intake-slot-dedup-queue.test.ts b/src/lib/jobs/__tests__/intake-slot-dedup-queue.test.ts index 16e1ae0ad..a99672474 100644 --- a/src/lib/jobs/__tests__/intake-slot-dedup-queue.test.ts +++ b/src/lib/jobs/__tests__/intake-slot-dedup-queue.test.ts @@ -3,7 +3,7 @@ * * Same source-text-grep approach as the drain + step-consolidation + * mean-consolidation regression guards: assert the queue is registered - * in `allQueues`, a `boss.work` handler is wired against it, and the + * in `allQueues`, a `createAndWork` handler is wired against it, and the * boot-discovery enqueue helper is called — without booting pg-boss + * Prisma. An unregistered queue silently never drains (the past incident * this guard exists to prevent). @@ -42,12 +42,12 @@ describe("reminder-worker — intake-slot-dedup wiring", () => { expect(allQueuesMatch![1]).toMatch(/\bINTAKE_SLOT_DEDUP_QUEUE\b/); }); - it("registers a boss.work handler against the intake-slot-dedup queue", () => { + it("registers a createAndWork handler against the intake-slot-dedup queue", () => { // v1.15.19 — span widened from {0,400}: the handler gained the daily // cron-tick dispatch branch (no `userId` → discovery fan-out) between // the queue name and the per-user dedup call. expect(source).toMatch( - /boss\.work[\s\S]{0,200}INTAKE_SLOT_DEDUP_QUEUE[\s\S]{0,1200}dedupeUserIntakeSlots/, + /createAndWork[\s\S]{0,200}INTAKE_SLOT_DEDUP_QUEUE[\s\S]{0,1400}dedupeUserIntakeSlots/, ); }); diff --git a/src/lib/jobs/__tests__/mcp-token-cleanup-queue.test.ts b/src/lib/jobs/__tests__/mcp-token-cleanup-queue.test.ts index 6215ec22f..7f42958a7 100644 --- a/src/lib/jobs/__tests__/mcp-token-cleanup-queue.test.ts +++ b/src/lib/jobs/__tests__/mcp-token-cleanup-queue.test.ts @@ -2,7 +2,7 @@ * MCP Phase 3 (M2) — mcp-token-cleanup queue registration guard. * * Source-text-grep guard like the other queue-wiring checks: assert the queue - * is in `allQueues`, scheduled, and wired to a `boss.work` handler. An + * is in `allQueues`, scheduled, and wired to a `createAndWork` handler. An * unregistered queue silently never drains (the recurring v1.4.37 dead-queue * bug), which would leave expired MCP connector access tokens growing unbounded. */ @@ -40,13 +40,13 @@ describe("reminder-worker — mcp-token-cleanup wiring", () => { it("schedules the mcp-token-cleanup cron", () => { expect(workerSource).toMatch( - /\[MCP_TOKEN_CLEANUP_QUEUE,\s*MCP_TOKEN_CLEANUP_CRON\]/, + /\[MCP_TOKEN_CLEANUP_QUEUE,\s*MCP_TOKEN_CLEANUP_CRON,\s*cronIsTheRetry\]/, ); }); - it("registers a boss.work handler for the queue", () => { + it("registers a createAndWork handler for the queue", () => { expect(workerSource).toMatch( - /boss\.work[\s\S]{0,200}MCP_TOKEN_CLEANUP_QUEUE[\s\S]{0,200}handleMcpTokenCleanup/, + /createAndWork[\s\S]{0,200}MCP_TOKEN_CLEANUP_QUEUE[\s\S]{0,200}handleMcpTokenCleanup/, ); }); diff --git a/src/lib/jobs/__tests__/mean-consolidation-queue.test.ts b/src/lib/jobs/__tests__/mean-consolidation-queue.test.ts index 3b5956ec6..4dbfd6d11 100644 --- a/src/lib/jobs/__tests__/mean-consolidation-queue.test.ts +++ b/src/lib/jobs/__tests__/mean-consolidation-queue.test.ts @@ -3,7 +3,7 @@ * * Same source-text-grep approach as the drain + step-consolidation * regression guards: assert the queue is registered in `allQueues`, a - * `boss.work` handler is wired against it, and the boot-discovery + * `createAndWork` handler is wired against it, and the boot-discovery * enqueue helper is called — without booting pg-boss + Prisma. An * unregistered queue silently never drains. */ @@ -34,9 +34,9 @@ describe("reminder-worker — mean-consolidation wiring", () => { expect(allQueuesMatch![1]).toMatch(/\bMEAN_CONSOLIDATION_QUEUE\b/); }); - it("registers a boss.work handler against the mean-consolidation queue", () => { + it("registers a createAndWork handler against the mean-consolidation queue", () => { expect(source).toMatch( - /boss\.work[\s\S]{0,200}MEAN_CONSOLIDATION_QUEUE[\s\S]{0,400}runMeanConsolidationForUser/, + /createAndWork[\s\S]{0,200}MEAN_CONSOLIDATION_QUEUE[\s\S]{0,400}runMeanConsolidationForUser/, ); }); diff --git a/src/lib/jobs/__tests__/measurement-reminder-queue.test.ts b/src/lib/jobs/__tests__/measurement-reminder-queue.test.ts index 9c36703d4..564b9cb95 100644 --- a/src/lib/jobs/__tests__/measurement-reminder-queue.test.ts +++ b/src/lib/jobs/__tests__/measurement-reminder-queue.test.ts @@ -5,7 +5,7 @@ * guards: assert the Vorsorge-reminder queue is declared, listed in * `allQueues` (the v1.4.37 dead-queue lesson — an unregistered queue is * never provisioned and the schedule silently no-ops), scheduled on its - * cron, and wired to a `boss.work` handler that delegates to + * cron, and wired to a `createAndWork` handler that delegates to * `runMeasurementReminderTick`. No pg-boss / Prisma boot. */ import { readFileSync } from "node:fs"; @@ -56,9 +56,9 @@ describe("reminder-worker — measurement-reminder wiring", () => { ); }); - it("registers a boss.work handler that runs the tick", () => { + it("registers a createAndWork handler that runs the tick", () => { expect(source).toMatch( - /boss\.work[\s\S]{0,200}MEASUREMENT_REMINDER_QUEUE[\s\S]{0,200}handleMeasurementReminderCheck/, + /createAndWork[\s\S]{0,200}MEASUREMENT_REMINDER_QUEUE[\s\S]{0,200}handleMeasurementReminderCheck/, ); expect(source).toMatch( /handleMeasurementReminderCheck[\s\S]{0,400}runMeasurementReminderTick/, @@ -80,12 +80,12 @@ describe("reminder-worker — eventful reminder-satisfy wiring (v1.18.1)", () => expect(allQueuesMatch![1]).toMatch(/\bREMINDER_SATISFY_QUEUE\b/); }); - it("binds a boss.work handler for the satisfy queue", () => { + it("binds a createAndWork handler for the satisfy queue", () => { expect(source).toMatch( - /boss\.work[\s\S]{0,200}REMINDER_SATISFY_QUEUE[\s\S]{0,200}handleReminderSatisfy/, + /createAndWork[\s\S]{0,200}REMINDER_SATISFY_QUEUE[\s\S]{0,200}handleReminderSatisfy/, ); expect(source).toMatch( - /handleReminderSatisfy[\s\S]{0,600}runReminderSatisfyForUser/, + /handleReminderSatisfy[\s\S]{0,800}runReminderSatisfyForUser/, ); }); }); diff --git a/src/lib/jobs/__tests__/measurement-tombstone-cleanup.test.ts b/src/lib/jobs/__tests__/measurement-tombstone-cleanup.test.ts index ed96efe98..fa68a9100 100644 --- a/src/lib/jobs/__tests__/measurement-tombstone-cleanup.test.ts +++ b/src/lib/jobs/__tests__/measurement-tombstone-cleanup.test.ts @@ -7,7 +7,7 @@ * recently-tombstoned rows untouched. * 2. Source-grep wiring (same approach as the drain-cumulative guard): * the queue is registered in `allQueues`, scheduled, and bound to a - * `boss.work` handler — a missing `allQueues` entry silently no-ops + * `createAndWork` handler — a missing `allQueues` entry silently no-ops * the schedule under pg-boss v12. */ import { readFileSync } from "node:fs"; @@ -114,9 +114,9 @@ describe("reminder-worker — measurement-tombstone-cleanup schedule", () => { ); }); - it("binds a boss.work handler to the queue", () => { + it("binds a createAndWork handler to the queue", () => { expect(source).toMatch( - /boss\.work[\s\S]{0,200}MEASUREMENT_TOMBSTONE_CLEANUP_QUEUE[\s\S]{0,200}handleMeasurementTombstoneCleanup/, + /createAndWork[\s\S]{0,200}MEASUREMENT_TOMBSTONE_CLEANUP_QUEUE[\s\S]{0,200}handleMeasurementTombstoneCleanup/, ); }); }); diff --git a/src/lib/jobs/__tests__/nightscout-queues.test.ts b/src/lib/jobs/__tests__/nightscout-queues.test.ts index 5873b8382..6c994d55a 100644 --- a/src/lib/jobs/__tests__/nightscout-queues.test.ts +++ b/src/lib/jobs/__tests__/nightscout-queues.test.ts @@ -50,9 +50,9 @@ describe("reminder-worker — Nightscout sync queue", () => { expect(source).toMatch(/\[NIGHTSCOUT_SYNC_QUEUE,\s*NIGHTSCOUT_SYNC_CRON\]/); }); - it("registers a boss.work handler for the queue", () => { + it("registers a createAndWork handler for the queue", () => { expect(source).toMatch( - /boss\.work[\s\S]{0,160}NIGHTSCOUT_SYNC_QUEUE[\s\S]{0,160}handleNightscoutSync/, + /createAndWork[\s\S]{0,160}NIGHTSCOUT_SYNC_QUEUE[\s\S]{0,160}handleNightscoutSync/, ); }); }); @@ -85,12 +85,12 @@ describe("reminder-worker — Polar / Oura sync queues", () => { expect(source).toMatch(/\[OURA_SYNC_QUEUE,\s*OURA_SYNC_CRON\]/); }); - it("registers a boss.work handler for each queue", () => { + it("registers a createAndWork handler for each queue", () => { expect(source).toMatch( - /boss\.work[\s\S]{0,160}POLAR_SYNC_QUEUE[\s\S]{0,160}handlePolarSync/, + /createAndWork[\s\S]{0,160}POLAR_SYNC_QUEUE[\s\S]{0,160}handlePolarSync/, ); expect(source).toMatch( - /boss\.work[\s\S]{0,160}OURA_SYNC_QUEUE[\s\S]{0,160}handleOuraSync/, + /createAndWork[\s\S]{0,160}OURA_SYNC_QUEUE[\s\S]{0,160}handleOuraSync/, ); }); }); diff --git a/src/lib/jobs/__tests__/period-narrative-warm.test.ts b/src/lib/jobs/__tests__/period-narrative-warm.test.ts index 45838182e..5c9e6ea83 100644 --- a/src/lib/jobs/__tests__/period-narrative-warm.test.ts +++ b/src/lib/jobs/__tests__/period-narrative-warm.test.ts @@ -196,8 +196,10 @@ describe("queue registration", () => { ); }); - it("registers a boss.work handler for the queue", () => { - expect(workerSrc).toMatch(/boss\.work[\s\S]{0,120}PERIOD_NARRATIVE_QUEUE/); + it("registers a createAndWork handler for the queue", () => { + expect(workerSrc).toMatch( + /createAndWork[\s\S]{0,120}PERIOD_NARRATIVE_QUEUE/, + ); }); it("exposes a sane queue name + nightly cron", () => { diff --git a/src/lib/jobs/__tests__/recovery-score-queue.test.ts b/src/lib/jobs/__tests__/recovery-score-queue.test.ts index 5e7a15866..65628b9e9 100644 --- a/src/lib/jobs/__tests__/recovery-score-queue.test.ts +++ b/src/lib/jobs/__tests__/recovery-score-queue.test.ts @@ -3,7 +3,7 @@ * * Same source-text-grep approach as the other queue-wiring guards: assert * the queue is imported, registered in `allQueues`, scheduled, and wired to - * a `boss.work` handler — without booting pg-boss + Prisma. An unregistered + * a `createAndWork` handler — without booting pg-boss + Prisma. An unregistered * queue silently never drains (the recurring past bug this guards against). * * Also pins the iOS-write rejection of the server-owned COMPUTED source on @@ -57,9 +57,9 @@ describe("reminder-worker — recovery-score wiring", () => { ); }); - it("registers a boss.work handler that runs the recovery-score pass", () => { + it("registers a createAndWork handler that runs the recovery-score pass", () => { expect(workerSource).toMatch( - /boss\.work[\s\S]{0,200}RECOVERY_SCORE_QUEUE[\s\S]{0,400}runRecoveryScore/, + /createAndWork[\s\S]{0,200}RECOVERY_SCORE_QUEUE[\s\S]{0,400}runRecoveryScore/, ); }); }); diff --git a/src/lib/jobs/__tests__/sleep-timeline-queues.test.ts b/src/lib/jobs/__tests__/sleep-timeline-queues.test.ts index c6375c871..cc803ecc5 100644 --- a/src/lib/jobs/__tests__/sleep-timeline-queues.test.ts +++ b/src/lib/jobs/__tests__/sleep-timeline-queues.test.ts @@ -3,7 +3,7 @@ * * The reminder-worker imports heavy infrastructure, so we read the source as * text and assert the queue constant is declared, registered in `allQueues`, - * wired to a `boss.work` handler, and enqueued at boot — the fast guard that + * wired to a `createAndWork` handler, and enqueued at boot — the fast guard that * catches the v1.4.37 dead-queue class (a queue declared but never registered, * which silently never drains). */ @@ -30,9 +30,9 @@ describe("reminder-worker — sleep-timeline backfill queue", () => { expect(block![1]!).toContain("SLEEP_TIMELINE_BACKFILL_QUEUE"); }); - it("wires a boss.work handler through the shared admission queue", () => { + it("wires a createAndWork handler through the shared admission queue", () => { expect(source).toMatch( - /boss\.work[\s\S]{0,260}SLEEP_TIMELINE_BACKFILL_QUEUE[\s\S]{0,300}enqueueIntegrationBackfillAdmission/, + /createAndWork[\s\S]{0,260}SLEEP_TIMELINE_BACKFILL_QUEUE[\s\S]{0,300}enqueueIntegrationBackfillAdmission/, ); }); diff --git a/src/lib/jobs/__tests__/status-cron-candidates.test.ts b/src/lib/jobs/__tests__/status-cron-candidates.test.ts index 15c13baa8..4f2690a1f 100644 --- a/src/lib/jobs/__tests__/status-cron-candidates.test.ts +++ b/src/lib/jobs/__tests__/status-cron-candidates.test.ts @@ -196,8 +196,8 @@ describe("mood-status queue registration (dead-queue guard)", () => { expect(workerSrc).toMatch(/MOOD_STATUS_CRON\s*=\s*"30 2 \* \* \*"/); }); - it("registers a boss.work handler for the queue", () => { - expect(workerSrc).toMatch(/boss\.work[\s\S]{0,120}MOOD_STATUS_QUEUE/); + it("registers a createAndWork handler for the queue", () => { + expect(workerSrc).toMatch(/createAndWork[\s\S]{0,120}MOOD_STATUS_QUEUE/); }); it("drives all seven status crons through the shared discovery", () => { diff --git a/src/lib/jobs/__tests__/step-consolidation-repair-queue.test.ts b/src/lib/jobs/__tests__/step-consolidation-repair-queue.test.ts index 8e0eeab75..e34c295cd 100644 --- a/src/lib/jobs/__tests__/step-consolidation-repair-queue.test.ts +++ b/src/lib/jobs/__tests__/step-consolidation-repair-queue.test.ts @@ -4,7 +4,7 @@ * * Same source-text-grep approach as the drain + step-consolidation + * mean-consolidation guards: assert the repair queue is registered in - * `allQueues`, a `boss.work` handler is wired against it, and the + * `allQueues`, a `createAndWork` handler is wired against it, and the * boot-discovery enqueue helper fires — without booting pg-boss + Prisma. * An unregistered queue silently never drains (the dead-queue class). */ @@ -36,9 +36,9 @@ describe("reminder-worker — step-consolidation-repair wiring", () => { expect(allQueuesMatch![1]).toMatch(/\bSTEP_CONSOLIDATION_REPAIR_QUEUE\b/); }); - it("registers a boss.work handler against the repair queue", () => { + it("registers a createAndWork handler against the repair queue", () => { expect(source).toMatch( - /boss\.work[\s\S]{0,200}STEP_CONSOLIDATION_REPAIR_QUEUE[\s\S]{0,400}runStepConsolidationRepairForUser/, + /createAndWork[\s\S]{0,200}STEP_CONSOLIDATION_REPAIR_QUEUE[\s\S]{0,400}runStepConsolidationRepairForUser/, ); }); diff --git a/src/lib/jobs/__tests__/stress-strain-retention-queue.test.ts b/src/lib/jobs/__tests__/stress-strain-retention-queue.test.ts index 839c2b575..969d80fba 100644 --- a/src/lib/jobs/__tests__/stress-strain-retention-queue.test.ts +++ b/src/lib/jobs/__tests__/stress-strain-retention-queue.test.ts @@ -5,7 +5,7 @@ * * Same source-text-grep approach as the WX-C recovery-score guard: assert * each queue is imported, registered in `allQueues`, scheduled (the two - * cron-driven score jobs), and wired to a `boss.work` handler — without + * cron-driven score jobs), and wired to a `createAndWork` handler — without * booting pg-boss + Prisma. An unregistered queue silently never drains * (the recurring past bug this guards against). */ @@ -67,9 +67,9 @@ describe("reminder-worker — stress-score wiring", () => { expect(source).toMatch(/\[STRESS_SCORE_QUEUE,\s*STRESS_SCORE_CRON\]/); }); - it("registers a boss.work handler that runs the stress-score pass", () => { + it("registers a createAndWork handler that runs the stress-score pass", () => { expect(source).toMatch( - /boss\.work[\s\S]{0,200}STRESS_SCORE_QUEUE[\s\S]{0,400}runStressScore/, + /createAndWork[\s\S]{0,200}STRESS_SCORE_QUEUE[\s\S]{0,400}runStressScore/, ); }); }); @@ -90,9 +90,9 @@ describe("reminder-worker — strain-score wiring", () => { expect(source).toMatch(/\[STRAIN_SCORE_QUEUE,\s*STRAIN_SCORE_CRON\]/); }); - it("registers a boss.work handler that runs the strain-score pass", () => { + it("registers a createAndWork handler that runs the strain-score pass", () => { expect(source).toMatch( - /boss\.work[\s\S]{0,200}STRAIN_SCORE_QUEUE[\s\S]{0,400}runStrainScore/, + /createAndWork[\s\S]{0,200}STRAIN_SCORE_QUEUE[\s\S]{0,400}runStrainScore/, ); }); }); @@ -111,9 +111,9 @@ describe("reminder-worker — dense intra-day retention wiring", () => { expect(allQueuesBlock()).toMatch(/\bDENSE_INTRADAY_RETENTION_QUEUE\b/); }); - it("registers a boss.work handler against the dense-intraday-retention queue", () => { + it("registers a createAndWork handler against the dense-intraday-retention queue", () => { expect(source).toMatch( - /boss\.work[\s\S]{0,250}DENSE_INTRADAY_RETENTION_QUEUE[\s\S]{0,500}runDenseIntradayRetentionForUser/, + /createAndWork[\s\S]{0,250}DENSE_INTRADAY_RETENTION_QUEUE[\s\S]{0,500}runDenseIntradayRetentionForUser/, ); }); @@ -163,9 +163,9 @@ describe("reminder-worker — dense hourly-rebuild wiring", () => { expect(allQueuesBlock()).toMatch(/\bDENSE_INTRADAY_HOURLY_REBUILD_QUEUE\b/); }); - it("registers a boss.work handler against the hourly-rebuild queue", () => { + it("registers a createAndWork handler against the hourly-rebuild queue", () => { expect(source).toMatch( - /boss\.work[\s\S]{0,250}DENSE_INTRADAY_HOURLY_REBUILD_QUEUE[\s\S]{0,500}runDenseIntradayHourlyRebuildForUser/, + /createAndWork[\s\S]{0,250}DENSE_INTRADAY_HOURLY_REBUILD_QUEUE[\s\S]{0,500}runDenseIntradayHourlyRebuildForUser/, ); }); diff --git a/src/lib/jobs/__tests__/tls-pin-monitor-queue.test.ts b/src/lib/jobs/__tests__/tls-pin-monitor-queue.test.ts index 68764d932..9fe67d0b0 100644 --- a/src/lib/jobs/__tests__/tls-pin-monitor-queue.test.ts +++ b/src/lib/jobs/__tests__/tls-pin-monitor-queue.test.ts @@ -3,7 +3,7 @@ * * Same source-text-grep approach as the other queue-wiring guards: assert * the queue is imported, registered in `allQueues`, scheduled, and wired to - * a `boss.work` handler — without booting pg-boss + Prisma. An unregistered + * a `createAndWork` handler — without booting pg-boss + Prisma. An unregistered * queue silently never drains (the recurring v1.4.37 dead-queue bug), which * would leave the pinned-leaf-rotation alarm dark. */ @@ -50,9 +50,9 @@ describe("reminder-worker — tls-pin-monitor wiring", () => { ); }); - it("registers a boss.work handler for the queue", () => { + it("registers a createAndWork handler for the queue", () => { expect(workerSource).toMatch( - /boss\.work[\s\S]{0,200}TLS_PIN_MONITOR_QUEUE[\s\S]{0,200}handleTlsPinMonitor/, + /createAndWork[\s\S]{0,200}TLS_PIN_MONITOR_QUEUE[\s\S]{0,200}handleTlsPinMonitor/, ); }); diff --git a/src/lib/jobs/__tests__/whoop-queues.test.ts b/src/lib/jobs/__tests__/whoop-queues.test.ts index 7d2c514c1..a1ad468b4 100644 --- a/src/lib/jobs/__tests__/whoop-queues.test.ts +++ b/src/lib/jobs/__tests__/whoop-queues.test.ts @@ -77,25 +77,25 @@ describe("reminder-worker — WHOOP sync queues", () => { /\[WHOOP_CYCLE_SYNC_QUEUE,\s*WHOOP_CYCLE_SYNC_CRON\]/, ); expect(source).toMatch( - /\[WHOOP_OAUTH_STATE_CLEANUP_QUEUE,\s*WHOOP_OAUTH_STATE_CLEANUP_CRON\]/, + /\[\s*WHOOP_OAUTH_STATE_CLEANUP_QUEUE,\s*WHOOP_OAUTH_STATE_CLEANUP_CRON,\s*cronIsTheRetry,?\s*\]/, ); }); - it("registers a boss.work handler for every WHOOP sync queue", () => { + it("registers a createAndWork handler for every WHOOP sync queue", () => { expect(source).toMatch( - /boss\.work[\s\S]{0,160}WHOOP_RECOVERY_SYNC_QUEUE[\s\S]{0,160}handleWhoopRecoverySync/, + /createAndWork[\s\S]{0,160}WHOOP_RECOVERY_SYNC_QUEUE[\s\S]{0,160}handleWhoopRecoverySync/, ); expect(source).toMatch( - /boss\.work[\s\S]{0,160}WHOOP_SLEEP_SYNC_QUEUE[\s\S]{0,160}handleWhoopSleepSync/, + /createAndWork[\s\S]{0,160}WHOOP_SLEEP_SYNC_QUEUE[\s\S]{0,160}handleWhoopSleepSync/, ); expect(source).toMatch( - /boss\.work[\s\S]{0,160}WHOOP_WORKOUT_SYNC_QUEUE[\s\S]{0,160}handleWhoopWorkoutSync/, + /createAndWork[\s\S]{0,160}WHOOP_WORKOUT_SYNC_QUEUE[\s\S]{0,160}handleWhoopWorkoutSync/, ); expect(source).toMatch( - /boss\.work[\s\S]{0,160}WHOOP_CYCLE_SYNC_QUEUE[\s\S]{0,160}handleWhoopCycleSync/, + /createAndWork[\s\S]{0,160}WHOOP_CYCLE_SYNC_QUEUE[\s\S]{0,160}handleWhoopCycleSync/, ); expect(source).toMatch( - /boss\.work[\s\S]{0,200}WHOOP_BACKFILL_QUEUE[\s\S]{0,260}enqueueIntegrationBackfillAdmission/, + /createAndWork[\s\S]{0,200}WHOOP_BACKFILL_QUEUE[\s\S]{0,260}enqueueIntegrationBackfillAdmission/, ); }); diff --git a/src/lib/jobs/__tests__/whoop-resource-sync.test.ts b/src/lib/jobs/__tests__/whoop-resource-sync.test.ts index 6359d2ecb..cbe181b6d 100644 --- a/src/lib/jobs/__tests__/whoop-resource-sync.test.ts +++ b/src/lib/jobs/__tests__/whoop-resource-sync.test.ts @@ -128,7 +128,18 @@ describe("runWhoopResourceSync dispatch", () => { findManyMock.mockResolvedValue([{ userId: "a" }, { userId: "b" }]); syncUserWorkout.mockRejectedValueOnce(new Error("boom for a")); - await expect(handleWhoopWorkoutSync([job({})])).resolves.toBeUndefined(); + // The pass is what the outcome judges: the sweep ran, so the job is + // done. The revoked-grant user rides out as `users_failed` rather than + // failing the queue and retrying the whole cohort over one account. + await expect(handleWhoopWorkoutSync([job({})])).resolves.toEqual({ + ok: true, + did: { + total: 2, + users_synced: 1, + users_failed: 1, + measurements_imported: 0, + }, + }); // Both users were attempted despite the first throwing. expect(syncUserWorkout).toHaveBeenCalledTimes(2); }); diff --git a/src/lib/jobs/__tests__/withings-ecg-worker.test.ts b/src/lib/jobs/__tests__/withings-ecg-worker.test.ts index cb11bde9e..e40bf868c 100644 --- a/src/lib/jobs/__tests__/withings-ecg-worker.test.ts +++ b/src/lib/jobs/__tests__/withings-ecg-worker.test.ts @@ -93,7 +93,15 @@ describe("handleWithingsEcgSync", () => { enddate: 1715000060, }); - await expect(handleWithingsEcgSync([queued])).resolves.toBeUndefined(); + await expect(handleWithingsEcgSync([queued])).resolves.toEqual({ + ok: true, + did: { + total: 1, + users_synced: 1, + users_failed: 0, + recordings_imported: 1, + }, + }); expect(ecgFindMany).not.toHaveBeenCalled(); expect(syncUserEcg).toHaveBeenCalledTimes(1); @@ -120,14 +128,24 @@ describe("handleWithingsEcgSync", () => { it("one user's failure never starves the rest of the cohort", async () => { ecgFindMany.mockResolvedValue([{ userId: "a" }, { userId: "b" }]); + syncUserEcg.mockResolvedValue(0); syncUserEcg.mockRejectedValueOnce(new Error("boom for a")); // Per-user catch-and-warn (mirrors the activity / sleep handlers): the - // handler resolves, both users attempted, the leaf records its own ledger - // failure so honesty is preserved without rejecting the whole batch. + // pass ran, so the job is done and the failing user rides out as a + // `users_failed` count. The leaf records its own ledger failure, so + // honesty is preserved without rejecting the whole batch. await expect( handleWithingsEcgSync([job({} as WithingsEcgSyncPayload)]), - ).resolves.toBeUndefined(); + ).resolves.toEqual({ + ok: true, + did: { + total: 2, + users_synced: 1, + users_failed: 1, + recordings_imported: 0, + }, + }); expect(syncUserEcg).toHaveBeenCalledTimes(2); }); }); @@ -143,7 +161,7 @@ describe("withings ECG queue registration", () => { /\[WITHINGS_ECG_SYNC_QUEUE\]:\s*\{[\s\S]*?policy:\s*"short"/, ); expect(registrarSource).toMatch( - /boss\.work\([\s\S]*?WITHINGS_ECG_SYNC_QUEUE[\s\S]*?handleWithingsEcgSync/, + /createAndWork\(\s*boss,\s*WITHINGS_ECG_SYNC_QUEUE[\s\S]{0,120}handleWithingsEcgSync/, ); }); }); diff --git a/src/lib/jobs/__tests__/withings-queues.test.ts b/src/lib/jobs/__tests__/withings-queues.test.ts index 5dc7dac08..9861c41a8 100644 --- a/src/lib/jobs/__tests__/withings-queues.test.ts +++ b/src/lib/jobs/__tests__/withings-queues.test.ts @@ -50,14 +50,14 @@ describe("reminder-worker — Withings activity + sleep v2 queues", () => { it("registers the activity-sync handler against pg-boss", () => { expect(source).toMatch(/handleWithingsActivitySync\b/); expect(source).toMatch( - /boss\.work[\s\S]{0,200}WITHINGS_ACTIVITY_QUEUE[\s\S]{0,200}handleWithingsActivitySync/, + /createAndWork[\s\S]{0,200}WITHINGS_ACTIVITY_QUEUE[\s\S]{0,200}handleWithingsActivitySync/, ); }); it("registers the sleep-sync handler against pg-boss", () => { expect(source).toMatch(/handleWithingsSleepSync\b/); expect(source).toMatch( - /boss\.work[\s\S]{0,200}WITHINGS_SLEEP_QUEUE[\s\S]{0,200}handleWithingsSleepSync/, + /createAndWork[\s\S]{0,200}WITHINGS_SLEEP_QUEUE[\s\S]{0,200}handleWithingsSleepSync/, ); }); @@ -78,7 +78,7 @@ describe("reminder-worker — Withings activity + sleep v2 queues", () => { }); it("schedules the ECG catch-net cron at :41 so watch-only accounts pull strips", () => { - // The queue is already in allQueues + boss.work-bound; W-1 adds the missing + // The queue is already in allQueues + createAndWork-bound; W-1 adds the missing // schedule entry so a payload-less cron tick walks the connection cohort. expect(source).toMatch( /WITHINGS_ECG_SYNC_CRON\s*=\s*["']41 \* \* \* \*["']/, diff --git a/src/lib/jobs/__tests__/withings-subscription-retry.test.ts b/src/lib/jobs/__tests__/withings-subscription-retry.test.ts index e873c9ca5..be6de5b0e 100644 --- a/src/lib/jobs/__tests__/withings-subscription-retry.test.ts +++ b/src/lib/jobs/__tests__/withings-subscription-retry.test.ts @@ -72,7 +72,16 @@ describe("handleWithingsFallbackSync subscription repair", () => { data: { triggeredAt: "2026-07-21T10:00:00.000Z" }, } as Job; - await handleWithingsFallbackSync([queued]); + await expect(handleWithingsFallbackSync([queued])).resolves.toEqual({ + ok: true, + did: { + total: 0, + users_synced: 0, + users_failed: 0, + measurements_imported: 0, + subscription_repair_failed: false, + }, + }); expect(retryDueWithingsWebhookSubscriptions).toHaveBeenCalledTimes(1); expect(syncUserMeasurements).not.toHaveBeenCalled(); @@ -89,7 +98,19 @@ describe("handleWithingsFallbackSync subscription repair", () => { data: { triggeredAt: "2026-07-21T11:00:00.000Z" }, } as Job; - await expect(handleWithingsFallbackSync([queued])).resolves.toBeUndefined(); + // The repair is an auxiliary leg, not the pass this queue is named for: + // the fallback sync still ran for everyone, so the job is done and the + // repair failure rides out as a fact on the outcome. + await expect(handleWithingsFallbackSync([queued])).resolves.toEqual({ + ok: true, + did: { + total: 2, + users_synced: 2, + users_failed: 0, + measurements_imported: 0, + subscription_repair_failed: true, + }, + }); expect(findMany).toHaveBeenCalledWith({ select: { userId: true } }); expect(syncUserMeasurements).toHaveBeenNthCalledWith( diff --git a/src/lib/jobs/__tests__/workout-insight-queue.test.ts b/src/lib/jobs/__tests__/workout-insight-queue.test.ts index 4fdc3ca15..7af32f112 100644 --- a/src/lib/jobs/__tests__/workout-insight-queue.test.ts +++ b/src/lib/jobs/__tests__/workout-insight-queue.test.ts @@ -32,9 +32,9 @@ describe("workout-insight-generate queue wiring", () => { expect(allQueues![1]).toMatch(/\bWORKOUT_INSIGHT_GENERATE_QUEUE\b/); }); - it("binds a boss.work handler that drains it", () => { + it("binds a createAndWork handler that drains it", () => { expect(statusRegistrar).toMatch( - /boss\.work[\s\S]{0,300}WORKOUT_INSIGHT_GENERATE_QUEUE[\s\S]{0,300}handleWorkoutInsightGenerate/, + /createAndWork[\s\S]{0,300}WORKOUT_INSIGHT_GENERATE_QUEUE[\s\S]{0,300}handleWorkoutInsightGenerate/, ); }); @@ -50,8 +50,10 @@ describe("workout-insight-generate queue wiring", () => { it("has no cron schedule — it is dispatched by arrival only", () => { // A cron here would be a standing invitation to regenerate, which is the // saturation surface the design refuses. + // Tolerates the optional third schedule element, so a cron sneaking in + // with send options still trips this. expect(statusRegistrar).not.toMatch( - /\[WORKOUT_INSIGHT_GENERATE_QUEUE,\s*\w+_CRON\]/, + /\[\s*WORKOUT_INSIGHT_GENERATE_QUEUE,\s*\w+_CRON\s*[,\]]/, ); }); diff --git a/src/lib/jobs/reminder/__tests__/poll-cohort.test.ts b/src/lib/jobs/reminder/__tests__/poll-cohort.test.ts index f0dc9123d..111c255ab 100644 --- a/src/lib/jobs/reminder/__tests__/poll-cohort.test.ts +++ b/src/lib/jobs/reminder/__tests__/poll-cohort.test.ts @@ -107,7 +107,17 @@ describe("makePollCohortHandler — marker-aware recordFailure boundary", () => recordFailure, }); - await expect(handler([job()])).resolves.toBeUndefined(); + // The recorder rejection is warned, not propagated: the pass ran, so the + // job is done and the failing user is a `users_failed` count. + await expect(handler([job()])).resolves.toEqual({ + ok: true, + did: { + total: 2, + users_synced: 1, + users_failed: 1, + measurements_imported: 1, + }, + }); // The next user still synced despite the recorder throwing. expect(syncUser).toHaveBeenCalledTimes(2); expect(syncUser).toHaveBeenLastCalledWith("next");