14 — QA Checklist (Files Module)
- 1. Functional
- 2. Large files & memory
- 3. Partial uploads & cancellation
- 4. MIME spoofing
- 5. Expired URLs / signed URLs
(planned) - 6. Tenant isolation
- 7. RBAC
(planned) - 8. Errors & resilience
- 9. Performance
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,sizematching the multipart part (files.service.ts:32-33). -
List returns newest-first (
createdAt desc,files.service.ts:48-50). -
GET /files/:idreturns 404 for unknown and soft-deleted ids (files.service.ts:54, repository soft-delete filter). -
Download sets
Content-Type= stored mimeType andContent-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:
FileInterceptorbuffer →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 servesContent-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-streamround-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
getSignedUrlignoresexpiresInSeconds(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 /filesdoes not include it;GET /files/:idwith 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); localstorage/<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.deletematrix 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 (
healthCheckthrows,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.deletethrows; 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,heightfields never populated (file.schema.ts:31-43) — verify no client code reads them.