Files
RenewCMS/features/article_title_rules.qa.md
T

4.8 KiB

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)

  1. 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)

  1. Call validator.InitValidator().
  2. 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.
  3. 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).
  4. 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.
  5. POST /v1/article/ with a 3-ASCII-character title -> status 201, response body decodes to the created article JSON, is_online false.
  6. POST /v1/article/ with a 200-ASCII-character title (strings.Repeat("a", 200)) -> status 201, created.
  7. POST /v1/article/ with a 201-ASCII-character title -> status 400, Create never called.
  8. 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.
  9. 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.
  10. 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.
  11. 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.