# Blackbox Test Harness (`e2e/`)

This directory is the **public-boundary** test harness for `gps-denied-onboard`. It is owned by the `blackbox_tests` cross-cutting entry in `_docs/02_document/module-layout.md` and implements task **AZ-406** (Test Infrastructure Bootstrap) plus its downstream test-task siblings (AZ-407..AZ-446).

The harness runs in two execution tiers (`environment.md` § Two-tier execution profile):

- **Tier-1** — workstation Docker. `cd e2e/docker && docker compose -f docker-compose.test.yml up --build --abort-on-container-exit e2e-runner`
- **Tier-2** — Jetson Orin Nano Super hardware loop. `./e2e/jetson/run-tier2.sh --fc-adapter <ardupilot|inav> --vio-strategy <okvis2|klt_ransac>`

Both tiers emit the same CSV report format (one row per test) per `environment.md` § Reporting.

## Layout

```
e2e/
├── docker/        Tier-1 entrypoint (docker-compose.test.yml + Tier-2 bridge override + secrets mount)
├── jetson/        Tier-2 entrypoint (run-tier2.sh + systemd unit + tegrastats/jtop parsers)
├── runner/        e2e-runner image (Dockerfile, conftest, pytest plugins, helpers, requirements)
├── fixtures/      Fixture builders (tile-cache, age-injector, injectors/, mock-suite-sat, secrets, security)
├── tests/         Pytest target — `positive/`, `negative/`, `performance/`, `resilience/`, `security/`, `resource_limit/`
└── _unit_tests/   Out-of-container unit tests for the harness internals (run as part of the project test suite)
```

## Public-Boundary Discipline (hard rule)

The e2e-runner image **MUST NOT** import any module from the SUT source tree (`src/gps_denied_onboard/**`). The only legal interaction surfaces are:

- MAVLink (ArduPilot SITL — UDP 14550)
- MSP2 (iNav SITL — TCP 5760)
- HTTP/JSON (mock-suite-sat-service — port 8080)
- Filesystem read of the FDR archive after a run (`fdr-output` volume)

This rule is enforced by:

1. The runner `Dockerfile` building from a base image that does NOT install the SUT package.
2. Layout discipline: no `import gps_denied_onboard.*` in any file under `e2e/`.
3. Compose `e2e-net.internal: true` — no external network egress (RESTRICT-SAT-1, NFT-SEC-02).

See `_docs/02_document/tests/environment.md` for the full per-service spec.

## RUN_ID and report paths

Each invocation must set `RUN_ID` (defaults to `local-${USER}-${EPOCH}` in development; CI sets it from the workflow run id). Reports land at:

- `e2e-results/run-${RUN_ID}/report.csv`
- `e2e-results/run-${RUN_ID}/evidence/` (per-run `.tlog`, FDR archives, screenshots, profiler traces, tegrastats CSV, jtop CSV)

The `e2e-results/` directory is gitignored.

## How to add a new blackbox scenario

1. Decompose the scenario into a task spec under `_docs/02_tasks/todo/`.
2. Implement the test under the appropriate `e2e/tests/<category>/` folder.
3. The conftest's session-scoped `(fc_adapter, vio_strategy)` parameterization automatically applies — opt out with `@pytest.mark.parametrize` overrides.
4. Trace the scenario to the AC/RESTRICT IDs it exercises via the `traces_to` pytest marker — the CSV reporter emits this verbatim.

## How to add a new fixture builder

Fixture builders live under `e2e/fixtures/` and may be standalone Python modules (for runtime injectors) or Dockerized helpers (for tile-cache / mock-suite-sat). Each builder must:

- Be reproducible — given the same input, produce bit-identical output.
- Document its output volume / path in `_docs/02_document/tests/test-data.md`.
- Have a corresponding unit test under `e2e/_unit_tests/fixtures/`.

## Out-of-container unit tests

The harness's internal Python — CSV reporter, helpers, parsers, mock app, conftest skip rules — is unit-tested under `e2e/_unit_tests/`. These tests do NOT require Docker, SITL, or any external service and run as part of the project's main pytest invocation (`testpaths` extension in `pyproject.toml`).
