74 lines
5.3 KiB
Markdown
74 lines
5.3 KiB
Markdown
# QA procedure: public_article_read
|
|
|
|
Package: `test` (black-box, extending `article_test.go`, `verified_account_required_http_test.go`
|
|
and `not_found_errors_test.go`).
|
|
|
|
## Setup shared by every case
|
|
1. Call `validator.InitValidator()` once.
|
|
2. Reuse `mockArticleRepo` (`test/article_test.go`) and `mockUserRepo` (`test/verified_account_required_http_test.go`)
|
|
as-is; no fake changes expected.
|
|
3. Wire the real use cases over the fakes: `useCases.NewGetArticleUseCase`, `useCases.NewGetUserUseCase`,
|
|
plus `NewCreateArticleUseCase`/`NewListArticlesUseCase`/`NewDeleteArticleUseCase`/
|
|
`NewUpdateArticleUseCase` for the regression routes. Build
|
|
`article.NewHandler(create, get, list, delete, update, getUser)`.
|
|
4. Build a test `jwtauth.New("HS256", []byte("test-secret"), nil)`.
|
|
5. Mount routes on a fresh `chi.NewRouter()` mirroring the amended `api/router.go` shape:
|
|
- `r.Route("/v1", func(r chi.Router) { r.Route("/article", func(r chi.Router) { ... }) })`
|
|
- `r.Use(jwtauth.Verifier(token))` on the whole `/article` group
|
|
- `r.Get("/", articleHandler.ListArticles)` and `r.Get("/{id}", articleHandler.GetArticle)`
|
|
directly under that Verifier-only group
|
|
- a nested `r.Group` adding `jwtauth.Authenticator(token)` then
|
|
`middleware.RequireVerifiedUser(getUserUseCase)`, containing `r.Post("/", ...)`,
|
|
`r.Delete("/{id}", ...)`, `r.Patch("/{id}", ...)`, `r.Patch("/{id}/publish", ...)`,
|
|
`r.Patch("/{id}/unpublish", ...)`
|
|
No live server.
|
|
6. Mint a valid jwt cookie for user id 1 via `token.Encode(map[string]any{"user_id": uint32(1)})`.
|
|
7. Seed the article repo directly, e.g. `repo.articles[1] = article.Article{ID: 1, Title:
|
|
"Backyard Renovation Update", IsOnline: true}` and `repo.articles[2] = article.Article{ID: 2,
|
|
Title: "Draft: Kitchen Remodel", IsOnline: false}`.
|
|
8. Seed the user repo with `repo.users[1] = user.User{ID: 1, IsVerified: <true|false>}` as each case needs.
|
|
|
|
## HTTP checks
|
|
9. `GET /v1/article/1` (online), no cookie -> 200, decode body, assert `id == 1`.
|
|
10. `GET /v1/article/2` (offline), no cookie -> 404, body `"Article not found\n"`.
|
|
11. `GET /v1/article/9999` (no article with that id), no cookie -> 404, body `"Article not found\n"`.
|
|
12. `GET /v1/article/2`, cookie for user 1 with `IsVerified: true` -> 200, decode body, assert `id == 2`.
|
|
13. `GET /v1/article/1`, cookie for user 1 with `IsVerified: true` -> 200, `id == 1`.
|
|
14. `GET /v1/article/9999`, cookie for user 1 with `IsVerified: true` -> 404, body `"Article not found\n"`.
|
|
15. `GET /v1/article/2`, cookie for user 1 with `IsVerified: false` -> 404, body `"Article not found\n"`
|
|
(never 403).
|
|
16. `GET /v1/article/1` and `GET /v1/article/2`, cookie named `jwt` whose value is an arbitrary
|
|
non-token string (e.g. `"not-a-real-token"`) -> 200 for id 1, 404 for id 2 (same as step 9/10,
|
|
proving the invalid cookie falls back to public visibility).
|
|
17. `GET /v1/article/not-a-number`, no cookie -> 400, body `apperr.IdUint32ErrorMessage + "\n"`.
|
|
18. Regression, no cookie: `POST /v1/article/` -> 401; `DELETE /v1/article/1` -> 401;
|
|
`PATCH /v1/article/1` -> 401; `PATCH /v1/article/1/publish` -> 401;
|
|
`PATCH /v1/article/1/unpublish` -> 401.
|
|
19. Configure the fake repo's `failSwitch` so the next `Get` call returns `errRepositoryFailure`
|
|
(`failNext("Get")`). `GET /v1/article/1` with no cookie -> 500, body `"Internal server error\n"`.
|
|
|
|
## Required amendments to existing tests/specs (part of this slice, not optional cleanup)
|
|
20. `test/verified_account_required_http_test.go`: move `r.Get("/{id}", articleHandler.GetArticle)`
|
|
in `newVerifiedAccountRequiredRouter` out of the `Authenticator` + `RequireVerifiedUser` group
|
|
into the outer Verifier-only group (mirroring step 5 above); remove the
|
|
`{http.MethodGet, "/v1/article/1", ""}` row from `protectedRouteCases` and the
|
|
`{http.MethodGet, "/v1/article/1"}` row from `noCookieCases` in `TestVerifiedAccountRequiredHttp`,
|
|
since that route no longer produces 403 or 401.
|
|
21. `test/not_found_errors_test.go`: move `r.Get("/{id}", articleHandler.GetArticle)` in
|
|
`newNotFoundErrorsRouter` the same way; change the `"Fetching an offline article still answers
|
|
404 with the same message"` subtest to use an unverified or anonymous caller (it currently uses
|
|
`newVerifiedUserRepo()`, which will now 200 instead of 404); replace `"The article route still
|
|
requires authentication before existence is even checked"` with an assertion that an anonymous
|
|
request for an unknown id now answers 404 (covered by step 11 above, so this subtest can instead
|
|
be deleted in favor of the new test file, or repurposed to check `GET /v1/article/1` with no
|
|
cookie still answers 200 as a cross-file regression note).
|
|
`features/verified_account_required.feature` and `features/not_found_errors.feature` get the
|
|
corresponding Examples-table/scenario edits described in this feature's Background.
|
|
|
|
## Manual verification (not exercised by the Go test)
|
|
- SPA-visible behaviour: confirm the public front-office single-article view (unauthenticated) can
|
|
open a published article and 404s on a draft URL, and the authenticated back-office view can open
|
|
its own unpublished draft for editing, against the real embedded frontend and a real SQLite file.
|
|
Not exercised by `task test`; requires `task dev` or a built `task build` binary.
|
|
- No SMTP involvement.
|