Skip to content

fix(tests): make notification-email waits deterministic instead of wall-clock - #253

Merged
KaliCZ merged 1 commit into
mainfrom
claude/bold-shirley-efb237
Jul 29, 2026
Merged

fix(tests): make notification-email waits deterministic instead of wall-clock#253
KaliCZ merged 1 commit into
mainfrom
claude/bold-shirley-efb237

Conversation

@KaliCZ

@KaliCZ KaliCZ commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Problem

JobOfferApiTests.Create_NotifiesTheOwner is flaky under full npm test load: it spent ~21s waiting for the job-offer notification, then failed with Assert.Single() Failure: The collection was empty, while passing in isolation.

The notification tests polled factory.EmailSender.Sent against a fixed 20-second wall-clock deadline. Under load that breaks two distinct ways:

  1. Slow daemon — with Docker + several parallel test hosts, the async daemon can legitimately take longer than 20s to deliver. The deadline gives up while delivery is still in flight.
  2. A silent-skip race at startup — the subscriptions register SubscribeFromPresent, which seeds each shard''s starting position at the current high-water mark when the shard starts. The daemon acquires HotCold leadership in the background after the host already serves requests, so under load an event committed by an early test before the seed is skipped forever — no timeout ever sees that email.

Fix (test infrastructure only)

  • TestWebApplicationFactory.InitializeAsync now blocks until every subscription agent is registered with the daemon. Agents register only after their position is seeded, and at fixture init the database is still empty, so no test event can ever land before the subscriptions listen.
  • The duplicated per-class WaitForEmailsAsync (fixed 20s poll) is replaced by one factory helper that awaits Marten''s WaitForNonStaleProjectionDataAsync — it tracks actual daemon progress up to the event-store sequence at call time, with a generous 60s cap that only bites when something is genuinely broken, and rich shard diagnostics on timeout.
  • Once the daemon has caught up, delivery for the committed events is final — so the Task.Delay(1500) "no extra email" grace sleeps are gone and those assertions are now deterministic (previously a slow daemon delivering a wrong extra email after the grace window would falsely pass).

No production code changed.

Verification

  • Full integration suite: 108/108 in ~11s (notification tests are faster without the grace sleeps).
  • 3 concurrent runs of the whole integration suite (18 PostgreSQL containers churning): all green.
  • Full npm test (backend + frontend + E2E): all green; integration suite finished in 12s under that load, where the flake used to appear.

🤖 Generated with Claude Code

…ll-clock

Create_NotifiesTheOwner (and the other notification tests) polled the fake
email sender against a fixed 20s deadline. Under full-suite load that broke
2 ways: the daemon can legitimately take longer than the deadline, and a
test event committed before the SubscribeFromPresent subscriptions seed
their starting position is skipped outright, so no deadline would ever see
the email.

The factory now blocks fixture init until every subscription agent is
registered (position seeded while the database is still empty), and tests
wait on Marten's WaitForNonStaleProjectionDataAsync — daemon progress, not
a guessed timeout. Since delivery is final once the daemon catches up, the
1.5s "no extra email" grace sleeps are gone too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@KaliCZ
KaliCZ merged commit fd3c118 into main Jul 29, 2026
7 checks passed
@KaliCZ
KaliCZ deleted the claude/bold-shirley-efb237 branch July 29, 2026 12:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant