Commit Graph
3 Commits
Author SHA1 Message Date
Marc-André Bombeck ef585c34c4 test(e2e): draw the SMTP stub port below the ephemeral ranges
The stub port was drawn from 49152-65535. Linux assigns local ports for
outgoing connections from 32768-60999, so the drawn number could already
be held by a client socket the runner had opened, and the notification
journey failed in beforeAll with EADDRINUSE although no other stub was
listening. The port now comes from 20000-32767, below the Linux and
macOS ephemeral ranges.
2026-09-14 00:20:58 +02:00
Marc-André Bombeck 74dbb1a2e4 test(e2e): give the notification journey a server and a scope of its own
The journey's isolation went one way only. `test.describe.configure` orders
the tests inside the file; the suite is fullyParallel with two workers, so
everything else runs beside it — and its own trigger, the admin reminder
sweep, walked the whole instance and dispatched for every account that had
an overdue dose. With the mail env pointed at the local responder, that
would have started sending real messages for accounts this file has never
heard of, and the exact-equality assertion on the accepted recipients would
have been reading them. The sweep now names the account it then counts, and
the comments that defended the opposite direction say what the exposure
actually is.

Its own server, too, and for two reasons. The `SMTP_*` vars decide whether
the Email card renders at all, so on the shared server they put a card the
axe scan and the card-anatomy sweep had never measured onto
`/settings/integrations` for every spec that opens the route. And unset,
`HEALTHLOG_PROCESS_TYPE` starts the pg-boss reminder scheduler inside the
process serving requests, whose every-fifteen-minutes tick either mints the
missed-dose row that makes the sweep skip its own dispatch or dispatches
beside it. A third server carries both: mail configured, `web`, and only
this spec talks to it.

The shared server keeps `all` on measured grounds rather than taste. Worker
status is per-process and in-memory, so a `web` process reports the worker
stopped, `/admin` renders that row destructive-red, and axe fails it at
3.97:1 against the muted tile — a real contrast defect any operator running
the documented web/worker split already sees, and not this branch's to fix.
That limit is written where the server is defined.

Also here: the stub draws its port per run instead of pinning one, and says
so by name when the port is taken; teardown ends its sockets and survives a
setup that never got as far as starting it; the ledger reset stops deleting
rows whose subject is this account but whose recipient is somebody else,
and clears intake events explicitly rather than through a cascade; the
attempt poll refuses a window larger than the twenty rows the diagnostic
returns; and the header names the precondition the file cannot enforce and
the internal reasons it pins on purpose.

Verified against a production build and a throwaway Postgres: the journey
three times over at one worker, then the a11y and settings geometry specs
at two. Both assertions broken once to see them fail.
2026-09-10 06:35:40 +02:00
Marc-André Bombeck 8be51d9beb test(e2e): cover notification preferences through to the dispatch decision
One browser journey for the web arm of notification delivery: an account
configures its channels on the settings surface, a due medication reminder
runs through the product's own sweep, and the dispatch decision is read back
off that account's `push_attempts` ledger via the admin diagnostic.

What it pins:
  - the enabled channel delivers (`result: ok`) and the message really leaves
    the process, asserted against a local SMTP responder rather than against
    the ledger row alone;
  - a channel that is configured and switched OFF is never attempted, and its
    own test control is inert on the surface;
  - the APNs arm is attempted and skipped, and the ledger's reason
    distinguishes the transport refusing (`apns_not_configured`) from the
    client-managed opt-in refusing (`client_managed`) — with email still
    delivering under the opt-in, which is the published "APNs leg only"
    contract;
  - an ntfy relay URL inside a private range is refused with 422, leaves the
    stored config untouched, and writes no attempt.

Email is the channel exercised for real because it is the only one that can
be: every account-supplied delivery target crosses the SSRF floor
(`isPublicUrl` plus `safeFetch({ requirePublicHost: true })` with a
connect-time address pin), so ntfy, the generic webhook and Web Push refuse a
local stub by design, and Telegram and APNs address hard-coded third parties.
Email's transport is operator configuration, so `e2e/setup/smtp-stub.ts` can
stand in for it end to end. The config points the e2e server at that stub.

The journey carries one skipped test at full strength: the medication detail
surface decides between the reminder switch and the client-managed chip on
`notificationPrefs.medication.clientManaged` read out of `/api/auth/me`, and
that payload does not carry the field. The flag therefore reads as false for
every account and the chip never renders, so a person whose phone owns the
reminders is still shown a server-side switch that no longer decides anything.
The write path and the dispatch decision are both correct; only the web
reflection is missing its end.

The account and its jar are seeded in global setup, and its channels, devices,
ledger and preferences are reset before every test so the counts are about the
run in front of them.
2026-09-10 05:41:17 +02:00