# Resource Limit Tests ### NFT-RES-LIM-01: Sustained-load process memory **Summary**: Process memory stays bounded under sustained `POST /annotations` traffic. **Traces to**: AC-N-03 (outbox depth bounded → memory bounded), HW-03 (memory pressure on `FailsafeProducer`'s image re-read) **Preconditions**: SUT freshly started; clean state; a stream consumer connected so the outbox actually drains. **Monitoring**: - `docker stats annotations` polled every 10s for `MemUsage` (RSS) and `MemPerc`. - Sample at the 0s / 60s / 600s marks. **Duration**: 10 minutes at 5 RPS. **Pass criteria**: RSS at the 600s mark ≤ 1.5× RSS at the 60s mark; no OOMKilled events; container stays healthy. --- ### NFT-RES-LIM-02: Single-file upload boundary **Summary**: Determine the maximum single-file upload size accepted by `POST /media`. **Traces to**: documented gap (no explicit limit in code; ASP.NET form-options apply) **Monitoring**: HTTP status code per uploaded size. **Steps**: | Size | Expected Result | |------|-----------------| | 1 MB | HTTP 200 | | 10 MB | HTTP 200 | | 50 MB | HTTP 200 | | 100 MB | HTTP 200 (probable, depends on ASP.NET defaults) | | 256 MB | HTTP 200 OR 400 (test the boundary) | | 512 MB | likely HTTP 400 / form-options reject | **Duration**: ~5 minutes (one upload per size). **Pass criteria**: a clear cutoff size is documented; below it the SUT accepts; at or above it the SUT returns the error envelope (NOT a 500 with no body, NOT a hang). --- ### NFT-RES-LIM-03: Outbox depth under broker outage **Summary**: With RabbitMQ stopped for an extended period, the outbox `annotations_queue_records` table grows linearly with traffic AND does not exceed disk capacity / DB connection pool limits within the test window. **Traces to**: NFT-RES-01 (extended), AC-N-03 **Monitoring**: - `SELECT COUNT(*) FROM annotations_queue_records` every 30s. - Disk usage of the Postgres data volume every minute. - `docker stats postgres` for memory. **Steps**: | Step | Action | Expected Behavior | |------|--------|------------------| | 1 | `docker exec rabbitmq rabbitmqctl stop_app` | broker down | | 2 | Run 10 RPS of `POST /annotations` for 5 minutes | 3000 outbox rows written | | 3 | Sample queue depth and disk usage | depth grows linearly; disk grows linearly with image bytes (since `images_dir` is also written) | | 4 | `docker exec rabbitmq rabbitmqctl start_app` | broker recovers | | 5 | Wait for queue to drain | depth goes to 0 within 5 minutes of recovery | **Duration**: 15 minutes total. **Pass criteria**: - During outage: SUT does not return 5xx; queue depth is exactly equal to total successful POSTs since the outage started. - During recovery: queue drains to 0 within 5 minutes. - No DB connection pool exhaustion (no `connection refused` from Postgres). - No SUT crashes. --- ### NFT-RES-LIM-04: Disk usage by `images_dir` over many distinct uploads **Summary**: Each distinct `image_bytes` POST consumes O(image-size) disk; identical re-uploads consume zero additional disk (idempotent). **Traces to**: AC-F-01, AC-F-02 **Steps**: | Step | Action | Expected Behavior | |------|--------|------------------| | 1 | Capture `du -sb $images_dir` baseline | non-empty path | | 2 | `POST /annotations` 100× with `image_small.jpg` (same bytes) | 1 file added, ~1.5 MB delta from step 1 | | 3 | `POST /annotations` 100× with random distinct image bytes (synthetic) | 100 new files; delta ≈ 100 × avg-size | **Pass criteria**: identical uploads do not duplicate disk; distinct uploads scale linearly. **Duration**: ~5 minutes. --- ### NFT-RES-LIM-05: Concurrent SSE subscribers — process-memory boundary **Summary**: 100 simultaneous SSE subscribers do not exhaust the SUT's memory or thread pool. **Traces to**: AC-N-05 (idle-channel memory bounded), OP-01 (per-instance SSE state) **Preconditions**: SUT freshly started. **Steps**: | Step | Action | Expected Behavior | |------|--------|------------------| | 1 | Open 100 SSE connections to `/annotations/events?missionId=` | all 100 alive | | 2 | Sample `docker stats annotations` immediately after connection | RSS recorded | | 3 | Idle for 10 minutes; sample every 60s | RSS stays within ± 10% of step 2 | | 4 | `POST /annotations` once for mission `` | all 100 subscribers receive the event within 1500ms | **Pass criteria**: RSS bounded; all subscribers receive the event; no `connection refused` or thread-pool starvation. **Duration**: ~12 minutes. --- ### NFT-RES-LIM-06: Migration on cold-start cost **Summary**: Boot-time `DatabaseMigrator.MigrateAsync()` adds bounded latency to cold start (`/health` returns 200 within `` after container start). **Traces to**: AC-N-01 **Steps**: | Step | Action | Expected Behavior | |------|--------|------------------| | 1 | `docker compose down annotations && docker compose up -d annotations` | container starting | | 2 | Poll `/health` every 200ms; record time-to-first-200 | record time | | 3 | Repeat with a fresh DB (cold migrator) and a populated DB (warm migrator) | both runs measured | **Pass criteria** (until contracted): time-to-first-200 ≤ 30s on cold migrator; ≤ 10s on warm migrator. **Step 15 will tune.** **Duration**: ~2 minutes.