Skip to content

test(e2e): implement welcome OTP email scenario (email-delivery)#91

Merged
aspiers merged 3 commits into
mainfrom
hyper-cucumber-all-features
Apr 20, 2026
Merged

test(e2e): implement welcome OTP email scenario (email-delivery)#91
aspiers merged 3 commits into
mainfrom
hyper-cucumber-all-features

Conversation

@aspiers
Copy link
Copy Markdown
Contributor

@aspiers aspiers commented Apr 20, 2026

Summary

  • Implements atproto-78w: first of three scenarios in features/email-delivery.feature (welcome OTP email for a new user)
  • Adds two new cucumber step definitions:
    • Given 'a mail trap is capturing outbound emails' — Mailpit health check against /api/v1/info
    • Then 'the email body contains a numeric OTP code' — asserts the extracted OTP is all digits
  • Reworks the feature file to use the existing unique test email / returning user fixture pattern (hermetic, matches passwordless-authentication.feature). Literal emails (alice@example.com, bob@example.com) required either a DB-reset hook not yet plumbed to the e2e runner or a seeded test env — neither pragmatic.

The feature stays @pending because scenarios 2 and 3 reference steps that don't exist yet (tracked by atproto-8mx and atproto-a21). The @pending tag is removed by the cleanup bead atproto-c7i once all three are implemented and passing.

Supersedes PR #63 (closed; force-pushed branch).

Test plan

  • pnpm format:check clean
  • pnpm lint clean
  • pnpm typecheck clean
  • pnpm test — 442 unit tests pass
  • pnpm test:coverage — no regression
  • pnpm test:e2e --dry-run --name "New user receives a welcome OTP email" — 6 steps, 0 undefined
  • E2E CI run against Railway PR env executes scenario 1 successfully (verified by reviewer after CI completes)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests
    • Enhanced email delivery tests to use Mailpit, including health-check gating and auth-protected access to the mail trap.
    • Improved OTP validation to support configurable charsets (alphanumeric or numeric) and to mark specific scenarios as pending; generalized tests to use generated test emails instead of hard-coded addresses.

Implements atproto-78w: the first of three scenarios in
features/email-delivery.feature. Adds two new cucumber step definitions:

  - Given 'a mail trap is capturing outbound emails' — Mailpit health
    check against /api/v1/info; fails the background step fast if the
    mail trap is unreachable, so scenario failures point at the trap
    rather than at the subsequent OTP-arrival step.
  - Then 'the email body contains a numeric OTP code' — asserts the OTP
    extracted by waitForEmail/extractOtp is all digits.

Reworks the feature file to match the existing 'unique test email' /
'returning user' fixture pattern used by passwordless-authentication.
Literal emails (alice@example.com, bob@example.com) required either a
DB-reset hook (not yet plumbed through to e2e runner) or a seeded test
env — neither pragmatic. The unique-email pattern is hermetic and
matches how the active scenarios already run.

The Feature stays @pending for now: scenarios 2 and 3 still reference
step definitions that don't exist yet (atproto-8mx, atproto-a21). The
@pending tag is removed by the cleanup bead atproto-c7i once all three
scenarios are implemented and passing in CI.

Dry-run confirms scenario 1 maps all 6 steps with 0 undefined.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 20, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
epds-demo Ready Ready Preview, Comment Apr 20, 2026 11:21am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

Warning

Rate limit exceeded

@aspiers has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 47 minutes and 29 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 47 minutes and 29 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d4c97331-999d-49b8-8ca3-a5b604352af4

📥 Commits

Reviewing files that changed from the base of the PR and between 1b05bba and 17515e2.

📒 Files selected for processing (6)
  • e2e/step-definitions/account-settings.steps.ts
  • e2e/step-definitions/auth.steps.ts
  • e2e/step-definitions/email.steps.ts
  • e2e/support/mailpit.ts
  • e2e/support/world.ts
  • features/email-delivery.feature
📝 Walkthrough

Walkthrough

