# QA procedure: user_response_field_hygiene Package: `test` (black-box), extending `test/account_verification_http_test.go`'s `newAccountVerificationRouter` / `newAccountVerificationTestUserRepo` helpers and `test/user_test.go`'s `mockUserRepo` / `stubHasher`. ## Setup shared by every case 1. Call `validator.InitValidator()` once. 2. Reuse `newAccountVerificationRouter(userRepo, mailRepo, token)` from `test/account_verification_http_test.go`, which wires `auth.NewHandler(...)` with fakes over a fresh `chi.NewRouter()` mirroring `api/router.go`'s `/v1/auth` group (public routes plus the `jwtauth.Verifier` + `Authenticator`-wrapped `verify` route). No live server, no real SQLite, no real SMTP. 3. `newAccountVerificationTestUserRepo()` seeds user id 7 with a non-empty `VerificationCode` (`"hashed:abc123"`) and `VerificationExpiration` one hour out, matching the existing fixture. ## Response-shape check for POST /v1/auth/verify (the fix) 4. Successful verify: `POST /v1/auth/verify` with `{"code":"abc123"}` and a valid jwt cookie for user id 7. Decode the response body into `map[string]json.RawMessage` (not into `user.User`) and assert: - the key set is exactly `{"id", "username", "email", "is_verified", "created_at", "updated_at", "display_name", "role"}`; - none of `password`, `password_reset_code`, `verification_code`, `verification_expiration`, `password_reset_expiration`, `token_version` is present; - `is_verified` unmarshals to `true`; - `display_name` unmarshals to `""`; - `role` unmarshals to `"owner"` (seeded user 7 has `Role: owner`). 5. Rejected verify: same seed, `{"code":"wrong-code"}` → assert status `403` and that the raw response body bytes, treated as a string, contain none of the substrings `password`, `verification_code`, `password_reset_code`, `verification_expiration`, `password_reset_expiration` (the body is plain text from `http.Error`, so this is a substring check, not a JSON-key check). ## Registration check (its own case, correctly gated) 6. `POST /v1/auth/register` against an **empty** `mockUserRepo` (the registration gate only allows register while no verified account exists) with `{"username":"bob","password":"hunter2example12","email":"bob@example.com"}` → assert status `200` and that the raw response body string contains none of the six sensitive substrings from step 5. ## Table-driven sweep of the other auth endpoints (regression guard, not the fix) 7. For each of `POST /v1/auth/login`, `/logout`, `/password-reset`, `/password-reset/confirm`, seed a `mockUserRepo`/`mockMailRepo` pair with a verified user "alice" (`IsVerified: true`, matching the Scenario Outline's Given, since these endpoints don't depend on the registration gate), send the request, and assert the raw response body string contains none of the six sensitive substrings from step 5. This should already pass today since these handlers only ever write a `{"message": ...}` body; the point of the test is to pin that fact down so a future change can't quietly regress it. ## Manual verification (not exercised by the Go test) - The SPA's post-verification screen (`web/src/`) only ever needs `username`/`is_verified` to render; confirm by hand that no SPA code reads `password`, `verification_code` or `password_reset_code` off the verify response, since removing them from the wire format could otherwise silently break a screen that isn't covered by this Go test suite. - Confirm the public response type lives in the shared package under `api/pkg` (not a change to `internal/domain/user/userDomain.go`'s json tags), per the project's stated preference to keep the domain struct's tags intact and shape the public response in the handler layer instead.