refac: authenticated user can edit an article's body; an unknown id returns 404

This commit is contained in:
fsylvain committed 2026-09-23 23:24:03 +02:00
1 parent 8822bdddd0
commit c16fdbd26d
5 files changed
+425 -9

No files matched your search

+103
View File
@@ -0,0 +1,103 @@
Feature: An authenticated user can edit an article's body
As an authenticated RenewCMS user managing the CMS
I want to change an existing article's body from an HTTP endpoint
So that a published or draft article's content can be corrected or updated without
deleting and recreating it
Background:
Given internal/domain/article.Repository already exposes
UpdateBody(id uint32, body string) (Article, error), and
UpdateArticleUseCase.UpdateBody delegates to it unchanged
And UpdateArticleUseCase is already built in api/dependecyInjection.go as
container.UpdateArticleUseCase, but before this slice no handler or route calls
UpdateBody
And this slice adds Handler.UpdateArticleBody, backed by
UpdateArticleUseCase.UpdateBody(id, body)
And this slice mounts PATCH /v1/article/{id} inside the same jwtauth Verifier +
Authenticator group as GET /v1/article/{id}, POST /v1/article/,
DELETE /v1/article/{id}, PATCH /v1/article/{id}/publish and
PATCH /v1/article/{id}/unpublish in api/router.go, so it also requires a valid
jwt cookie
And the request body is JSON {"body": "..."}, validated with
validate:"required,max=10000" on the Body field, the same tag PostArticle.Body
already carries
And a successful edit answers 200 with the JSON body of the updated article.Article,
the same shape GetArticle, PostArticle, PublishArticle and UnpublishArticle
already return; the article's title, is_online flag and images are unchanged
And an id that does not parse as a uint32 answers 400 with body
apperr.IdUint32ErrorMessage, the same as GetArticle, DeleteArticle,
PublishArticle and UnpublishArticle
And a body that is not valid JSON answers 400 with body apperr.BodyErrorMessage,
the same as PostArticle
And a body that fails its own required/max=10000 validation answers 400 with the
raw validator error text, the same as PostArticle already does for its own
fields; no new apperr message is introduced for this
And an id absent from the article repository answers 404 with body
apperr.ArticleNotFoundErrorMessage, backed by
errors.Is(err, article.ErrArticleNotFound), the same as DeleteArticle,
PublishArticle and UnpublishArticle already do per
features/write_not_found_errors.feature
And this slice does not add any way to change an article's title after creation,
per features/article_title_rules.feature
Scenario: Editing an existing article's body updates only the body
Given a valid jwt cookie for a user with id 1
And the article repository contains an online article titled
"Backyard Renovation Update" with id 1 and body "Old body text"
When the client sends PATCH /v1/article/1 with that cookie and body
{"body": "New body text"}
Then the response status is 200
And the response body is the article with id 1, "body": "New body text",
"title": "Backyard Renovation Update" and "is_online": true
Scenario: Editing an offline article's body leaves it offline
Given a valid jwt cookie for a user with id 1
And the article repository contains an offline article titled
"Draft: Kitchen Remodel" with id 2 and body "Draft notes"
When the client sends PATCH /v1/article/2 with that cookie and body
{"body": "Revised draft notes"}
Then the response status is 200
And the response body is the article with id 2, "body": "Revised draft notes"
and "is_online": false
Scenario: Editing an unknown article answers 404
Given a valid jwt cookie for a user with id 1
And the article repository has no article with id 9999
When the client sends PATCH /v1/article/9999 with that cookie and body
{"body": "New body text"}
Then the response status is 404
And the response body is the text "Article not found"
Scenario: A malformed id answers 400 before any repository call
Given a valid jwt cookie for a user with id 1
When the client sends PATCH /v1/article/not-a-number with that cookie and body
{"body": "New body text"}
Then the response status is 400
And the response body is the text apperr.IdUint32ErrorMessage
Scenario: A non-JSON request body answers 400 with the body error message
Given a valid jwt cookie for a user with id 1
And the article repository contains an article with id 1
When the client sends PATCH /v1/article/1 with that cookie and a body that is not
valid JSON
Then the response status is 400
And the response body is the text apperr.BodyErrorMessage
Scenario Outline: A body value that fails validation answers 400
Given a valid jwt cookie for a user with id 1
And the article repository contains an article with id 1
When the client sends PATCH /v1/article/1 with that cookie and body <payload>
Then the response status is 400
Examples:
| payload |
| {"body": ""} |
| {"body": "<10001-character string>"} |
Scenario: Editing an article requires authentication
Given no jwt cookie is sent
And the article repository contains an article with id 1
When the client sends PATCH /v1/article/1 without a cookie and body
{"body": "New body text"}
Then the response status is 401
+58
View File
@@ -0,0 +1,58 @@
# QA procedure: edit_article_body
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 `UpdateBody` already
supports 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(...)` (existing signature, already takes the update use case),
exposing the new `UpdateArticleBody` method.
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 `article_publishing.qa.md`): `GET /` under `jwtauth.Verifier` only;
`GET /{id}`, `POST /`, `DELETE /{id}`, `PATCH /{id}/publish`,
`PATCH /{id}/unpublish` under `jwtauth.Verifier` + `jwtauth.Authenticator`. Add
`PATCH /{id}` 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", Body: "Old body text", IsOnline: true}`
and `repo.articles[2] = article.Article{ID: 2, Title: "Draft: Kitchen Remodel", Body: "Draft notes", IsOnline: false}`.
## HTTP checks
8. `PATCH /v1/article/1` with the valid cookie and JSON body `{"body": "New body text"}`,
repo seeded per step 7 -> status `200`; decode the JSON response and assert
`id == 1`, `body == "New body text"`, `title == "Backyard Renovation Update"`,
`is_online == true`; assert `repo.articles[1].Body` is now `"New body text"` and
`repo.articles[1].Title`/`IsOnline` are unchanged.
9. `PATCH /v1/article/2` with the valid cookie and JSON body
`{"body": "Revised draft notes"}`, repo seeded per step 7 -> status `200`; decode
the JSON response and assert `id == 2`, `body == "Revised draft notes"`,
`is_online == false`.
10. `PATCH /v1/article/9999` with the valid cookie and a valid JSON body, empty repo
id `9999` -> status `404`, body `"Article not found\n"`.
11. `PATCH /v1/article/not-a-number` with the valid cookie and a valid JSON body ->
status `400`, body matching `apperr.IdUint32ErrorMessage + "\n"`.
12. `PATCH /v1/article/1` with the valid cookie and a malformed JSON body (e.g. raw
string `not-json`), repo seeded with article id 1 -> status `400`, body matching
`apperr.BodyErrorMessage + "\n"`.
13. `PATCH /v1/article/1` with the valid cookie and JSON body `{"body": ""}`, repo
seeded with article id 1 -> status `400`; assert `repo.articles[1].Body` is
unchanged (nothing stored).
14. `PATCH /v1/article/1` with the valid cookie and JSON body `{"body": "<10001-char
string>"}`, repo seeded with article id 1 -> status `400`; assert
`repo.articles[1].Body` is unchanged.
15. `PATCH /v1/article/1` with **no** cookie, repo seeded with article id 1 -> status
`401`.
## Manual verification (not exercised by the Go test)
- SPA-visible behaviour: confirm the back-office article editor exposes a body-editing
control that calls `PATCH /v1/article/{id}` and reflects the updated body, 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.)
- No SMTP involvement.