Adds Mailpit readiness check and OTP validation steps to Cucumber tests; updates feature scenarios to use Mailpit, abstracts hard-coded emails, and changes OTP expectation to follow a configurable charset.

Changes

Cohort / File(s) Summary
Mailpit integration & step defs
e2e/step-definitions/email.steps.ts
Added a Given step that checks Mailpit health via ${testEnv.mailpitUrl}/api/v1/info using mailpitAuthHeader() and a 5s timeout (gates on testEnv.mailpitPass); added a Then step that validates previously-extracted this.otpCode against a charset-dependent regex (alphanumeric or numeric) and returns pending when mailpitPass is unset; expanded imports to include Given and mailpitAuthHeader.
Feature scenarios
features/email-delivery.feature
Switched mail trap reference from MailHog to Mailpit; removed feature-level @pending and moved @pending to two scenarios; replaced hard-coded email addresses and explicit account/login preconditions with abstract "unique test email", "returning user", and "user is logged into account settings" steps; adjusted assertions to verify receipt for generated test/backup emails and made OTP validation charset-configurable (subject and verification-link checks retained).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I sniff the Mailpit trail tonight,
I hop through tests with tiny might,
OTPs in charset dress,
I check, I cheer, I never rest —
Hooray for green test light! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'test(e2e): implement welcome OTP email scenario (email-delivery)' accurately describes the main change: implementing the first scenario for welcome OTP email testing in the e2e test suite using Cucumber.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch hyper-cucumber-all-features

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@railway-app
Copy link
Copy Markdown

railway-app Bot commented Apr 20, 2026

🚅 Deployed to the ePDS-pr-91 environment in ePDS

Service Status Web Updated (UTC)
@certified-app/pds-core ✅ Success (View Logs) Web Apr 20, 2026 at 11:23 am
@certified-app/demo ✅ Success (View Logs) Web Apr 20, 2026 at 11:22 am
@certified-app/demo untrusted ✅ Success (View Logs) Web Apr 20, 2026 at 11:22 am
@certified-app/auth-service ✅ Success (View Logs) Web Apr 20, 2026 at 11:21 am

@railway-app railway-app Bot temporarily deployed to ePDS / ePDS-pr-91 April 20, 2026 01:07 Destroyed
@coveralls-official
Copy link
Copy Markdown

coveralls-official Bot commented Apr 20, 2026

Coverage Report for CI Build 24663700868

Coverage remained the same at 36.52%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 2027
Covered Lines: 770
Line Coverage: 37.99%
Relevant Branches: 1163
Covered Branches: 395
Branch Coverage: 33.96%
Branches in Coverage %: Yes
Coverage Strength: 3.18 hits per line

