4.4 KiB
4.4 KiB
QA procedure: delete_article_cascades_images
Package: test (black-box, extending article_test.go).
Setup shared by every case
- Call
validator.InitValidator()once. - Reuse
mockArticleRepo(test/article_test.go) andmockImageRepo(test/image_test.go) as-is. ExtendmockArticleReposeeding so a seeded article'sImages []*image.Imagecan hold pointers into the samemockImageRepo.imagesmap used by the test, so deleting through the article side and asserting through the image side observe the same state. - Add a call counter (or reuse the existing
failSwitch/call-tracking pattern already intest/) onmockImageRepo.Deleteso a scenario can assert it was never called, and reusemockImageRepo.fail(the existingfailSwitch) to makeDeletefail for a specific image id. - Wire the real use cases over the fakes:
useCases.NewCreateArticleUseCase,useCases.NewGetArticleUseCase,useCases.NewListArticlesUseCase,useCases.NewUpdateArticleUseCase, and the new two-argumentuseCases.NewDeleteArticleUseCase(articleRepo, imageRepo). Buildarticle.NewHandler(...)with the same constructor shape it already has (the use case's internal dependency count changes, not the handler's). - Build a test
jwtauth.New("HS256", []byte("test-secret"), nil). - Mount the routes on a fresh
chi.NewRouter()mirroringapi/router.go's current shape:GET /underjwtauth.Verifieronly;GET /{id},POST /,DELETE /{id},PATCH /{id},PATCH /{id}/publish,PATCH /{id}/unpublishunderjwtauth.Verifier+jwtauth.Authenticator. - Mint a valid jwt cookie for user id 1:
token.Encode(map[string]any{"user_id": uint32(1)}), set as thejwtcookie on the relevanthttptest.NewRequest.
HTTP checks
- Seed
imageRepo.images[10]andimageRepo.images[11], then seedarticleRepo.articles[1] = article.Article{ID: 1, Title: "Backyard Renovation Update", Images: []*image.Image{imageRepo.images[10], imageRepo.images[11]}}(pointers into the same map). SendDELETE /v1/article/1with the valid cookie -> status204; assertarticleRepo.articlesno longer has key1; assertimageRepo.imagesno longer has keys10or11. - Seed
articleRepo.articles[2] = article.Article{ID: 2, Title: "Draft: Kitchen Remodel"}with a nil/emptyImagesslice. SendDELETE /v1/article/2with the valid cookie -> status204; assertarticleRepo.articlesno longer has key2; assert the image-delete call counter is0. - Empty
articleRepo, emptyimageRepo. SendDELETE /v1/article/9999with the valid cookie -> status404, body"Article not found\n"; assert the image-delete call counter is0. - Seed
imageRepo.images[10]andarticleRepo.articles[1]withImagesholding image id 10, per step 8's pattern. SendDELETE /v1/article/1with no cookie -> status401; assertarticleRepo.articles[1]is still present andimageRepo.images[10]is still present. - Seed
imageRepo.images[20]andimageRepo.images[21], then seedarticleRepo.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). ArmimageRepo.failsoDeletereturns an error only when called with id20(leave id21unarmed). SendDELETE /v1/article/3with the valid cookie -> status500, body"Failed to delete article\n"; assertarticleRepo.articles[3]is still present; assertimageRepo.images[21]is still present (never reached because the delete aborted on image20first).
Manual verification (not exercised by the Go test)
- Real cascading delete against a real SQLite file and real
UPLOAD_DIR: confirm that afterDELETE /v1/article/{id}on a real running instance, the uploaded image files underUPLOAD_DIRfor that article are gone from disk and theimagestable no longer holds those rows, and that no orphaned rows are left behind in thearticle_imagesjoin table. Not exercised bytask test(fakes only); requirestask devor a builttask buildbinary 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.