Files
RenewCMS/features/articles_pagination_api.qa.md

7.0 KiB

QA: Paginated article listing (API)

HTTP-level (Go, test/, task test)

Build article.NewHandler(...) over the real ListArticlesUseCase and a fake article.Repository (extend mockArticleRepo in test/article_test.go, keeping type names unique across files). The fake implements the interface contract: paging (Page/Size 0 = unpaged), q/status filtering, ordering updated_at DESC then id DESC, total, and the visibility counts. Mount on a fresh chi.NewRouter() mirroring NewArticleRouter in api/router.go (GET / under jwtauth.Verifier only, test jwtauth.New("HS256", ...)). Call validator.InitValidator(). Use a user fake for the verified, unverified and missing cases, as in test/public_article_listing_test.go. No live server, no SQLite file.

Helper: decode the body into {items []map, total, page, size int, counts {all, online, draft}} and also assert the raw JSON contains "items":[ (never null).

  1. Bare request: 30 online articles, GET /v1/article/ -> object (not array), page 1, size 25, total 30, 25 items, counts 30/30/0.
  2. Defaults table (page=2, page=3, size=5, size=50) on 60 articles.
  3. Order: ids 3,2,1 for the equal-timestamp case with no sort parameter (default updated_at:desc). Sort table: the 8 whitelisted values on the mixed-case fixture (apple/Banana/Cherry), asserting the item ids. The fake repo implements the same contract: key, direction, then id desc. 3a. Equal titles with sort=title:asc -> ids 3,2,1. 3b. Sort before paging: 12 articles, ?sort=title:desc&size=5, three pages, no overlap. 3c. Sort + q + status combination: ordered titles, total 3, counts unchanged. 3d. Use-case test: ListArticlesUseCase passes the typed sort through to Filters and defaults to updated_at desc when absent. Only whitelisted values reaching Order in the repository is a code-review item; the handler test covers the 400 side.
  4. Partition: 12 articles, pages 1-3 of size 5 -> 5,5,2 and 12 distinct ids.
  5. Public: 5 online + 3 offline -> total 5, counts 5/5/0, no offline item.
  6. Verified: same data -> total 8, counts 8/5/3, three offline items.
  7. Malformed cookie and unverified user -> same as public.
  8. Out of range: ?page=99&size=5 -> 200, raw "items":[], total 12, counts.all 12.
  9. Empty repository -> items [], total 0, counts 0/0/0.
  10. Status table (verified): online -> total 4, draft -> total 2, counts always 6/4/2.
  11. Public status=draft -> items [], total 0, counts 4/4/0.
  12. q=PLÂSTER (accent and case insensitive) -> total 1, counts.all 2. q=zzzz -> empty, counts unchanged.
  13. q+status+page combination: total 7, 2 items on page 2, counts.draft 7.
  14. Invalid table (all rows incl. page=, page=1.5, status=archived, status=, 201-char q, and the 7 sort rows: sort=title, sort=title:up, sort=body:asc, sort=, sort=updated_at, sort=title:asc;DROP TABLE, sort=TITLE:ASC): each -> 400 with the exact BodyErrorMessage text, and the fake's FindByFilters/count counters stay 0.
  15. ?page=0&format=pdf -> 400 with the format message.
  16. ?size=5&format=html -> every item body_format "html".
  17. Fake repo failure (existing failNext("FindByFilters")) -> 500 "Failed to retrieve articles".
  18. Use-case tests: the paged list method returns items, total and counts, propagates a repo error (errors.Is on a sentinel), and the unpaged Filters{} call used by purgePlan.go and ListMedia.go still returns every article (existing tests keep passing).
  19. Existing tests that decode GET /v1/article/ as an array (test/public_article_listing_test.go, test/article_test.go, test/use_case_baseline_test.go, features/articles_list.qa.md item 1) are amended to the envelope. List them in the diff.

counts.suggestions (Go, test/, task test)

Build article.NewHandler(...) over the real use cases and the extended mockArticleRepo, as in the section above. The fixture articles carry bodies that yield suggestions, hidden (noindex) or ready SEO status and a seo_dismissed list. Mount on a fresh chi.NewRouter() that mirrors NewArticleRouter and call validator.InitValidator().

  1. Counts table: 4 online and 2 draft with 3 suggestions gives counts exactly {all:6, online:4, draft:2, suggestions:3}. Decode into a map and assert the key set is exactly those four.
  2. Zero case: 5 clean articles gives suggestions 0. Empty library gives all four at 0.
  3. Equality: 30 articles, 12 with suggestions; GET /?seo=suggestions gives total 12 and counts.suggestions 12.
  4. Independence table: q=tile, status=draft, site=journal, seo=ready, seo=hidden, sort=title:asc, size=5&page=99 all give 3.
  5. Exclusions: a noindex article with suggestions and an article whose only suggestion is dismissed are not counted; one with two suggestions of which one is dismissed is counted once.
  6. Visibility: a verified caller counts an online and a draft article (2); an anonymous caller and an unverified caller count the online one only, with draft 0.
  7. Repository failure while evaluating gives 500 "Failed to retrieve articles" and no partial body.
  8. Regression: the existing assertions on total, page, size, counts.all/online/draft and items stay green without edits.
  9. Docs, read by hand: docs/api/articles.md (example JSON gets "suggestions", and the counts bullet says global, visibility-scoped, noindex and dismissed excluded, equal to the unfiltered seo=suggestions total); web/src/data/apiCatalogue.ts list response fields with its spec and IntegrationView.spec.ts counts; docs/release-notes.md line.
  10. Scoped mutation through task mutate only: MUTATE_FILES=ListArticles.go MUTATE_RUN='TestArticle.*Count' MUTATE_WORKERS=2 task mutate -- ./internal/infrastructure/useCases, plus any other file touched by the change. Re-check LIVED or NOT COVERED suspects without MUTATE_RUN. Then task lint:go and task fmt.

Docs (verified by reading the diff)

  • docs/api/articles.md: new "Pagination, search, sort and counts" section (parameters including sort values, default and id tie-break, envelope, ordering, errors) and a "Breaking change" note, like the existing format note: GET /v1/article/ no longer returns a bare array; read items.
  • web/src/data/apiCatalogue.ts art-list: add page, size, q, status, sort query entries, the 400 error, and a paged JSON example; the Integration specs that assert this catalogue are amended.
  • docs/design/screens/integration.md is read-only for agents: flag that its text "GET /v1/article/" example needs the user's update.
  • A release note under docs/releases/ is the maintainer's call: flag it.

Manual verification

  • Real SQLite file via task dev: seed 60 articles, check ?page=2&size=25, ordering, q accent-insensitivity, total and counts. MySQL and PostgreSQL parity of the COUNT / LIMIT-OFFSET / LIKE queries is manual (no live DB in tests).
  • Sort by title on a live SQLite file, and on MySQL/PostgreSQL if available: title collation differs (binary, case-insensitive, locale), so mixed-case and accented ordering may differ between databases. Accepted; note any difference.
  • An anonymous curl against a running server returns the envelope.