8.4 KiB
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
validator.InitValidator()once.jwtauth.New("HS256", []byte("test-secret"), nil)stands in forJWT_SECRET.- Reuse the fakes of
test/image_variants_support_test.go:mockVariantRepo,mockVariantFileRepo,mockVariantStorage,countingRenderer. Add tomockVariantFileRepothe per-source totals method (rows and bytes by source stored name) with a call counter. ReusemockImageRepo(width, height, size, content type, SHA256, created_at seeded),mockArticleRepo, the user fake and the audit fakes oftest/activity_support_test.go. - Users: owner 1, admin 2, verified plain user 3 (role other than owner/admin), unverified user 4. Real signed
jwtcookies withuser_idandtoken_version; a stale cookie with a lowertoken_version; a valid API key in the key fake. - Router: a fresh
chi.NewRouter()mirroringNewMediaRouterinapi/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 handlerstaticimages.NewHandler(...)at/static/uploadedImagesover the same storage and variant use cases so a generated file is observed through the public url. - 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
- 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, 404Image not found.
B. Listing the variants of one image
- Seed the Rule's Background. Decode the body and assert order (name), the absence of the disabled variant,
width/heightas JSONnullon the raw body, output size and format,generated,size(null when not generated),url. Stale fingerprint row:generatedfalse. Disabled variant's row ignored. SVG: raw body{"unsupported":"svg","items":[]}. No enabled variant: raw"items":[]. - Output format Examples:
countingRenderer.EncodableFormats()configured for webp on and off; a webp source withoriginalgivespngwithout an encoder. - Geometry table: for each row assert the body's
output_width/output_heightand then render the same source and settings with the real renderer and assert the decoded size equals them (one source of truth). - Listing renders nothing: renderer count 0, storage
Open/Putcounts 0, no audit event.
C. Generating one
- First generation: 200, item fields, exactly one
Put, one index row whose size equals the itemsize, 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. - 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 andPutcount 1. - Refusals: unknown, disabled and wrongly-cased variant names give 404
Variant not foundwith 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 500Internal server error, no blob, no row, and the next call is 200.
D. Generating all
- Fresh image: 200,
generated5,failed0,failuresraw[], five items all generated, five blobs and rows,printuntouched. Pre-generated subset: counts only the newly generated, renderer count equals the missing ones. - Failure Examples: make the renderer fake return
ErrSourceTooLarge,ErrVariantSourceUnreadableand a plain error forheroonly; 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
- 200 with
purged_files1 andpurged_bytesequal to the seeded size; blobDeletecalled 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. StorageDeletefailing gives 500 and the row is kept.
F. Generate on upload
- Upload through the real upload path with the Rule's four variants: after the 201, assert rows for
thumbandheroonly,variant_count2 andvariant_sizeequal to the sum, exactly one audit event (media.upload). Renderer failing forhero: 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
- Seed the Rule's table.
variant_count/variant_sizeper item (raw JSON integers, 0 not null). Per-source totals fake call count is 1 per list call.variants=with|withoutsets;countsunchanged; combination rows;sort=total_sizeorder1,4,2,3,5,sort=largestorder1,2,3,4,5; tie breaks by id (make two totals equal); cursor walk withlimit=2; empty cache withvariants=with; every 400 row withapperr.MediaQueryErrorMessage. Rename response carries the fields. - Delete consistency: seed four rows and four blobs for image 1, DELETE, assert exactly those four
Deletecalls and rows. Usage and merge rows with a?v=herosuffix in the article body.
H. Audit
- Generate-all success, partial failure (
causes), all-failed (failedoutcome 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=medialists both new events. Extend the mapping test offailureReasonsfor the three new sentences.
I. Mutation guard
task mutatescope is./internal/infrastructure/useCases, scoped withMUTATE_FILESto the new use case files andMUTATE_RUNto the new test names. Each new branch needs a killing assertion: the generated-flag fingerprint comparison (equal and differing), thegenerate_on_uploadANDenabledcondition (each alone false), skipped already-generated variants, the failure-cause mapping order,generated > 0versusfailed > 0for the outcome, thepurged_files > 0guard, 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
- Guard test (
go list -depsor grep): the domain imports only the stdlib and domain packages; the new use cases import nopersistence,mailer,models,gorm,net/http, chi orapi; the media handler imports nopersistence,mailer,modelsorgorm.CGO_ENABLED=0 go build -tags nofrontend ./...succeeds. No comment in new or edited Go files, tests or docs.docs/api.mdlists the new routes, shapes, params and errors.
Manual (not automated)
task dev:apiover a scratch SQLite file andUPLOAD_DIR: upload an image with a flagged variant and check the file lands inUPLOAD_DIR; then use the generate-all and purge routes with curl and a real cookie and check the files appear and disappear.- S3 driver (
STORAGE_DRIVER=s3, MinIO): per-image generate and purge, upload with a flagged variant. - Latency of an upload of a 10 MB image with five flagged variants.
- Read the two new events in the real Activity screen against section 8 copy.