Files
RenewCMS/features/serve_uploaded_images.qa.md
T

4.7 KiB

QA procedure: serve_uploaded_images

Package: test (black-box, new file test/serve_uploaded_images_test.go).

Setup shared by every case

  1. Call validator.InitValidator() once (harmless even though this handler does no validation).
  2. Create a fresh uploadDir := t.TempDir() per test/subtest. This handler has no domain repository to fake — it serves real files from a real directory on disk, which is standard for filesystem-serving code and is not the "real SQLite file" / "live server" / "real SMTP" the project convention rules out.
  3. Build staticimages.NewHandler(uploadDir) (api/handlers/staticimages).
  4. Mount it on a fresh chi.NewRouter() mirroring api/router.go's shape after this slice:
    • r.Mount("/static/uploadedImages", staticHandler) — no jwtauth.Verifier / jwtauth.Authenticator wrapping.
    • r.Mount("/", stubFrontend) where stubFrontend is a trivial http.HandlerFunc that writes a recognizable marker string, e.g. "SPA-INDEX-MARKER", with status 200 — standing in for the real SPA fallback, just to prove routing order without needing the embedded dist/ build.
  5. Helper to seed a file: os.WriteFile(filepath.Join(uploadDir, name), content, 0o644), creating parent directories with os.MkdirAll when a subdirectory case needs one.

HTTP checks

  1. Write 4b9e2f1a-1111-....png, .jpeg, .webp files with distinct non-empty byte content. For each, httptest.NewRequest("GET", "/static/uploadedImages/"+name, nil), no cookie. Assert status 200; Content-Type equals the expected value; Cache-Control equals "public, no-cache"; X-Content-Type-Options equals "nosniff"; Content-Security-Policy header is absent; body bytes equal what was written.
  2. Write a .svg file whose content contains an inline onload handler (content doesn't matter functionally, just needs to be non-empty and distinguishable). GET it. Assert status 200, Content-Type is "image/svg+xml", Content-Security-Policy equals "default-src 'none'; style-src 'unsafe-inline'; sandbox", X-Content-Type-Options is "nosniff", body equals the exact SVG bytes (not sanitized/altered).
  3. Write notes.txt. GET it. Assert status 200, Content-Type is "application/octet-stream", no Content-Security-Policy header.
  4. Don't create missing.png. GET /static/uploadedImages/missing.png. Assert status 404; assert the body does not contain "SPA-INDEX-MARKER" (proves the static mount, not the frontend fallback, produced the 404 — i.e. mount ordering in the test router matches the required ordering in api/router.go).
  5. Write a file, then os.Remove it before the request. GET its old name. Assert status 404.
  6. os.MkdirAll(filepath.Join(uploadDir, "nested"), 0o755) and write a file inside it. GET /static/uploadedImages/nested. Assert status 404 (a directory is never listed or served).
  7. Write one real file inside uploadDir, and separately write a marker file (e.g. "sentinel-outside") to a location outside uploadDir (a second t.TempDir(), or a path one level above uploadDir) so a successful escape would be detectable. For each of the traversal paths "../"+sentinelName, "..%2f"+sentinelName (raw in the request path, since httptest.NewRequest takes a literal target string and Go's net/http mux/handler must not decode-then-escape), and a triple-..%2f variant: send GET /static/uploadedImages/<path>. Assert status 404 for every case, and assert the response body never equals the sentinel file's content.
  8. Write a .webp file. Send httptest.NewRequest("HEAD", ...). Assert status 200, the same Content-Type / Cache-Control / X-Content-Type-Options headers as the GET case, and an empty response body (rec.Body.Len() == 0).
  9. Repeat step 6's PNG case with explicitly no Cookie header set on the request (already true in step 6, but assert this once more as its own subtest/comment-free assertion block) and confirm status 200, not a 3xx redirect and not 401.

Manual verification (not exercised by the Go test)

  • Real end-to-end mount order: run task dev or a built task build binary, confirm GET /static/uploadedImages/<unknown> returns 404 from the real router (with the real embedded dist/), not index.html, since the Go test above stands in a stub for the frontend rather than the embedded SPA.
  • Browser-level CSP enforcement: open an uploaded SVG containing a script directly in a browser and confirm the CSP header actually blocks execution (header presence is asserted by the test; browser enforcement isn't).
  • No SMTP involvement.