refac: deleting an article also deletes its images, and deleting a missing article returns 404

This commit is contained in:
fsylvain committed 2026-09-23 23:42:18 +02:00
1 parent c16fdbd26d
commit 8a8a24434a
13 files changed
+414 -18

No files matched your search

@@ -0,0 +1,67 @@
# QA procedure: delete_article_cascades_images
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`) and `mockImageRepo`
(`test/image_test.go`) as-is. Extend `mockArticleRepo` seeding so a seeded article's
`Images []*image.Image` can hold pointers into the same `mockImageRepo.images` map used
by the test, so deleting through the article side and asserting through the image side
observe the same state.
3. Add a call counter (or reuse the existing `failSwitch`/call-tracking pattern already in
`test/`) on `mockImageRepo.Delete` so a scenario can assert it was never called, and reuse
`mockImageRepo.fail` (the existing `failSwitch`) to make `Delete` fail for a specific
image id.
4. Wire the real use cases over the fakes: `useCases.NewCreateArticleUseCase`,
`useCases.NewGetArticleUseCase`, `useCases.NewListArticlesUseCase`,
`useCases.NewUpdateArticleUseCase`, and the new two-argument
`useCases.NewDeleteArticleUseCase(articleRepo, imageRepo)`. Build `article.NewHandler(...)`
with the same constructor shape it already has (the use case's internal dependency count
changes, not the handler's).
5. Build a test `jwtauth.New("HS256", []byte("test-secret"), nil)`.
6. Mount the routes on a fresh `chi.NewRouter()` mirroring `api/router.go`'s current shape:
`GET /` under `jwtauth.Verifier` only; `GET /{id}`, `POST /`, `DELETE /{id}`,
`PATCH /{id}`, `PATCH /{id}/publish`, `PATCH /{id}/unpublish` under `jwtauth.Verifier` +
`jwtauth.Authenticator`.
7. 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`.
## HTTP checks
8. Seed `imageRepo.images[10]` and `imageRepo.images[11]`, then seed
`articleRepo.articles[1] = article.Article{ID: 1, Title: "Backyard Renovation Update",
Images: []*image.Image{imageRepo.images[10], imageRepo.images[11]}}` (pointers into the
same map). Send `DELETE /v1/article/1` with the valid cookie -> status `204`; assert
`articleRepo.articles` no longer has key `1`; assert `imageRepo.images` no longer has
keys `10` or `11`.
9. Seed `articleRepo.articles[2] = article.Article{ID: 2, Title: "Draft: Kitchen Remodel"}`
with a nil/empty `Images` slice. Send `DELETE /v1/article/2` with the valid cookie ->
status `204`; assert `articleRepo.articles` no longer has key `2`; assert the image-delete
call counter is `0`.
10. Empty `articleRepo`, empty `imageRepo`. Send `DELETE /v1/article/9999` with the valid
cookie -> status `404`, body `"Article not found\n"`; assert the image-delete call
counter is `0`.
11. Seed `imageRepo.images[10]` and `articleRepo.articles[1]` with `Images` holding image id
10, per step 8's pattern. Send `DELETE /v1/article/1` with **no** cookie -> status `401`;
assert `articleRepo.articles[1]` is still present and `imageRepo.images[10]` is still
present.
12. Seed `imageRepo.images[20]` and `imageRepo.images[21]`, then seed
`articleRepo.articles[3] = article.Article{ID: 3, Title: "Kitchen Progress Photos",
Images: []*image.Image{imageRepo.images[20], imageRepo.images[21]}}` (image 20 before
image 21 in the slice). Arm `imageRepo.fail` so `Delete` returns an error only when
called with id `20` (leave id `21` unarmed). Send `DELETE /v1/article/3` with the valid
cookie -> status `500`, body `"Failed to delete article\n"`; assert
`articleRepo.articles[3]` is still present; assert `imageRepo.images[21]` is still
present (never reached because the delete aborted on image `20` first).
## Manual verification (not exercised by the Go test)
- Real cascading delete against a real SQLite file and real `UPLOAD_DIR`: confirm that after
`DELETE /v1/article/{id}` on a real running instance, the uploaded image files under
`UPLOAD_DIR` for that article are gone from disk and the `images` table no longer holds
those rows, and that no orphaned rows are left behind in the `article_images` join table.
Not exercised by `task test` (fakes only); requires `task dev` or a built `task build`
binary with a scratch DB/upload dir.
- SPA-visible behaviour: confirm the back-office article deletion flow (if/when one exists)
reflects that any images shown for that article are gone too. No such UI exists yet as of
this slice.
- No SMTP involvement.