# Test Specification — C8 FC + GCS Adapter

Component-scoped. Suite-level coverage in `_docs/02_document/tests/*.md`.

## Acceptance Criteria Traceability

| AC ID | Acceptance Criterion (one-line) | Test IDs | Coverage |
|-------|---------------------------------|----------|----------|
| AC-4.3 | FC output: GPS_INPUT (AP) + MSP2_SENSOR_GPS (iNav) with honest covariance | FT-P-03, FT-P-09-AP, FT-P-09-iNav, **C8-IT-01** | Covered |
| AC-4.4 | Estimates streamed frame-by-frame | NFT-PERF-02, **C8-IT-02** | Covered |
| AC-5.1 | Init from FC EKF's last valid GPS + IMU-extrapolated | FT-P-11, **C8-IT-03** | Covered |
| AC-5.2 | On >3 s without estimate, FC IMU-only fallback; SUT logs | NFT-RES-01 | Covered (C8 emission path) |
| AC-6.1 | GCS stream at 1–2 Hz | FT-P-12, **C8-IT-04** | Covered |
| AC-6.2 | GCS may send commands via standard MAVLink | FT-P-13, **C8-IT-05** | Covered |
| AC-6.3 | WGS84 output | FT-P-14, **C8-IT-06** | Covered |
| AC-NEW-2 | Spoofing-promotion latency <3 s p95 (source-set switch) | NFT-PERF-04, **C8-IT-07** | Covered |
| RESTRICT-COMM-2 | iNav has no MAVLink signing; documented asymmetry | NFT-SEC-03, **C8-IT-08** | Covered |
| D-C8-9 = (d) | MAVLink 2.0 per-flight signing on AP | IT-3, **C8-ST-01** | Open (gated by IT-3 SITL pass) |

---

## Component-Internal Tests

### C8-IT-01: per-FC encoder produces honest 6×6 → 2×2 covariance projection

**Summary**: `EstimatorOutput.covariance_6x6`'s position 3×3 block projects to the FC's 2×2 horizontal covariance with the same Frobenius norm scale (no fake-confidence regression).

**Traces to**: AC-4.3

**Description**: 100 emitted `EstimatorOutput` records with varying covariances; capture the encoded `GPS_INPUT` bytes; decode via pymavlink; assert the FC-side 2×2 covariance Frobenius norm equals the source 3×3 horizontal-block norm to within 1% (small numerical tolerance for unit conversion). Same assertion against `MSP2_SENSOR_GPS` for the iNav adapter.

**Input data**: scripted `EstimatorOutput` sequence with controlled covariances.

**Expected result**: norm equality within 1% for both adapters.

**Max execution time**: 30 s.

---

### C8-IT-02: 5 Hz periodic emission stays within ±5% of nominal interval

**Summary**: `emit_external_position` runs at exactly 5 Hz with jitter ≤ ±5%.

**Traces to**: AC-4.4

**Description**: drive the emit path for 60 s; record emission timestamps; compute inter-emission interval distribution; assert mean = 200 ms ± 1 ms; std-dev ≤ 10 ms; max gap ≤ 220 ms.

**Input data**: scripted `EstimatorOutput` source at 5 Hz.

**Expected result**: jitter within bound.

**Max execution time**: 90 s.

---

### C8-IT-03: warm-start GPS from FC EKF feeds C5

**Summary**: at takeoff, the FC EKF's last-valid-GPS is read by C8 and surfaced via `current_flight_state` for C5's warm-start.

**Traces to**: AC-5.1

**Description**: stage a SITL with a known last-valid-GPS; bring up C8; call `current_flight_state`; assert `FlightStateSignal` includes the EKF's pose hint with timestamp ≤ 1 s old.

**Input data**: ArduPilot SITL with scripted EKF state.

**Expected result**: warm-start hint exposed within 1 s of C8 ready.

**Max execution time**: 60 s.

---

### C8-IT-04: 1–2 Hz GCS downsampling

**Summary**: GCS stream is downsampled from C5's 5 Hz to 1–2 Hz per AC-6.1.

**Traces to**: AC-6.1

**Description**: drive 5 Hz `EstimatorOutput`; capture GCS-side stream; assert rate is between 1 and 2 Hz with consistent downsample factor.

**Input data**: scripted source.

**Expected result**: 1–2 Hz GCS stream.

**Max execution time**: 60 s.

---

### C8-IT-05: GCS commands are accepted and routed

**Summary**: standard MAVLink commands sent from QGC reach C8 and the system handles them per AC-6.2 (e.g., parameter read, mode set).

**Traces to**: AC-6.2

**Description**: connect QGC SITL to the C8 GCS adapter; send a parameter-read; assert the response is well-formed and the parameter values match the running config.

