# QA procedure: article_publishing Package: `test` (black-box, extending `article_test.go`). ## Setup shared by every case 1. Call `validator.InitValidator()` once. 2. Reuse `mockArticleRepo` (`test/article_test.go`) as-is; its `UpdateIsOnline` and `FindByFilters` already support everything this slice needs, no fake changes expected. 3. Wire the real use cases over the fake: `useCases.NewCreateArticleUseCase`, `useCases.NewGetArticleUseCase`, `useCases.NewListArticlesUseCase`, `useCases.NewDeleteArticleUseCase`, `useCases.NewUpdateArticleUseCase`. Build `article.NewHandler(...)` extended to also take the update use case (matching whatever `NewHandler` signature the coder lands on), exposing the new `PublishArticle` and `UnpublishArticle` methods. 4. Build a test `jwtauth.New("HS256", []byte("test-secret"), nil)`. 5. Mount the routes on a fresh `chi.NewRouter()` mirroring `api/router.go`'s current shape (per `public_article_listing.qa.md`): `GET /` under `jwtauth.Verifier` only; `GET /{id}`, `POST /`, `DELETE /{id}` under `jwtauth.Verifier` + `jwtauth.Authenticator`. Add `PATCH /{id}/publish` and `PATCH /{id}/unpublish` to that same authenticated group. 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 the relevant `httptest.NewRequest`. 7. Seed the repo directly via the map pattern already used elsewhere, 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}`. ## HTTP checks 8. `PATCH /v1/article/2/publish` with the valid cookie, repo seeded per step 7 -> status `200`; decode the JSON body and assert `id == 2` and `is_online == true`; assert `repo.articles[2].IsOnline` is now `true`. 9. `PATCH /v1/article/1/unpublish` with the valid cookie, repo seeded per step 7 -> status `200`; decode the JSON body and assert `id == 1` and `is_online == false`; assert `repo.articles[1].IsOnline` is now `false`. 10. `PATCH /v1/article/1/publish` with the valid cookie against an article already seeded `IsOnline: true` -> status `200`, body `is_online == true`, no error (idempotency). 11. `PATCH /v1/article/2/unpublish` with the valid cookie against an article already seeded `IsOnline: false` -> status `200`, body `is_online == false`, no error (idempotency). 12. `PATCH /v1/article/9999/publish` with the valid cookie, empty repo id `9999` -> status `404`, body `"Article not found\n"`. Repeat for `PATCH /v1/article/9999/unpublish` -> same assertions. 13. `PATCH /v1/article/not-a-number/publish` with the valid cookie -> status `400`, body matching `apperr.IdUint32ErrorMessage + "\n"`. Repeat for `.../unpublish`. 14. `PATCH /v1/article/1/publish` with **no** cookie, repo seeded with article id 1 -> status `401`. Repeat for `PATCH /v1/article/1/unpublish` with no cookie -> status `401`. 15. Integration with the public listing: seed repo with an offline article id 2, call `PATCH /v1/article/2/publish` with the valid cookie (assert `200`), then call `GET /v1/article/` with no cookie and assert the decoded JSON array contains an element with id `2`. 16. Mirror check: seed repo with an online article id 1, call `PATCH /v1/article/1/unpublish` with the valid cookie (assert `200`), then call `GET /v1/article/` with no cookie and assert the decoded JSON array does not contain an element with id `1`. ## Manual verification (not exercised by the Go test) - SPA-visible behaviour: confirm the back-office article editor exposes a publish/unpublish control that calls the new endpoints and updates the visible online/offline state, 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 such UI exists yet as of this slice — this note applies once the SPA is wired to the new endpoints.) - No SMTP involvement.