feat(API): media library
This commit is contained in:
1 parent
95ae043d3e
commit
0d99544579
65 files changed
+3992
-1530
No files matched your search
@@ -4,70 +4,18 @@ 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()`.
|
||||
2. Wire `image.NewHandler(createImageUseCase, getArticleUseCase)` (no update or delete use cases) and mount only `POST /{id}/image` in the article group, behind `Verifier`, `Authenticator`, `RequireCurrentToken`, `RequireVerifiedUser`.
|
||||
3. Use real image bytes generated in the test, as in `media_library.qa.md` step 8, and multipart bodies with an explicit part `Content-Type`.
|
||||
|
||||
## HTTP checks
|
||||
9. 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.
|
||||
10. 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).
|
||||
11. 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.
|
||||
12. 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.
|
||||
4. Valid PNG on article 1 -> 201 `{id, location}` matching the created image, and `articleRepo.articles[1]` has an unchanged body.
|
||||
5. Unknown article 9999 -> 404 `"Article not found\n"`, `Create` never called.
|
||||
6. Rejected files: `application/pdf` -> 415, PNG padded to 10485761 bytes -> 413, text bytes as `image/png` -> 400; `Create` never called.
|
||||
7. No cookie -> 401, `Create` never called.
|
||||
8. `DELETE /v1/article/1/image/10` on this router -> 405, image 10 still exists.
|
||||
|
||||
## 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.
|
||||
## Cleanup
|
||||
Delete `test/remove_article_image_http_test.go` and update `test/upload_article_image_http_test.go` to the cases above.
|
||||
|
||||
## Manual verification
|
||||
With a real instance, `POST /v1/article/{id}/image` writes a file to `UPLOAD_DIR` and a row visible in `GET /v1/media/`, and no article association is written.
|
||||
Reference in new issue
Block a user