Files
RenewCMS/features/article_title_rules.qa.md

77 lines
4.8 KiB
Markdown

# QA procedure: article_title_rules
Package: `test` (black-box, alongside `article_test.go` / `use_case_baseline_test.go` /
`not_found_errors_test.go` / the hasher-domain-interface tests).
## Fakes needed
1. Extend `mockArticleRepo` (`test/article_test.go`) / its shared `failSwitch`
(`test/fakes_common.go`) so a configured failure can return a specific error, not
only the generic `errRepositoryFailure`. Today `failSwitch.trigger` always returns
the same sentinel; add a way to say "the next call to `Create` returns
`article.ErrInvalidTitle`" specifically, distinct from "the next call to `Create`
returns a generic repository error" (e.g. a `failNextWith(method string, err error)`
on `failSwitch`, keeping `errRepositoryFailure` as the default so existing
generic-failure tests don't change).
2. Add a `createCalled` flag (or equivalent) to `mockArticleRepo` if not already
present, so a test can assert `Create` was never reached when request-layer
validation rejects a title first.
## Use-case-level checks (fake repo, no HTTP)
3. `useCases.NewCreateArticleUseCase(repo)`, `CreateArticle(...)` with an empty fake
repo:
- Title `strings.Repeat("😀", 2)` (2 runes, 8 bytes) -> `errors.Is(err,
article.ErrInvalidTitle)`, nothing stored.
- Title `strings.Repeat("😀", 3)` (3 runes, 12 bytes) -> no error, created,
`is_online` false, one article stored.
- Title `strings.Repeat("😀", 200)` (200 runes, 800 bytes) -> no error, created.
Proves the check counts runes (`utf8.RuneCountInString`), not `len()` bytes.
- Title `strings.Repeat("😀", 201)` (201 runes, 804 bytes) -> `errors.Is(err,
article.ErrInvalidTitle)`, nothing stored.
- Keep the existing ASCII boundary cases in `TestCreateArticleTitleLengthLimit`
(0/2/3/200/201) passing unchanged, as a regression check that byte length and
rune length coincide for ASCII.
## HTTP checks (real CreateArticleUseCase over the fake repo, mirroring api/router.go)
4. Call `validator.InitValidator()`.
5. Build `useCases.NewCreateArticleUseCase(fakeRepo)` plus `GetArticleUseCase`,
`ListArticlesUseCase`, `DeleteArticleUseCase` (real, over the same fake repo). Build
`article.NewHandler(create, get, list, delete)`. Mount its routes inside a group
wrapped by `jwtauth.Verifier(token)` + `jwtauth.Authenticator(token)` on a fresh
`chi.NewRouter()`, with a test `jwtauth.New("HS256", []byte("test-secret"), nil)`, as
in `api/router.go`.
6. Mint a valid jwt cookie for user id 1 (`token.Encode(map[string]any{"user_id":
uint32(1)})`, set as the `jwt` cookie on each `httptest.NewRequest`).
7. `POST /v1/article/` with a 2-ASCII-character title (`"ab"`) and a valid body ->
status `400`, `fakeRepo.createCalled` is false. Assert the body is neither
`apperr.ArticleTitleErrorMessage` nor `"Failed to create article"`, to keep the two
400 sources (request-layer vs. domain-layer) distinguishable in the test.
8. `POST /v1/article/` with a 3-ASCII-character title -> status `201`, response body
decodes to the created article JSON, `is_online` false.
9. `POST /v1/article/` with a 200-ASCII-character title (`strings.Repeat("a", 200)`) ->
status `201`, created.
10. `POST /v1/article/` with a 201-ASCII-character title -> status `400`, `Create`
never called.
11. `POST /v1/article/` with a title of 200 repetitions of "😀" (200 runes, 800 bytes)
and a valid body -> status `201`, created, response JSON's `title` equals the sent
title.
12. Configure the fake repo (per step 1) so its next `Create` call returns
`article.ErrInvalidTitle` directly. `POST /v1/article/` with title "Valid Title"
and a valid body (both well inside 3-200 runes, so `validator.Validate.Struct`
passes and `CreateArticleUseCase`'s own check would also pass) -> status `400`,
body `apperr.ArticleTitleErrorMessage + "\n"`. This scenario is deliberately
artificial: with the rule aligned, no real HTTP input can make
`CreateArticleUseCase.CreateArticle`'s own check return `article.ErrInvalidTitle`
once `validator.Validate.Struct` has already accepted the same title, so the only
way to exercise `Handler.PostArticle`'s mapping branch today is to have the fake
repository hand back that sentinel directly, standing in for a future or
repository-level source of the same error.
13. Configure the fake repo to fail on its next `Create` with the generic
`errRepositoryFailure` (existing `failNext("Create")`). `POST /v1/article/` with
title "Valid Title" and a valid body -> status `500`, body `"Failed to create
article\n"` -- regression proof a non-title failure still answers 500.
14. `POST /v1/article/` with no jwt cookie -> status `401` (regression: the route is
still gated).
## Manual verification (not exercised by the Go test)
- None. This slice has no SPA-visible behaviour and no SMTP involvement.