[AZ-487] [AZ-488] docs: cycle 2 test-spec sync

Append cycle 2 entries to test-spec artifacts (cycle-update mode):

* security-tests.md: SEC-05..SEC-09 (AZ-487 JWT 401/403/parity)
  + SEC-10..SEC-11 (AZ-488 permission + reject-detail leak hygiene).
* blackbox-tests.md: BT-13..BT-17 (UAV happy / mixed / multi-source
  coexistence / same-source UPSERT / rule-ordering) + BT-18 (existing
  endpoints parity with Bearer token).
* resource-limit-tests.md: RL-05..RL-07 (MaxBatchSize, per-item MaxBytes,
  Kestrel/Form envelope cap).
* performance-tests.md: untouched (PT-08 already landed with AZ-488 as
  Deferred — see _docs/_process_leftovers/2026-05-11_perf-pt07-harness).
* traceability-matrix.md: append AC rows for AZ-487 AC-1..AC-8 and
  AZ-488 AC-1..AC-10 + AC-7a..AC-7e; annotate "No authentication"
  restriction as superseded by AZ-487+AZ-488; add NFR rows (perf,
  security, reliability, compatibility) for both tasks; refresh totals
  (78 tests; 47/47 ACs; 8/8 restrictions).

Coverage shape: AZ-487 AC-7 (Swagger Authorize) and the perf NFRs are
recorded but not actively measured this commit (manual UI smoke +
deferred PT-08 harness, respectively).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-12 00:00:14 +03:00
parent dc3dabe7bd
commit 98cdcd17c1
5 changed files with 191 additions and 10 deletions
+63
View File
@@ -23,3 +23,66 @@
**Trigger**: POST /api/satellite/route with invalid JSON body
**Expected**: Parse error returned
**Pass criterion**: HTTP 400; error message indicates parsing failure; no crash
---
## Cycle 2 — AZ-487 JWT validation baseline
The pre-AZ-487 assumption "no authentication" is superseded by these scenarios. The SEC-01..SEC-04 scenarios above still hold (they probe input handling, not the auth layer), but every authenticated test variant must now attach a Bearer token.
## SEC-05: Anonymous Request to Any Authenticated Endpoint Returns 401
**Trigger**: GET `/api/satellite/tiles/latlon?Latitude=...&Longitude=...&ZoomLevel=18` (or any `/api/satellite/*` endpoint) with NO `Authorization` header.
**Precondition**: API running with `JWT_SECRET` configured.
**Expected**: HTTP 401 Unauthorized; `WWW-Authenticate: Bearer` header present; response body does not leak validation internals.
**Pass criterion**: status == 401 AND `WWW-Authenticate` header starts with `Bearer`.
**AC trace**: AZ-487 AC-1.
## SEC-06: Expired Token Returns 401
**Trigger**: Same request as SEC-05 carrying a JWT signed with the configured secret but with `exp` in the past (clock-skew margin already exceeded).
**Expected**: HTTP 401; the failure reason surfaces via `WWW-Authenticate` (e.g. `error="invalid_token"`, `error_description="The token is expired"`), never in the response body.
**Pass criterion**: status == 401 AND response body does not contain `Expires:` / `NotBefore:` / stack traces / internal exception types.
**AC trace**: AZ-487 AC-2.
## SEC-07: Tampered Signature Returns 401
**Trigger**: Same request as SEC-05 carrying a JWT whose payload was modified after signing so the HMAC no longer verifies.
**Expected**: HTTP 401; body free of cryptographic detail.
**Pass criterion**: status == 401 AND request never reaches downstream handlers (no DB write, no Google Maps fetch).
**AC trace**: AZ-487 AC-3.
## SEC-08: Startup Fails on Missing / Short Secret
**Trigger**: Boot the API container with `JWT_SECRET` unset, empty, or shorter than 32 bytes.
**Observable**: Container exit code, stdout error message.
**Pass criterion**: container exits non-zero within 10s of start; stderr contains a message identifying the missing or short `JWT_SECRET` and the 32-byte minimum; Kestrel never binds to its port.
**AC trace**: AZ-487 AC-5. Behavioral test — no input data.
## SEC-09: Valid Token Reaches Handler Unchanged
**Trigger**: GET `/api/satellite/tiles/latlon?...` with a JWT signed by the configured secret and `exp` in the future.
**Expected**: Response is byte-identical (status, body, headers other than `Authorization`/`WWW-Authenticate`) to the pre-AZ-487 baseline for the same parameters.
**Pass criterion**: status == 200 AND response body matches BT-01 expected schema.
**AC trace**: AZ-487 AC-4. Also exercised by AZ-487 AC-8 / integration smoke parity.
---
## Cycle 2 — AZ-488 UAV upload authorization
## SEC-10: Valid Token Without GPS Permission Returns 403 on UAV Upload
**Trigger**: POST `/api/satellite/upload` carrying a JWT with `permissions: ["FL"]` (no `GPS`); body is an otherwise-valid 1-item batch.
**Precondition**: AZ-487 in place; AZ-488 endpoint registered with the `GPS` permission policy.
**Expected**: HTTP 403 Forbidden; no row in `tiles`; no file under `./tiles/uav/`.
**Pass criterion**: status == 403 AND `SELECT COUNT(*) FROM tiles WHERE source='uav' AND ...` == pre-test count AND uploaded file does NOT exist on disk.
**AC trace**: AZ-488 AC-6.
## SEC-11: Reject-Reason Details Do Not Leak Server Internals
**Trigger**: POST `/api/satellite/upload` with a batch where item-1 deliberately fails Rule 5 (`IMAGE_TOO_UNIFORM`) and item-2 deliberately fails Rule 1 (`INVALID_FORMAT`).
**Precondition**: Authenticated request with `GPS` permission.
**Expected**: HTTP 200 with per-item results; each `rejectDetails` is short, human-readable, and contains none of: server-side file paths, exception type names, stack traces, internal class names, secrets, or hostnames.
**Pass criterion**: For every rejected item, `rejectDetails` matches `^[A-Za-z0-9 .,()<>=:%/-]{0,200}$` AND contains no path separator (`/` or `\`) followed by a directory name from the server image (`tiles`, `src`, `obj`, `bin`).
**AC trace**: AZ-488 § Security NFR.