Files
HealthLog/tests/integration
Marc-André BombeckandClaude Opus 4.7 2fbf56dc16 feat(audit): sync failures logged with structured meta
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>
2026-05-09 21:07:44 +02:00
..