feat: new emails design
This commit is contained in:
1 parent
4e41d60142
commit
30a4df47b1
15 files changed
+498
-68
No files matched your search
@@ -0,0 +1,71 @@
|
||||
# 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`.
|
||||
Reference in new issue
Block a user