Five test surfaces updated to match the v1.4.47 W1 drop:
- `src/lib/integrations/__tests__/status.test.ts`
* `recordSyncSuccess`, `markDisconnected`, `markReconnected`,
`resumeIntegrationFromPark` upsert assertions drop the
`consecutiveFailures: 0` field.
* Alert-ladder tests now seed the existing-row mock with a bucket
payload at N-1 so the in-memory increment lands at N — the
writer no longer consults the upsert return's
`consecutiveFailures` for the threshold check.
* The "back-fills from legacy single counter" case is replaced
with a "seeds a fresh zero envelope when the row has no bucket
payload yet" case (the back-fill path is gone).
* New `down-script restores legacy consecutiveFailures via
Math.max` block locks in the migration 0076 rollback recipe at
the JS level: an in-memory `projectLegacyCounter` mirrors the
SQL `GREATEST(transient, reauth_required, persistent)`
projection and is asserted against zero, null, the 28-deep
production-shape persistent streak, and parity with the live
alert ladder's `Math.max(...Object.values(buckets))`.
* Stale `consecutiveFailures: N` properties on Prisma mock returns
are stripped (the writer no longer reads them).
- `src/lib/integrations/__tests__/admin-alert.test.ts`
* `parkIntegrationAtReauth` "creates a fresh row at counter=0"
now asserts the zero bucket envelope rather than the dropped
`consecutiveFailures: 0` literal.
* The "not.toHaveProperty consecutiveFailures" assertion gains
a sibling for `consecutiveFailuresByKind` — the bucket is the
live counter now, and `parkIntegrationAtReauth` must still leave
it untouched on a re-park.
- `src/components/settings/__tests__/integrations-section.test.tsx`
* The six fixture envelopes drop the now-removed
`consecutiveFailures: N` field (the view-model dropped the
property in this release).
- `tests/integration/integration-status.test.ts`
* Five `row.consecutiveFailures` assertions are migrated to
`row.consecutiveFailuresByKind` matchers carrying the same
counter value bucketed by failure kind.
* The parked-state fixture's `integrationStatus.create` call no
longer sets the dropped column.
Pill UI gains a fifth state — 'parked' — for the v1.4.43 W14 24h
persistent-failure park. Copy reads 'Pausiert — manuell wieder
verbinden' (DE) / 'Paused — reconnect manually' (EN); chip uses the
dracula-orange tokens (heavier than warning yellow, lighter than the
red reconnect pill).
The Withings card renders a 'Wieder verbinden' / 'Reconnect' banner
under the pill when the state is parked. Clicking POSTs to the new
/api/integrations/withings/resume endpoint (rate-limited 5/min per
user, same envelope as the test-connection endpoint). The endpoint
calls resumeIntegrationFromPark which flips state back to connected
and zeroes every per-kind bucket; the per-card status query
invalidates so the pill flips back without a page refresh.
Locale parity across all six locales (en/de/es/fr/it/pl); end-to-end
integration test against real Postgres pins both the resume flow and
the per-kind bucket increment contract.
v1.4.15 Phase B2, criterion 4. Every IntegrationStatus failure path
now writes a single AuditLog row with a documented schema, and this
commit lights up the real-Postgres regression guard for it:
action : "integrations.sync.failed"
userId : <subject>
details : {
integration: "withings" | "moodlog",
kind: "transient" | "reauth_required",
errorCode: string | null,
message: string, // plaintext (not encrypted —
// audit table is admin-only)
attemptNumber: number, // 1..N consecutive
state: "error_transient" | "error_reauth"
}
Successes are NOT audited — that path would be far too noisy
(every webhook-triggered sync is a row, ~ten times a day per active
user) and the integration_statuses.lastSuccessAt column already
tracks the same signal at one row per integration per user.
The integration test in tests/integration/integration-status.test.ts
provides 8 cases against a real Postgres testcontainer:
- failure writes both the integration_statuses row AND a matching
AuditLog entry, with the structured meta validated field-by-field
- lastError is encrypted at rest (column never matches plaintext)
- getIntegrationStatus() decrypts cleanly on read
- success clears the streak AND does NOT write an audit row
- admin Telegram alert fires once per burst (24h re-alert window)
- reauth_required parks the integration so isReauthRequired() is true
- markReconnected() clears the parked state without writing a fresh
success — that's the next sync's job
- markDisconnected() preserves the row as a tombstone but wipes
error state
- CASCADE delete on User removes the IntegrationStatus row
Setup file additions: integration_statuses added to the truncate-list
in tests/integration/setup.ts (already shipped under commit 4db72a8;
this commit only adds the test file that consumes it).
Tests: 11 / 39 integration pass. The dispatcher is module-mocked so
the test doesn't actually hit Telegram — we only assert that the
helper INVOKED the dispatcher with the right payload.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>