💛 - Coveralls

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/step-definitions/email.steps.ts`:
- Around line 10-23: The Mailpit health check can hang; wrap the fetch call in
an explicit timeout using AbortSignal.timeout to fail fast: create an
AbortSignal via AbortSignal.timeout(timeoutMs) (e.g., 5_000) and pass it as the
signal option to fetch inside the Given step (the async function in
email.steps.ts that checks testEnv.mailpitPass), keep the existing headers
(mailpitAuthHeader()) and ensure the signal is aborted/used so fetch throws on
timeout; handle the thrown error the same way (so the existing res.ok check
stays) and propagate a clear error if the probe times out or fails.
- Around line 78-86: The step Then('the email body contains a numeric OTP code',
implemented as the anonymous step function in email.steps.ts, should use the
configured charset instead of hard-coding digits and must not log the actual OTP
value; update the validation to read testEnv.otpCharset (available from
e2e/support/env.ts) and choose the correct pattern (e.g., numeric → /^\d+$/,
alphanumeric → /^[A-Za-z0-9]+$/) to test this. Keep the existing guard that
throws if this.otpCode is missing, but change the validation error to a
non-sensitive message like "OTP does not match configured charset" (no token
included). Optionally, if you prefer runtime detection, implement the same
charset detection used by buildIncorrectOtpCode in auth.steps.ts and skip
validation when detection is ambiguous.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0cf39ec6-eda8-4228-9791-c4fad120b275

📥 Commits

Reviewing files that changed from the base of the PR and between 362cdd4 and cedbc8a.

📒 Files selected for processing (2)
  • e2e/step-definitions/email.steps.ts
  • features/email-delivery.feature

Comment thread e2e/step-definitions/email.steps.ts
Comment thread e2e/step-definitions/email.steps.ts Outdated
Comment thread features/email-delivery.feature Outdated
@@ -1,27 +1,26 @@
@email @pending
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 The @pending tag is placed on the Feature itself (line 1), which Gherkin propagates to all three Scenarios — including Scenario 1, which this PR claims is fully implemented. Because e2e/cucumber.mjs excludes all @pending scenarios via not @pending, CI will produce 0 runs for this feature, making the PR test plan item "E2E CI run against Railway PR env executes scenario 1 successfully" impossible to satisfy. Fix: move @pending from the Feature to only Scenarios 2 and 3.

Extended reasoning...

What the bug is and how it manifests

The file features/email-delivery.feature opens with @email @pending on line 1, decorating the Feature: keyword itself. In Gherkin, any tag applied to a Feature node is automatically inherited by every Scenario and Scenario Outline within that feature. This means all three scenarios — including Scenario 1 ("New user receives a welcome OTP email"), which the PR describes as fully implemented — carry the @pending tag.

The specific code path that triggers it

e2e/cucumber.mjs line 8 adds 'not @pending' to tagExclusions, and line 17 joins those into the tag expression used by both pnpm test:e2e and pnpm test:e2e:headless. The CI workflow .github/workflows/e2e-tests.yml line 609 runs pnpm test:e2e:headless, which uses that config. The resulting tag filter causes the cucumber runner to skip every scenario tagged @pending, which is every scenario in this feature.

Why existing code doesn't prevent it

The individual step definitions are correctly implemented and the dry-run check in the test plan (pnpm test:e2e --dry-run --name "New user receives a welcome OTP email") passes because --name overrides tag filtering. However, the actual CI invocation uses tag filtering without a name filter, so the tag exclusion wins and all three scenarios are skipped.

What the impact would be

The PR's stated goal — having CI verify Scenario 1 — is impossible as written. The CI job will report 0 scenarios executed for this feature, not a passing scenario. The reviewer checkpoint "E2E CI run against Railway PR env executes scenario 1 successfully (verified by reviewer after CI completes)" cannot be satisfied, leaving the PR's testing claim unverifiable.

Step-by-step proof

  1. features/email-delivery.feature line 1: @email @pending → Feature inherits @pending
  2. Gherkin propagation: all 3 Scenarios inherit @pending from the Feature
  3. e2e/cucumber.mjs line 8: tagExclusions = [..., 'not @pending']
  4. Line 17 joins these into the final tag expression passed to cucumber
  5. .github/workflows/e2e-tests.yml line 609: CI runs pnpm test:e2e:headless
  6. Cucumber evaluates tag expression → Scenario 1 matches @pending → excluded
  7. Result: 0 scenario runs for the entire feature in CI

How to fix it

Remove @pending from the Feature and add it only to Scenarios 2 and 3:

@email
Feature: Email delivery
  ...

  Scenario: New user receives a welcome OTP email
    ...

  @pending
  Scenario: Returning user receives a sign-in OTP email
    ...

  @pending
  Scenario: Backup email verification link is delivered
    ...

This allows Scenario 1 to run in CI while keeping Scenarios 2 and 3 pending until their step definitions are implemented.

Comment on lines +20 to +23
)
}
},
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The Mailpit health check error message reports at ${testEnv.mailpitUrl} but the actual endpoint fetched is ${testEnv.mailpitUrl}/api/v1/info, omitting the path. This makes the error less actionable when debugging failures. Fix by appending /api/v1/info to the URL in the error message.

Extended reasoning...

What the bug is and how it manifests

In e2e/step-definitions/email.steps.ts lines 14–22, the Given step fetches ${testEnv.mailpitUrl}/api/v1/info to verify Mailpit is reachable. When the response is not OK, the thrown error message says Mailpit health check failed: ${res.status} at ${testEnv.mailpitUrl}, which omits the /api/v1/info path portion of the URL.

The specific code path that triggers it

The discrepancy is on line 14 vs line 20:

  • Fetch target: ${testEnv.mailpitUrl}/api/v1/info
  • Error message: at ${testEnv.mailpitUrl} (missing path)

This means whenever the health check fails (Mailpit down, wrong credentials, network issue), the developer sees only the base URL with no indication of which endpoint was probed.

Why existing code doesn't prevent it

There is no lint rule or convention enforcement to catch this mismatch. The established pattern in common.steps.ts (e.g. PDS health check failed: ${res.status} at ${testEnv.pdsUrl}/health) correctly includes the full path, but this new step deviates from that pattern.

What the impact would be

Purely a debugging inconvenience in test/e2e code. The health check itself is functionally correct — if Mailpit is reachable, the step passes; if not, it throws. The only consequence is that a developer seeing the failure message cannot immediately tell which specific endpoint was queried without reading the source code.

How to fix it

Change line 20 from:

`Mailpit health check failed: ${res.status} at ${testEnv.mailpitUrl}`

to:

`Mailpit health check failed: ${res.status} at ${testEnv.mailpitUrl}/api/v1/info`

Step-by-step proof

  1. Mailpit is unreachable (e.g. wrong MAILPIT_URL in env).
  2. fetch(${testEnv.mailpitUrl}/api/v1/info) returns a non-OK status (e.g. 404 or connection error).
  3. The if (\!res.ok) branch fires.
  4. The thrown error reads: Mailpit health check failed: 404 at http://localhost:8025.
  5. Developer sees the base URL only — they cannot tell from the message alone that the /api/v1/info endpoint was the one that failed, requiring them to read the source to understand what was checked.

- Move @pending from Feature to scenarios 2 & 3. The Feature-level tag
  was propagating to scenario 1, so cucumber.mjs's 'not @pending'
  exclusion skipped scenario 1 entirely — making the Railway CI "pass"
  meaningless. Scenario 1 now runs; scenarios 2 & 3 stay pending until
  atproto-8mx / atproto-a21.
- Add AbortSignal.timeout(5_000) to the Mailpit health probe so DNS or
  connectivity stalls fail fast instead of hanging CI; include the full
  /api/v1/info path in the error message.
- Rename 'the email body contains a numeric OTP code' to
  '... an OTP code matching the configured charset' and read the regex
  from testEnv.otpCharset so the step works whether OTP_CHARSET is
  numeric (default) or alphanumeric. Drop the extracted OTP from the
  error message — a mismatched charset shouldn't leak the token.

Addresses: claude-bot (@pending propagation, error URL path), CodeRabbit
(timeout, charset detection, OTP leakage).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implement the two remaining scenarios in email-delivery.feature:
  * Returning user receives a sign-in OTP email
  * Backup email verification link is delivered

Remove @pending tags so both now run in CI. Add supporting step defs:
  * "the user requests an OTP for the test email" (reuses world.testEmail
    set by the returning-user Given; clears mailpit to avoid racing with
    the prior welcome OTP).
  * "the user adds a unique backup email" (drives the /account/backup-email/add
    form on the account settings page).
  * "a verification email arrives in the mail trap for the backup email"
    and "the email contains a verification link".

Add fetchEmailBody helper to mailpit support. Add backupEmail and
lastEmailBody fields to EpdsWorld.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@railway-app railway-app Bot temporarily deployed to ePDS / ePDS-pr-91 April 20, 2026 11:21 Destroyed
@sonarqubecloud
Copy link
Copy Markdown

@aspiers aspiers merged commit 1a4be35 into main Apr 20, 2026
15 checks passed
@aspiers aspiers deleted the hyper-cucumber-all-features branch April 20, 2026 14:26
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