Files
RenewCMS/features/verified_account_required.qa.md
T

7.5 KiB

QA procedure: verified_account_required

Package: test (black-box), extending test/article_test.go (mockArticleRepo) and test/user_test.go (mockUserRepo). This slice's router mount touches both the article and auth routers, so the HTTP-level checks live in a new test/verified_account_required_http_test.go.

Test infrastructure additions needed first

  1. mockUserRepo.Get already honors m.fail.trigger("Get") and already returns user.ErrUserNotFound for an absent id (test/user_test.go), so no new mock methods are required on the user side.
  2. mockArticleRepo already supports seeding via repo.articles[id] = article.Article{...} and a failNext(...) helper (test/article_test.go); no changes needed there either.
  3. Build the middleware under test directly: middleware.RequireVerifiedUser(getUser *useCases.GetUserUseCase) func(http.Handler) http.Handler, exported from api/middleware, and claims.UserId(r *http.Request) (uint32, bool), exported from api/pkg/claims. Construct useCases.NewGetUserUseCase(repo) over a mockUserRepo.

Setup shared by every case

  1. Call validator.InitValidator() once.
  2. Build a test jwtauth.New("HS256", []byte("test-secret"), nil).
  3. Wire article.NewHandler(create, get, list, delete, update, getUser) (extended with the new GetUserUseCase parameter) over a mockArticleRepo and the mockUserRepo/GetUserUseCase from step 3.
  4. Mount the routes on a fresh chi.NewRouter() mirroring the new api/router.go shape:
    • r.Use(jwtauth.Verifier(token)) on the whole /article group (parses, never rejects)
    • r.Get("/", articleHandler.ListArticles) directly under that Verifier-only group
    • a nested r.Group adding jwtauth.Authenticator(token) then middleware.RequireVerifiedUser(getUser), containing r.Get("/{id}", ...), r.Post("/", ...), r.Delete("/{id}", ...), r.Patch("/{id}", ...), r.Patch("/{id}/publish", ...), r.Patch("/{id}/unpublish", ...), r.Post("/{id}/image", ...) No live server.
  5. Mint jwt cookies with token.Encode(map[string]any{"user_id": uint32(1)}) for the valid-token cases, following the convention in features/article_publishing.qa.md.
  6. Seed mockUserRepo per scenario via repo.users[1] = user.User{ID: 1, IsVerified: ...}.

HTTP checks: protected routes reject an unverified caller

  1. Seed mockUserRepo with user id 1, IsVerified: false; seed mockArticleRepo with article id
    1. Table-driven over GET /v1/article/1, POST /v1/article/ (body {"title":"Valid Title","body":"Some content"}), DELETE /v1/article/1, PATCH /v1/article/1 (body {"body":"Updated"}), PATCH /v1/article/1/publish, PATCH /v1/article/1/unpublish, each sent with the valid cookie from step 8 → assert status 403 and body exactly apperr.VerificationRequiredErrorMessage. Assert the article repository was not mutated by any of the write calls.
  2. Same seed, POST /v1/article/1/image with the valid cookie and a multipart form field "file" holding before.png / image/png → assert status 403, body apperr.VerificationRequiredErrorMessage, and the image repository's Create is never called (mirroring the assertion style in test/upload_article_image_test.go).

HTTP checks: a verified caller is unaffected

  1. Seed mockUserRepo with user id 1, IsVerified: true. POST /v1/article/ with the valid cookie and body {"title":"Valid Title","body":"Some content"} → assert status 201 and the response body decodes to the created article.Article.

HTTP checks: an unresolvable caller still answers 401, not 403

  1. Table-driven over three token shapes, each against a fresh seed of user id 1, IsVerified: false, sent as POST /v1/article/ with body {"title":"Valid Title","body":"Some content"}, asserting status 401 and body exactly apperr.UnauthorizedErrorMessage:
    • mint the jwt with token.Encode(map[string]any{"role": "user"}) (no user_id key at all);
    • mint the jwt with token.Encode(map[string]any{"user_id": "one"}) (string, not a number);
    • mint the jwt with token.Encode(map[string]any{"user_id": uint32(999)}) (well-formed but no such user in the repo).

HTTP checks: no session at all is still 401

  1. GET /v1/article/1 and POST /v1/article/ with no jwt cookie → assert status 401 for both (produced by jwtauth.Authenticator itself, before RequireVerifiedUser runs); do not assert an exact body.

HTTP checks: a user-repository failure while resolving the caller

  1. Mint a valid jwt cookie for user id 1. Configure mockUserRepo's failSwitch so the next call to Get returns a generic error (not user.ErrUserNotFound), via the existing failNext("Get")-style helper. DELETE /v1/article/1 with that cookie, article repository seeded with article id 1 → assert status 500, body exactly "Internal server error", and that the article repository's article with id 1 still exists (the delete use case was never reached).

HTTP checks: the offline-inclusive listing is gated by verification

  1. Seed mockUserRepo with user id 1, IsVerified: false; seed mockArticleRepo with an online article id 1 and an offline article id 2. GET /v1/article/ with the valid cookie from step 8 → assert status 200; decode the JSON array and assert it has exactly one element, id 1.
  2. Same article seed, mockUserRepo user id 1 with IsVerified: true → GET /v1/article/ with the same cookie → assert status 200; decode the JSON array and assert both ids 1 and 2 are present.
  3. Same article seed, no user with id 999 in mockUserRepo, jwt cookie minted for user id 999 → GET /v1/article/ with that cookie → assert status 200; assert exactly id 1 is present (fallback to online-only on user.ErrUserNotFound).
  4. Same article seed, valid cookie for user id 1, mockUserRepo's failSwitch set so the next Get call returns a generic error → GET /v1/article/ with that cookie → assert status 200; assert exactly id 1 is present (a user-lookup failure falls back to online-only rather than failing the request).

Regression check on existing fixtures

  1. features/article_publishing.feature, features/edit_article_body.feature, features/upload_article_image.feature and the corresponding test/*_test.go files mint a valid jwt cookie for "a user with id 1" without asserting anything about that user's IsVerified status. After this slice, those HTTP tests' mockUserRepo fixture must seed user id 1 with IsVerified: true (or the equivalent id used in each file), or every one of those protected-route assertions will start failing with 403 instead of the expected success/404/400 status. This is a required update to those test files' setup, not a new behavior; call it out explicitly when touching them so the regression is caught at review time rather than by a surprise task test failure.

Manual verification (not exercised by the Go test)

  • The SPA-visible behaviour of an unverified, logged-in user landing on the back-office article screens (create/edit/publish/image-upload) and seeing a 403 instead of the normal editing UI, and the front-office/back-office listing showing only published articles to that same unverified session, is out of scope for this slice's automated test and should be checked by hand against a running dev build with an unverified test account.
  • No SMTP involvement.