Files
annotations/_docs/02_document/tests/environment.md
T
Oleksandr Bezdieniezhnykh 03f879206e docs+src: complete Steps 1-3 outcomes + auth re-sync baseline
This commit captures everything produced during autodev existing-code
Steps 1 (Document), 2 (Architecture Baseline Scan), and 3 (Test Spec),
together with the targeted auth + CORS re-sync triggered on 2026-05-14
when codebase drift was detected at Step 4 entry. None of this work was
previously committed.

Step 1 (Document) — 50+ _docs/02_document/ files: problem, solution,
architecture, system flows, glossary, module-layout, per-component
specs (01..06), modules, deployment, diagrams, data model, FINAL
report, verification log, discovery.

Step 2 (Architecture Baseline) — architecture_compliance_baseline.md.
Verdict PASS_WITH_WARNINGS (0 Critical, 0 High, 1 Medium, 2 Low). No
High/Critical findings; auto-chained to Step 3 per existing-code flow.

Step 3 (Test Spec) — _docs/02_document/tests/* (67 scenarios across
blackbox, security, resilience, resource-limit, performance), plus
e2e/docker-compose.test.yml, e2e/seed/run.sh, scripts/run-tests.sh,
scripts/run-performance-tests.sh. Coverage 88% over the active scope
(40 of 45 items covered, 6 RB-deferred, 5 documented-as-uncovered).

Targeted auth + CORS re-sync — replaces the deleted in-house token
issuer with a JWKS-verifier model. AuthController and TokenService
removed; JwtExtensions switched from HS256 symmetric to ES256 over
admin's JWKS. ConfigurationResolver and CorsConfigurationValidator
added under src/Infrastructure/. ADR-002 and ADR-006 retired; SEC-01,
SEC-02, SEC-03 marked Closed. One new testability risk recorded in
architecture.md Open Risks Section 6 (JWKS HTTPS gating).

Source changes:
- src/Auth/JwtExtensions.cs (modified) — ES256, JWKS, alg pinning
- src/Program.cs (modified) — DI wiring for ConfigurationResolver
  and CorsConfigurationValidator
- src/Controllers/AuthController.cs (deleted) — no in-service issuance
- src/Services/TokenService.cs (deleted) — same
- src/Infrastructure/ConfigurationResolver.cs (new)
- src/Infrastructure/CorsConfigurationValidator.cs (new)
- .env.example (new) — required env var documentation
- .gitignore (updated)

Cross-repo coordination: _docs/cross-repo/flights_h1_h2_h3_change_spec
captures the change-spec for downstream services that consumed the now
deleted /auth endpoints.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-14 20:19:05 +03:00

11 KiB

Test Environment

Overview

System under test: Azaion.Annotations HTTP API on port 8080 (REST + SSE) plus its RabbitMQ Stream producer (azaion-annotations stream). Consumer app purpose: A standalone test runner that exercises the system through its public HTTP / SSE / Stream interfaces only — no in-process imports, no direct DB queries against the system's main DB, no shared filesystem.

Docker Environment

Services

Service Image / Build Purpose Ports
annotations Built from src/Dockerfile (ARM64) with AZAION_REVISION=test-<sha> System under test 8080:8080
postgres postgres:13 DB for the system under test 5432:5432 (private to test net)
rabbitmq rabbitmq:3.13-management with the streams plugin enabled Stream broker the SUT publishes to 5552:5552 (stream listener), 15672:15672 (mgmt UI, optional)
e2e-runner Built from tests/Azaion.Annotations.E2E/Dockerfile Black-box test runner (xUnit + HttpClient + RabbitMQ.Stream.Client consumer); also holds the ES256 private key used to mint per-test bearer tokens
e2e-issuer python:3.12-alpine running tests/harness/mock_issuer.py (≈40 lines, serves a static JWKS over HTTP) Mock JWKS endpoint stand-in for admin's real issuer; publishes the public ES256 key the SUT validates against 8080 (on e2e-net; not exposed to host)
dataseed One-shot job: psql only Boot-time seed of any required reference data (no users — annotations has no users table)

The fixture binaries (frame images, videos) are mounted from ../detections/_docs/00_problem/input_data/ (suite-relative path, see _docs/00_problem/input_data/fixtures.md) into both the annotations service (read-only, for direct file ingestion paths) and the e2e-runner (read-only, for upload-as-multipart paths).

Networks

Network Services Purpose
e2e-net annotations, postgres, rabbitmq, e2e-issuer, e2e-runner, dataseed Isolated bridge network — services reach each other by container hostname

Volumes

Volume Mounted to Purpose
annotations-images annotations:/data/images images_dir — content-addressed image bytes + YOLO label files
annotations-videos annotations:/data/videos videos_dir
annotations-deleted annotations:/data/deleted deleted_dir (post RB-01 soft-delete relocation)
pg-data postgres:/var/lib/postgresql/data DB durability across container restart (resilience scenarios)
fixtures-ro (bind) annotations:/fixtures:ro, e2e-runner:/fixtures:ro Reuse of detections corpus binaries

docker-compose structure

services:
  postgres:
    image: postgres:13
    environment:
      POSTGRES_DB: annotations
      POSTGRES_USER: annotations
      POSTGRES_PASSWORD: annotations
    volumes:
      - pg-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U annotations"]

  rabbitmq:
    image: rabbitmq:3.13-management
    environment:
      RABBITMQ_DEFAULT_USER: annotations
      RABBITMQ_DEFAULT_PASS: annotations
      RABBITMQ_PLUGINS: rabbitmq_stream rabbitmq_management
    healthcheck:
      test: ["CMD", "rabbitmq-diagnostics", "ping"]

  e2e-issuer:
    image: python:3.12-alpine
    command: ["python", "/harness/mock_issuer.py"]
    volumes:
      - ../tests/harness:/harness:ro
      - jwt-keys:/keys
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8080/.well-known/jwks.json"]

  annotations:
    build:
      context: ../src
    environment:
      ASPNETCORE_ENVIRONMENT: E2ETest
      DATABASE_URL: postgresql://annotations:annotations@postgres:5432/annotations
      JWT_ISSUER: https://e2e-issuer.test
      JWT_AUDIENCE: annotations-e2e
      JWT_JWKS_URL: http://e2e-issuer:8080/.well-known/jwks.json
      CorsConfig__AllowedOrigins__0: http://e2e-runner.test
      RABBITMQ_HOST: rabbitmq
      RABBITMQ_STREAM_PORT: 5552
      RABBITMQ_PRODUCER_USER: annotations
      RABBITMQ_PRODUCER_PASS: annotations
      AZAION_REVISION: test-${GIT_SHA:-local}
    volumes:
      - annotations-images:/data/images
      - annotations-videos:/data/videos
      - annotations-deleted:/data/deleted
      - ../../detections/_docs/00_problem/input_data:/fixtures:ro
    depends_on:
      postgres:
        condition: service_healthy
      rabbitmq:
        condition: service_healthy
      e2e-issuer:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost:8080/health"]

  dataseed:
    image: postgres:13
    depends_on:
      annotations:
        condition: service_healthy
    entrypoint: ["/bin/sh", "/seed/run.sh"]
    volumes:
      - ./seed:/seed:ro

  e2e-runner:
    build:
      context: ../tests/Azaion.Annotations.E2E
    depends_on:
      dataseed:
        condition: service_completed_successfully
    environment:
      ANNOTATIONS_BASE_URL: http://annotations:8080
      JWT_ISSUER: https://e2e-issuer.test
      JWT_AUDIENCE: annotations-e2e
      RABBITMQ_HOST: rabbitmq
      RABBITMQ_STREAM_PORT: 5552
      RABBITMQ_USER: annotations
      RABBITMQ_PASS: annotations
      FIXTURES_DIR: /fixtures
    volumes:
      - ../../detections/_docs/00_problem/input_data:/fixtures:ro
      - jwt-keys:/keys:ro

volumes:
  pg-data: {}
  annotations-images: {}
  annotations-videos: {}
  annotations-deleted: {}
  jwt-keys: {}

networks:
  default:
    name: e2e-net

Consumer Application

Tech stack: .NET 10 + xUnit (matches the SUT runtime to avoid a second toolchain in CI). Uses HttpClient for REST, raw HttpClient with text/event-stream for SSE, and RabbitMQ.Stream.Client for stream-consumer scenarios. Entry point: dotnet test --logger "console;verbosity=normal" --logger "trx" --results-directory /results

Communication with system under test

Interface Protocol Endpoint / Topic Authentication
Annotations REST HTTP/1.1 JSON http://annotations:8080/annotations/*, /media/*, /dataset/*, /settings/*, /classes, /health Authorization: Bearer <jwt> (ES256 JWT minted on demand by the runner using the in-stack mock-issuer key)
Annotations SSE HTTP/1.1 text/event-stream http://annotations:8080/annotations/events?missionId=<guid> Same ES256 bearer token
Mock JWKS HTTP/1.1 JSON http://e2e-issuer:8080/.well-known/jwks.json None (test-net only)
RabbitMQ Stream AMQP 1.0 / streams (port 5552) Stream azaion-annotations Username + password env vars; consumer offset starts at next for fresh test runs
Postgres (test-only, read-only assertions on DB state) direct (out-of-band) postgresql://postgres:5432/annotations DB user; only the test runner uses this and only for blackbox-allowed assertions (e.g., F4-001 verifying the outbox row was inserted). Tests that need DB introspection are clearly marked.

What the consumer does NOT have access to

  • No in-process import of the Azaion.Annotations assembly.
  • No direct write to the SUT's annotations, media, detection, annotations_queue_records tables (DB read access only, for outbox-state assertions documented in test-data.md). Annotations has no users table.
  • No shared memory or filesystem with the SUT (volumes are mounted read-only).
  • No mocking of internal services (AnnotationService, FailsafeProducer, etc.) — all interactions go through the public surface.

CI/CD Integration

When to run: on every push to dev and on every PR; nightly full run including the long-running performance + resilience scenarios. Pipeline stage: after Woodpecker build step; new step test-e2e invoking docker compose -f e2e/docker-compose.test.yml up --abort-on-container-exit --exit-code-from e2e-runner (or, equivalently, scripts/run-tests.sh). Gate behavior: any failed scenario blocks the merge; nightly perf failures emit a warning but do not block a green PR. Timeout: 30 min for the standard suite (functional + smoke perf); 2 hours for the nightly full perf + resilience suite.

Reporting

Format: CSV (xUnit's trx output is converted by the runner into a flat CSV). Columns: test_id, test_name, category, traces_to, execution_time_ms, result, error_message. Output path: e2e-results/report.csv inside the e2e-runner container, mounted out to ./e2e-results/report.csv on the host.

In addition, raw xUnit .trx is preserved at e2e-results/results.trx for human inspection / IDE integration.

Dependencies on the existing stack

This environment intentionally does not re-use the suite's running development DB or RabbitMQ — it stands up its own. The only suite-level dependency is the read-only mount of detections/_docs/00_problem/input_data/ for fixtures.

Test Execution

Decision: Docker only.

Rationale (from Hardware-Dependency Assessment, run between test-spec Phase 3 and Phase 4):

  • Documentation scanrestrictions.md lists HW-01 (ARM64-only image), HW-02 (writable filesystem dirs), HW-03 (memory pressure on FailsafeProducer). None of these are accelerator / sensor / OS-feature dependencies; they are generic infrastructure constraints satisfiable in any Linux container.
  • Code scan — zero hits across src/ for CUDA, TensorRT, CoreML, OpenCL, Vulkan, TPU, V4L2, GPIO, cv2.VideoCapture, sys.platform-style branches, or platform.machine() checks. The Dockerfile's TARGETARCH branch (line 5) is a buildplatform-aware Node toolchain selector, not a runtime hardware gate — the running binary uses managed .NET 10 with no native acceleration paths.
  • Dependency filesAzaion.Annotations.csproj references only managed NuGet packages (Linq2DB, Npgsql, JwtBearer, RabbitMQ.Stream.Client, MessagePack, Swashbuckle, System.IO.Hashing). No native-binding libraries, no hardware-specific packages.

Classification: not hardware-dependent. Docker is the preferred default and the only chosen mode.

Docker mode — execution instructions

Run from the suite root (parent of annotations/ and detections/) so the fixture bind-mount path resolves:

# From the annotations repo root:
./scripts/run-tests.sh           # functional + smoke perf
./scripts/run-performance-tests.sh   # full perf scenarios

# Equivalent without the wrapper:
docker compose -f e2e/docker-compose.test.yml up \
  --abort-on-container-exit \
  --exit-code-from e2e-runner

Results land at e2e/e2e-results/report.csv (host path), and at test-results/ for any JUnit/CTRX outputs. The exit code of e2e-runner becomes the suite's exit code; CI uses it as the gate.

Why not local mode

The xUnit test runner CAN execute against a SUT bound to localhost:8080 if a developer wants to iterate inside the IDE. That path is not the supported test environment for CI; it is a developer convenience. Phase 4 produces only the Docker runner script.

CI image arch

The Docker test stack runs on the same ARM64 hosts the Woodpecker pipeline already targets (HW-01). If a future CI runner family is x86_64-only, the same docker-compose works because every service in e2e-net is multi-arch (postgres:13, rabbitmq:3.13-management, the SUT itself if rebuilt with --platform linux/amd64).