**Input data**: QGC SITL + C8 adapter.

**Expected result**: parameter read returns values.

**Max execution time**: 60 s.

---

### C8-IT-06: WGS84 conversion round-trip identity

**Summary**: `WgsConverter` (helper) round-trips a local-tangent-plane → WGS84 → local-tangent-plane within 1 cm position residual.

**Traces to**: AC-6.3 (defensive — backstops the helper that C4 + C8 both rely on)

**Description**: 1000 random local-tangent-plane points; round-trip through the helper; assert max residual < 1 cm position; max angular residual < 0.001°.

**Input data**: scripted random points within ±10 km of an origin.

**Expected result**: round-trip residual within bound.

**Max execution time**: 5 s.

---

### C8-IT-07: source-set switch latency under spoof event

**Summary**: when the spoof gate clears, `request_source_set_switch` fires the AP `MAV_CMD_SET_EKF_SOURCE_SET` within the AC-NEW-2 3 s budget.

**Traces to**: AC-NEW-2

**Description**: scripted scenario from C5-IT-06 (spoof recovery); assert C8 issues the `MAV_CMD_SET_EKF_SOURCE_SET` within 3 s of the gate-clear event; assert SITL acknowledges the command.

**Input data**: ArduPilot SITL + scripted gate-clear.

**Expected result**: command issued + ACK within 3 s.

**Max execution time**: 60 s.

---

### C8-IT-08: iNav signing-asymmetry assertion

**Summary**: the iNav adapter never attempts MAVLink signing (iNav doesn't support it per RESTRICT-COMM-2); the AP adapter always does.

**Traces to**: RESTRICT-COMM-2

**Description**: bring up the iNav adapter; assert no signing handshake is attempted (capture wire bytes, check no MAVLink2 signed-flag set). Bring up AP adapter; assert signing handshake completes.

**Input data**: iNav SITL + AP SITL.

**Expected result**: per the assertion above.

**Max execution time**: 60 s.

---

## Performance Tests

### C8-PT-01: emission latency under load

**Traces to**: AC-4.4 / AC-4.1 (emission portion)

**Load scenario**: 5 Hz emit + 200 Hz inbound IMU subscribe + GCS at 2 Hz; 10 min replay.

**Expected results**:

| Metric | Target | Failure Threshold |
|--------|--------|-------------------|
| `emit_external_position` p95 | ≤ 5 ms | 15 ms |
| Inbound IMU callback p95 | ≤ 1 ms | 5 ms |

---

## Security Tests

### C8-ST-01: MAVLink 2.0 per-flight signing handshake (D-C8-9 = (d), gated by IT-3)

**Summary**: at takeoff, C8 (AP adapter) generates a per-flight ephemeral key and completes the MAVLink 2.0 signing handshake with ArduPilot SITL.

**Traces to**: D-C8-9 = (d) (R03 risk)

**Test procedure**:
1. Bring up SITL with no pre-shared key.
2. C8 generates a fresh per-flight key, completes handshake.
3. Send 100 signed messages; assert SITL accepts them.
4. Replay one of those messages with a tampered signature; assert SITL rejects it.

**Pass criteria**: handshake succeeds; tampered messages rejected.
**Fail criteria**: handshake fails (escalate to D-C8-2-FALLBACK per ADR-008) OR tampered messages accepted.

**Status**: gated by IT-3 SITL pass; this test IS the gate.

---

### C8-ST-02: signing key never persists across flights

**Summary**: per-flight ephemeral key is destroyed in memory at landing; never written to disk.

**Traces to**: defensive (D-C8-9 = (d))

**Test procedure**:
1. Run a flight; capture the key in memory at takeoff via test harness.
2. Trigger landing.
3. Inspect process memory + disk for any residue of the key.
4. Assert no on-disk persistence; in-memory residue cleared by the configured zeroisation routine.

**Pass criteria**: no on-disk + memory zeroised.
**Fail criteria**: any persistence.

---

## Acceptance Tests

Covered transitively via FT-P-09-AP / FT-P-09-iNav / FT-P-12 / FT-P-13 / FT-P-14.

---

## Test Data Management

| Data Set | Source | Size |
|----------|--------|------|
| ArduPilot SITL fixture (Plane) | upstream SITL Docker | image |
| iNav SITL fixture | upstream SITL Docker | image |
| QGC SITL | upstream Docker | image |
| Scripted `EstimatorOutput` sequences | scripted | <5 MB each |

**Setup**: SITL containers must be running; C8 connects via simulated UART.
**Teardown**: stop containers; clean ephemeral keys.
**Data isolation**: per-test SITL container.
