Files
RenewCMS/features/s3_image_storage.qa.md

3.0 KiB

QA: S3 image storage

All tests live in test/ (package test). No cloud account, no real SMTP, no real SQLite file. Test names carry "S3Storage".

Settled decisions: boot backfill gets a log line only, wired in the composition root, with no cap and no use case change. No ETag is emitted; the staticimages handler is unchanged and conditional requests are validated by Last-Modified only.

Automated

  1. Config validation: table test over STORAGE_DRIVER and S3_* lookup functions (the MissingEnvVarError pattern, a StorageRequiredEnvVars(driver) helper like DatabaseRequiredEnvVars). Covers the validation table, UPLOAD_DIR ignored, unknown driver error.
  2. Registry: storage.Open with "local" returns local storage, "s3" returns the S3 storage after HEAD bucket, anything else returns the "unsupported storage driver" error.
  3. Fake S3: an httptest.Server on 127.0.0.1 only (never bind :port) implementing HEAD bucket, PUT, GET with Range, HEAD and DELETE object, with injectable statuses and recorded keys, headers and bodies.
  4. Adapter tests against the fake: the Put, Open (found, 404, 500) and Delete scenarios, HEAD bucket 404/403/500, prefix handling, ModTime and Size from metadata, Content as io.ReadSeeker (seek then read issues a ranged GET), secret absent from error text.
  5. Handler tests: staticimages.NewHandler over OpenMediaUseCase over the S3 storage on the fake server, mounted on a fresh chi.NewRouter() at /static/uploadedImages/*. Cover GET, HEAD, 304 via If-Modified-Since, 206, 416, SVG CSP, 404, 500, traversal.
  6. Handler regression: a storage fake returning a non-seekable Blob. GET and HEAD behave exactly as today.
  7. Backfill log: the boot log line for the number of images read, captured with a log writer, containing no credential.
  8. Local regression: all existing image tests pass unchanged.
  9. Gates: task test and task lint:go pass. task mutate is not part of this slice, but the new storage package becomes a target if the mutation scope is widened. The 404 to ErrBlobNotFound and idempotent-Delete branches need killing tests.

Optional integration (task test:s3)

Start a SeaweedFS container with Docker, create a bucket, set RENEWCMS_TEST_S3_ENDPOINT and run the adapter tests against it. Skip with a message when Docker is unavailable. Not part of task test.

Manual

  • Run docker-compose.s3.yml, upload an image in the SPA, confirm the object is in the bucket and /static/uploadedImages/ serves it, including after the container is recreated.
  • Real AWS S3, R2, B2 or Scaleway smoke test, and an instance-role credential check.
  • Start with a wrong bucket and confirm a clear, secret-free startup error.
  • Sync an old UPLOAD_DIR with aws s3 sync or mc mirror and confirm old images load.
  • Confirm docs describe: variables (configuration.md, .env.example, docker.env.example, Docker docs), UPLOAD_DIR ignored with s3, manual migration, update backups covering the DB only and not the bucket, and the updated architecture.md "Storage and database seams" section.