Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

14 — QA Checklist (Files Module)

Files-specific verification. Global QA baseline: 00-shared/10_QA_Baseline. Every check below is traceable to a source line; focus areas mandated by the module's real behavior: large files, partial uploads, MIME spoofing, expired URLs, tenant isolation.


1. Functional

  • Upload a file → 201 record with originalName, mimeType, size matching the multipart part (files.service.ts:32-33).
  • List returns newest-first (createdAt desc, files.service.ts:48-50).
  • GET /files/:id returns 404 for unknown and soft-deleted ids (files.service.ts:54, repository soft-delete filter).
  • Download sets Content-Type = stored mimeType and Content-Disposition: attachment; filename="<originalName>" (files.controller.ts:60-61).
  • Delete: object removed from provider AND metadata soft-deleted; list no longer shows it; GET /files/:id → 404 (files.service.ts:66-70).
  • Uploading a file whose name contains " or ; — header quoting stays valid (files.controller.ts:61); names are un-sanitized (files.service.ts:31) — verify no header injection.

2. Large files & memory

  • No size limit is enforced (gap): verify behavior at 1 MB, 25 MB, 100 MB. Confirm the API process memory does not balloon per concurrent request (buffered path: FileInterceptor buffer → storage.upload(buffer)res.send(buffer), files.controller.ts:39-63). Record the practical ceiling.
  • Concurrent uploads (5 × 25 MB) — no OOM, no socket timeout.
  • Large download on slow link — client shows progress; no server-side timeout crash.
  • Streaming/Range: confirm absent (no Accept-Ranges) — document that resume is impossible; UI must not offer pause/resume.

3. Partial uploads & cancellation

  • Cancel mid-upload → no orphan object at provider? (Today: unknown — no cleanup job; partial bodies are discarded by the server, but verify no zombie object when the provider received a complete object before the client disconnected.)
  • Retry after failure → new record created (no idempotency) — confirm no duplicate storage objects remain.

4. MIME spoofing

  • Upload evil.pdf (actually a script, mimetype: application/pdf) → download serves Content-Type: application/pdf (echoed, files.controller.ts:60) — no server sniffing. Document: files render/execute per their claimed type; client "open with caution" guidance applies (09_User_Behaviour.md §6).
  • mimetype: application/octet-stream round-trips unchanged.

5. Expired URLs / signed URLs (planned)

  • R2 getSignedUrl(fileId, expiresIn) respects TTL (r2.provider.ts:77-86) — after expiry, request returns 403; client must re-request a fresh URL (no raw 403 UX).
  • Appwrite getSignedUrl ignores expiresInSeconds (appwrite-storage.provider.ts:67-74) — flagged gap; verify expected TTL behavior before relying on it.
  • Local driver returns fake path /api/v1/files/<id> (local-storage.provider.ts:47-51, ponytail shortcut) — dev-only; never ship.

6. Tenant isolation

  • Tenant A upload → Tenant B: GET /files does not include it; GET /files/:id with A's id → 404 (repository tenant scoping, file.repository.ts:9-15 + TenantContextService, files.service.ts:28).
  • Storage keys: R2 <tenantId>/<uuid>--<name> (r2.provider.ts:39); local storage/<tenantId>/... (local-storage.provider.ts:24). Verify object keys carry tenant prefix. Appwrite: single bucket, no prefix (appwrite-storage.provider.ts:38-44) — isolation relies on metadata only; verify cross-tenant download by object id is impossible via API paths.
  • Delete from one tenant never touches another tenant's objects.

7. RBAC (planned)

  • file.read/file.upload/file.delete matrix on all 5 routes (permissions.constants.ts:86-88); 403 without permission; guards are not yet implemented (AGENTS.md) — test once landed.

8. Errors & resilience

  • Provider down (healthCheck throws, storage-provider.ts:27) → upload/download/delete fail with 500 and clear message; no partial Mongo state on upload failure (files.service.ts:29-34 — record created only after successful provider upload).
  • Delete when provider object already gone → storage.delete throws; record remains soft-deletable — decide reconciliation behavior (proposed).
  • 404 paths (list, get, download, delete) return the standard error envelope (00-shared/07_API_Conventions).

9. Performance

  • List with 1 000 records — envelope + JSON serialization time; no pagination exists (gap; (planned)).
  • thumbnailFileId, width, height fields never populated (file.schema.ts:31-43) — verify no client code reads them.