Files
RenewCMS/features/upload_article_image.qa.md
T

4.2 KiB

QA procedure: upload_article_image

Package: test (black-box, extending article_test.go / image_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; both already implement AddImage / Create returning article.ErrArticleNotFound / image.ErrImageNotFound for an unknown id, per features/write_not_found_errors.feature.
  3. Wire the real use cases over the fakes: useCases.NewCreateArticleUseCase, useCases.NewGetArticleUseCase, useCases.NewListArticlesUseCase, useCases.NewUpdateArticleUseCase(articleRepo), useCases.NewDeleteArticleUseCase(articleRepo, imageRepo), useCases.NewCreateImageUseCase(imageRepo).
  4. Build article.NewHandler(...) with its existing constructor shape, and image.NewHandler(createImageUseCase, updateArticleUseCase) (api/handlers/image).
  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 shape after this slice: GET / under jwtauth.Verifier only; GET /{id}, POST /, DELETE /{id}, PATCH /{id}, PATCH /{id}/publish, PATCH /{id}/unpublish and the new POST /{id}/image (routed to imageHandler.PostImage) 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.
  8. Build multipart request bodies with mime/multipart.Writer, field name "file", setting the part's Content-Type header explicitly (Go's multipart writer defaults to sniffing otherwise, so set it via CreatePart with an explicit MIME header rather than CreateFormFile), and set the request's Content-Type header to the writer's FormDataContentType().

HTTP checks

  1. Seed articleRepo.articles[1] = article.Article{ID: 1, Title: "Backyard Renovation Update"}. Send POST /v1/article/1/image with the valid cookie and a multipart "file" part named "before.png", content type "image/png", some non-empty byte content. Assert status 201; decode the JSON body and assert location equals "/static/uploadedImages/before.png"; assert imageRepo.images gained exactly one entry whose Path matches that location; assert articleRepo.articles[1].Images now contains that image's id.
  2. Empty articleRepo, empty imageRepo. Send POST /v1/article/9999/image with the valid cookie and the same "before.png" / "image/png" part. Assert status 404, body "Article not found\n"; assert imageRepo.images is still empty (the article's existence is confirmed before any image is created, so nothing is left orphaned).
  3. Seed articleRepo.articles[1] = article.Article{ID: 1, Title: "Backyard Renovation Update"} with an empty Images slice. Send POST /v1/article/1/image with the valid cookie and a multipart "file" part named "notes.pdf", content type "application/pdf". Assert status 400, body "the file must be a PNG, JPEG, WEBP, or SVG image\n"; assert imageRepo.images is still empty; assert articleRepo.articles[1].Images is still empty.
  4. Seed the same article id 1 as step 11. Send POST /v1/article/1/image with no cookie and the "before.png" / "image/png" part. Assert status 401; assert imageRepo.images is still empty; assert articleRepo.articles[1].Images is still empty.

Manual verification (not exercised by the Go test)

  • Real upload against a real SQLite file and real UPLOAD_DIR: confirm that after POST /v1/article/{id}/image on a real running instance, the file actually lands on disk under UPLOAD_DIR, the images row's path resolves to a URL the SPA can load, and the article_images join table gets the new row. 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 editor (if/when an image-upload UI exists) shows the uploaded image at that location. No such UI exists yet as of this slice.
  • No SMTP involvement.