Files
RenewCMS/features/image_variants_per_image_api.qa.md
T

8.4 KiB

QA procedure: image_variants_per_image_api

Go only, no SPA. Package test (black-box). New files: test/image_variants_per_image_http_test.go, test/image_variants_generate_on_upload_test.go, test/media_variants_list_http_test.go, test/image_variants_per_image_audit_test.go. Extend test/image_variants_support_test.go; fake type names must stay unique in the package. No live server, no real SQLite file, no real SMTP, no time.Sleep.

Setup shared by every case

  1. validator.InitValidator() once. jwtauth.New("HS256", []byte("test-secret"), nil) stands in for JWT_SECRET.
  2. Reuse the fakes of test/image_variants_support_test.go: mockVariantRepo, mockVariantFileRepo, mockVariantStorage, countingRenderer. Add to mockVariantFileRepo the per-source totals method (rows and bytes by source stored name) with a call counter. Reuse mockImageRepo (width, height, size, content type, SHA256, created_at seeded), mockArticleRepo, the user fake and the audit fakes of test/activity_support_test.go.
  3. Users: owner 1, admin 2, verified plain user 3 (role other than owner/admin), unverified user 4. Real signed jwt cookies with user_id and token_version; a stale cookie with a lower token_version; a valid API key in the key fake.
  4. Router: a fresh chi.NewRouter() mirroring NewMediaRouter in api/router.go: jwtauth.Verifier, the API-key middleware, AuditContext, Authenticator, RequireCurrentToken, RequireVerifiedUser, then the owner/admin group holding the new routes beside the existing purge routes and the ungrouped media routes. Mount at /v1/media. Mount the real public handler staticimages.NewHandler(...) at /static/uploadedImages over the same storage and variant use cases so a generated file is observed through the public url.
  5. Images are generated in the test with image/png, image/jpeg, image/draw (as in the other variant tests). The SVG is a real string. Seed the Listing library of features/media_library.feature for the list cases and the numbers of the Rule's table.

A. Guards

  1. Table of the feature: each method, path and credential gives the status and the exact body constant; assert no variant, index, storage or audit fake was touched. API key gets 401. Owner and admin both get 200. Malformed and unknown ids: 400 apperr.IdUint32ErrorMessage, 404 Image not found.

B. Listing the variants of one image

  1. Seed the Rule's Background. Decode the body and assert order (name), the absence of the disabled variant, width/height as JSON null on the raw body, output size and format, generated, size (null when not generated), url. Stale fingerprint row: generated false. Disabled variant's row ignored. SVG: raw body {"unsupported":"svg","items":[]}. No enabled variant: raw "items":[].
  2. Output format Examples: countingRenderer.EncodableFormats() configured for webp on and off; a webp source with original gives png without an encoder.
  3. Geometry table: for each row assert the body's output_width/output_height and then render the same source and settings with the real renderer and assert the decoded size equals them (one source of truth).
  4. Listing renders nothing: renderer count 0, storage Open/Put counts 0, no audit event.

C. Generating one

  1. First generation: 200, item fields, exactly one Put, one index row whose size equals the item size, original blob untouched, no audit event. A public GET afterwards returns the same bytes with the renderer count still 1. A second POST: 200, renderer count unchanged.
  2. Concurrency (run with -race): a renderer blocking on a channel; one admin POST and one public GET for the same pair released together; assert renderer count 1 and Put count 1.
  3. Refusals: unknown, disabled and wrongly-cased variant names give 404 Variant not found with renderer count 0. SVG gives 422 with the constant. Byte cap and pixel cap give 413 (construct the use case with the low cap). Undecodable bytes give 415 with the constant. A renderer failure gives 500 Internal server error, no blob, no row, and the next call is 200.

D. Generating all

  1. Fresh image: 200, generated 5, failed 0, failures raw [], five items all generated, five blobs and rows, print untouched. Pre-generated subset: counts only the newly generated, renderer count equals the missing ones.
  2. Failure Examples: make the renderer fake return ErrSourceTooLarge, ErrVariantSourceUnreadable and a plain error for hero only; assert the status 200, failures, the causes and the four others generated. Nothing to generate, no enabled variant and SVG (422) rows.

E. Purging one

  1. 200 with purged_files 1 and purged_bytes equal to the seeded size; blob Delete called for that name only; other variant and the other image untouched; a following public GET renders again. Not generated and disabled-variant rows; unknown variant 404. Storage Delete failing gives 500 and the row is kept.

F. Generate on upload

  1. Upload through the real upload path with the Rule's four variants: after the 201, assert rows for thumb and hero only, variant_count 2 and variant_size equal to the sum, exactly one audit event (media.upload). Renderer failing for hero: 201, one row, the log line (capture with a test logger handed to the use case, assert it names the variant and the image id). Pixel cap: 201 and zero rows. SVG, no flagged variant, and 415 upload: renderer count 0. Creating a variant or a PUT with the flag on does not render for existing images. Same bytes uploaded twice: separate rows per stored name.

G. Library list

  1. Seed the Rule's table. variant_count/variant_size per item (raw JSON integers, 0 not null). Per-source totals fake call count is 1 per list call. variants=with|without sets; counts unchanged; combination rows; sort=total_size order 1,4,2,3,5, sort=largest order 1,2,3,4,5; tie breaks by id (make two totals equal); cursor walk with limit=2; empty cache with variants=with; every 400 row with apperr.MediaQueryErrorMessage. Rename response carries the fields.
  2. Delete consistency: seed four rows and four blobs for image 1, DELETE, assert exactly those four Delete calls and rows. Usage and merge rows with a ?v=hero suffix in the article body.

H. Audit

  1. Generate-all success, partial failure (causes), all-failed (failed outcome and exact reason), the no-op rows (all generated, no enabled variant, SVG, single generate, unknown image, GET, public generation, upload generation). Purge-by-image success params, the zero rows, the storage-failure row (500 plus failed event with the generic reason). A failing audit repository leaves response and files unchanged. GET /v1/activity?category=media lists both new events. Extend the mapping test of failureReasons for the three new sentences.

I. Mutation guard

  1. task mutate scope is ./internal/infrastructure/useCases, scoped with MUTATE_FILES to the new use case files and MUTATE_RUN to the new test names. Each new branch needs a killing assertion: the generated-flag fingerprint comparison (equal and differing), the generate_on_upload AND enabled condition (each alone false), skipped already-generated variants, the failure-cause mapping order, generated > 0 versus failed > 0 for the outcome, the purged_files > 0 guard, the total-size sum (original plus variants, not variants alone) and its id tie-break. Bare-bool conditions produce no mutant, so cover both branches anyway.

J. Layering and hygiene

  1. Guard test (go list -deps or grep): the domain imports only the stdlib and domain packages; the new use cases import no persistence, mailer, models, gorm, net/http, chi or api; the media handler imports no persistence, mailer, models or gorm. CGO_ENABLED=0 go build -tags nofrontend ./... succeeds. No comment in new or edited Go files, tests or docs. docs/api.md lists the new routes, shapes, params and errors.

Manual (not automated)

  1. task dev:api over a scratch SQLite file and UPLOAD_DIR: upload an image with a flagged variant and check the file lands in UPLOAD_DIR; then use the generate-all and purge routes with curl and a real cookie and check the files appear and disappear.
  2. S3 driver (STORAGE_DRIVER=s3, MinIO): per-image generate and purge, upload with a flagged variant.
  3. Latency of an upload of a 10 MB image with five flagged variants.
  4. Read the two new events in the real Activity screen against section 8 copy.