Files
RenewCMS/features/media_library.qa.md
T

6.1 KiB

QA procedure: media_library

Package: test (black-box). New file test/media_library_http_test.go. Fake type names must be unique in the package.

Setup shared by every case

  1. Call validator.InitValidator() once.

  2. Rewrite mockImageRepo (test/image_test.go) to the new image.Repository: Create (stores the metadata from the ImageInput and derives a /static/uploadedImages/<n>.<ext> path), Get, Delete (records calls, honours failDeleteFor), List(query), FindByHash, Rename, ListByHash groups, Counts. Add fields for seeding Filename, Size, ContentType, Width, Height, SHA256, UploadedByID, CreatedAt.

  3. Extend mockArticleRepo (test/article_test.go) with FindByBodyContaining(filename) (substring over Body) and a per-id failure switch for UpdateBody. Remove AddImage and the article Images field from the fake and from every test that uses them.

  4. Use the existing user fake so uploaded_by resolves: user 1 display name "Inès Duarte", user 2 username "marta" with empty display name.

  5. Wire the real use cases over the fakes: create, list, rename, delete, merge and usage media use cases, NewCreateImageUseCase(imageRepo), NewGetUserUseCase(userRepo). Build media.NewHandler(...) (api/handlers/media).

  6. Build jwtauth.New("HS256", []byte("test-secret"), nil). Mount on a fresh chi.NewRouter() mirroring api/router.go: jwtauth.Verifier at the top, then in the media group Authenticator, RequireCurrentToken(getUser), RequireVerifiedUser(getUser), with the six routes under /v1/media.

  7. Mint cookies via token.Encode(map[string]any{"user_id": uint32(n), ...}) as in the other HTTP tests. Build a stale-token cookie with a token_version lower than the user's, and use an unverified user for the 403 case.

  8. Build multipart bodies with mime/multipart.Writer, using CreatePart with an explicit part Content-Type header. Generate real bytes in the test:

    • PNG (3x2) with image/png;
    • JPEG (3x2) with image/jpeg;
    • a tiny valid WEBP fixture (1x1) as a byte constant;
    • SVG <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>.

    Compute expected sha256 in the test with crypto/sha256. For size limits, pad valid PNG bytes to 10485760 and 10485761 bytes.

  9. Seed the Listing background exactly as in the feature (5 images, 3 articles, 2 users), with created_at strictly increasing by id.

HTTP checks

  1. Auth table: each method+path from the outline with no cookie -> 401, stale cookie -> 401 with apperr.UnauthorizedErrorMessage, unverified cookie -> 403 with apperr.VerificationRequiredErrorMessage. Assert no fake method beyond the middleware's user lookup was called.
  2. List (amended by features/media_pagination_api.feature): decode {items, total, page, size, counts}, with no next_cursor. Assert each row of the sort, filter, q and hash tables, the combined query, the paging walk (size=5 across three pages with page=1..3, each reporting the same total), the empty library (items is [] in the raw JSON, not null), SVG width/height are JSON null (assert on the raw body), and counts is unchanged by q/filter. Assert every 400 query row, body equals apperr.MediaQueryErrorMessage + "\n".
  3. Upload: for each allowed type assert 201, the item fields, sha256 equals the test's own hash, uploaded_by, the url regex, and one repo entry with UploadedByID == 1. Upload identical bytes twice: both 201, second has duplicate: true, list counts.duplicates == 1. Assert filename rules (no extension gets .png, path stripped). Assert 413/415/400 statuses and bodies, that Create is never called on rejection, boundary 10485760 vs 10485761, a missing file part -> 400, and an armed Create failure -> 500 "Internal server error\n".
  4. Rename: assert 200 with the new filename, the same url, the article body unchanged, and every invalid row -> 400 with "Enter a filename.\n" for the filename rows; 404 for 9999; 400 with apperr.IdUint32ErrorMessage for abc. Assert the repository filename is unchanged after each 400.
  5. Delete: in-use image -> 204 with an empty body, article bodies byte-identical, deleteCalls == [1]; 404, 400, 500 rows. The "file already gone" case is a persistence behaviour and is a manual item.
  6. Usage: assert the exact JSON arrays (raw body compared as JSON), the empty [], the absolute-url article, 404 and 400.
  7. Merge: assert 200 {articles_updated, merged}, resulting bodies (both occurrences replaced), unchanged bodies for the other articles, and only image 2 left. Assert the 409 rows and 404 row, with no body change and no Delete calls (deleteCalls empty). Assert every 400 row with apperr.MediaMergeRequestErrorMessage. Assert the two failure rows: UpdateBody failing for article 2 leaves article 1 restored and no Delete call; Delete failing for image 3 leaves bodies switched and returns 500.
  8. Unit-level asserts (same file, no HTTP) with errors.Is on the sentinels ErrImageUnreadable, ErrMergeCopyMissing, ErrMergeHashMismatch, ErrInvalidFilename.
  9. Layering guard: go list -deps or a grep-style test that api/handlers/media imports no persistence, mailer, models or gorm package.

Manual verification (not exercised by task test)

  • Real upload, rename, delete, merge against a scratch SQLite file and UPLOAD_DIR via task dev: the files land on disk, url loads through /static/uploadedImages/, deleting an in-use image leaves the article body text untouched, and a merge removes the copy files.
  • Delete when the file was already removed from UPLOAD_DIR still answers 204.
  • Startup backfill: copy an old database (rows with only path), boot, and check that filename, size, content_type, width, height and sha256 are filled; check idempotency on a second boot; check that a row whose file is missing does not stop the boot; check that the article_images table and article_id column are still present and not written.
  • UploadedByID 0 rows display an empty uploaded_by.
  • WEBP dimensions with a real WEBP file (the fixture is minimal).
  • No SMTP involvement. No real rate limit (out of scope for this slice).