72 lines
4.9 KiB
Markdown
72 lines
4.9 KiB
Markdown
# QA procedure: transactional email templates
|
|
|
|
## Automated (Go, `test/`)
|
|
|
|
This slice touches a use-case call site (a string literal) and two static template files; it has
|
|
no new HTTP surface of its own, so no new handler/router wiring is exercised here. Reuse the
|
|
existing fakes already in `test/` (`mockMailRepo` from `test/mail_test.go`, the fake `user.Repository`
|
|
used by the registration and password-reset tests) rather than building new ones.
|
|
|
|
1. **Registration sends "verification".**
|
|
In the existing registration test file (or its replacement after this slice), build
|
|
`CreateUserUseCase` over an empty fake `user.Repository`, a real hasher, and `mockMailRepo`, call
|
|
`CreateUser` with username `alice`, password `hunter2example12`, email `alice@example.com`, assert
|
|
the call succeeds, then assert `mockMailRepo.SentEmails` has exactly one entry whose `Template`
|
|
field equals `"verification"` (not `"mailValidation"`).
|
|
|
|
2. **Password reset sends "password-reset".**
|
|
Build `RequestPasswordResetUseCase` over a fake `user.Repository` seeded with a user whose email is
|
|
`alice@example.com`, a real hasher, `mockMailRepo` and a public URL, call `RequestPasswordReset("alice@example.com")`,
|
|
assert it returns `nil`, then assert `mockMailRepo.SentEmails` has exactly one entry whose
|
|
`Template` field equals `"password-reset"` (not `"passwordReset"`).
|
|
|
|
3. **render() produces the right subject, link and copy for each known template.**
|
|
Construct a real `mailer.NewMailRepository(config.SmtpSettings{})` (SMTP fields are irrelevant
|
|
since `DialAndSend` is never called here) and call its render step directly (test lives in
|
|
package `test`, so this exercises the exported behavior surface of a real `*mailer.MailRepository`
|
|
without opening a network connection):
|
|
- `render("verification", <data with Host "https://cms.example.com", VerificationCode "abc123">)`
|
|
→ subject `"Verify your RenewCMS account"`, body contains
|
|
`https://cms.example.com/register/validate?c=abc123`, `Verify my account`,
|
|
`This link expires in 2 hours.`
|
|
- `render("password-reset", <data with Host "https://cms.example.com", VerificationCode "abc123",
|
|
Email "alice@example.com">)` → subject `"Reset your RenewCMS password"`, body contains
|
|
`https://cms.example.com/register/reset/validate?c=abc123&email=alice%40example.com`,
|
|
`Choose a new password`, `This link expires in 1 hour and works once.`
|
|
|
|
(`html/template` auto-escapes `Email` in the query string, turning `@` into `%40`; assert the
|
|
escaped form, matching what `MailRepository.Send` will actually produce.)
|
|
|
|
3b. **render() fallback and failure paths.**
|
|
- `render("does-not-exist", nil)` returns an error (template file not found) and the returned
|
|
subject is still the old default `"RenewCMS | Action required"`, with an empty body.
|
|
- A `render(<known templateName>, <data that fails Execute>)` call returns an error; assert
|
|
`MailRepository.Send` with the same inputs returns an error too and never gets to
|
|
`DialAndSend` (no network dependency needed to prove this: a failing `render` return is
|
|
sufficient to prove `Send` short-circuits before dialing, since `Send`'s existing structure
|
|
calls render, then returns immediately on its error).
|
|
|
|
4. **Old template files are gone.**
|
|
`os.Stat("internal/infrastructure/mailer/templates/mailValidation.html")` and the `passwordReset.html`
|
|
equivalent each return an error satisfying `os.IsNotExist`.
|
|
|
|
5. **Existing slice tests still pass with updated literals.**
|
|
Run the full suite (`task test`) and confirm the tests backing
|
|
`registration_gate_and_verification_email.feature` and `request_password_reset.feature` still pass
|
|
now that their scenario text and any Go assertions inside their test files use `"verification"` and
|
|
`"password-reset"` instead of the old strings. If those test files still contain the old literals
|
|
as Go string constants (not just prose in the `.feature` files), they need the same rename; point
|
|
this out to `coder` rather than silently doing a code edit here.
|
|
|
|
## Manual verification (not covered by the automated Go tests above)
|
|
|
|
- **Rendered appearance in an actual mail client.** The bulletproof button (table-cell background +
|
|
padded link), the hidden preheader text, and the `prefers-color-scheme: dark` overrides can only be
|
|
judged by opening the rendered HTML in a real or preview mail client (or the board file
|
|
`docs/design/source/RenewCMS Emails.dc.html` next to a manually triggered send through a real SMTP
|
|
server) — this is not something `os.ReadFile` + `html/template.Execute` can verify. Confirm both
|
|
light and dark rendering for both templates.
|
|
- **Real SMTP delivery end to end.** Sending an actual email via `SMTP_HOST`/`SMTP_PORT` and eyeballing
|
|
the arrived message (subject line, sender name "RenewCMS <...>", clickable button, monospace
|
|
fallback URL) is a manual item; nothing here exercises `gomail.Dialer.DialAndSend`.
|