5.3 KiB
QA procedure: account_export_data
Package: test (black-box), extending test/user_test.go (mockUserRepo), the existing article, entry, image, author, api-key, role and audit fakes (reuse; do not duplicate type names), and stubHasher.
Test infrastructure additions needed first
- Fakes: add the list-by-creator and list-by-uploader methods to the article, entry and image fakes, and an actor-filtered list to the audit fake (honouring
audit.Filter.Actor). - A
zipEntries(t, body []byte) map[string][]bytehelper that reads the archive witharchive/zipand fails the test if there are directory entries or duplicate names. - A fixed clock for the use case (
func() time.Time) so the filename date is deterministic.
Setup shared by every case
- Call
validator.InitValidator()once. - Build
useCases.NewExportUserDataUseCase(...)over the fakes. MountGET /v1/user/me/exporton a freshchi.NewRouter()with the session chain ofNewUserRouterand the per-user success-only limiter (one per 15 minutes). The limiter takes a clock, or the tests drive the window with twohttptestcalls and a short injected window for the expiry case. - Seed marta (id 7), ines (id 8), articles, entries, images, audit events and an author per the feature. No live server, real SMTP or real SQLite.
Use-case checks
- Run the use case for marta. Assert:
- Exactly four entries with the expected names.
- Each parses as JSON, with the field names of the feature.
profile.jsonhas no key containingpassword,code,tokenorhash(walk the decoded JSON recursively).- The API-key rows have neither the hashed key nor the raw key.
- Scope. Assert that ines's rows never appear. Assert article 4 (credited to marta's author, not created by her) is included. Assert that soft-deleted rows are excluded and that anonymous failed-login events are excluded.
- Empty user. Assert four files, each with empty arrays (never null) and
authornull. - A failing fake repository gives a non-nil error and no archive bytes.
- The audit event
user.export_datais recorded only on success.
HTTP checks
- Success. Assert the status,
Content-Type: application/zip,Cache-Control: no-store, andContent-Dispositionequal toattachment; filename="renewcms-export-marta-2026-10-29.zip". Also assert the archive opens. - A username with unusual characters gives a sanitised filename.
- Rate limit. A second call within 15 minutes gives 429 with a numeric
Retry-After(<= 900 and > 0) and the exact body "Too many requests. Please try again later.". Another user is unaffected. A call after the window gives 200. - A 500 does not consume the slot (fail the fake once, then succeed).
- Auth gates: 401, API key 403 and unverified 403, with exact bodies.
Vitest (SPA)
Strings live in messages.ts in English and French under the same keys.
I18n checks (extend the existing web/src/i18n/*.spec.ts style, e.g. spa.spec.ts):
- Key parity: every new key exists in both
enandfr, with no empty value and the same placeholders (filename, minutes, countdown). - French render: for each state with
setLocale('fr'), assert the French text is shown and no English string remains; switchingen,fr,enrestores the English copy. - English values equal the design copy verbatim.
Flagged assumption: the French wording is authored by the coder because docs/design has none. French strings to author, for the user's review: the card title and text, the four content rows (title and description), "Download my data", "Ready in a few seconds.", "Preparing download…", the "Download started" toast pair, the rate-limit title, text and "Available in {time}", and the error title and text.
| State | Mocked /v1 |
|---|---|
| Direct (recommended): default | none |
| Direct: preparing | GET /user/me/export pending |
| Direct: started | 200, application/zip, the Content-Disposition header; assert the toast text and that a download was triggered with that filename |
| Direct: rate limited (429) | 429 with Retry-After 720; assert the copy and the "Available in 12:00" label, no date mocking |
| Direct: error | 500 and a rejected fetch |
- Also assert that a second click while preparing sends no second request.
- Also assert that focus returns to the button after an error.
Visual
task visual:check -- account. Board data to reuse: username marta, the filename renewcms-export-marta-2026-10-29.zip in the toast, and the Retry-After of 720.
- Checked states: "Direct (recommended): default", "Direct: preparing", "Direct: started", "Direct: error".
skip: clock-sensitive: "Direct: rate limited (429)".- Async states ("Alternative, emailed link: default", "Alternative: requested", "Alternative: already in progress", "Alternative: error"): the user accepted the recommendation to skip them in
web/visual/states.tsthroughexceptionentries. TheapprovedByfield of each exception must still be recorded as the user at coder time; the coder must not invent an approver, and the entries are not valid until it is set.
Manual verification
- Open a real downloaded ZIP and read all four files.
- Download in Firefox and Chromium and check the saved filename.
- Large account: time a user with thousands of events.
- The 15-minute window against a real clock and a restart.
- Keyboard: the button and the toast announce "Download started".