--- id: integration-suite name: Integration suite — invariant pins + feature-matrix + seam round-trips (tests/) status: completed depends_on: [port-server, port-client] scope: broad risk: medium impact: phase level: implementation tags: [tests, integration, invariants, phase-gate] --- ## Description The `tests/` integration suite per ADR-006: the cross-module surfaces the in-module seed tests cannot cover. A full-crate crate has no workspace consumers to lean on, so this suite is the invariant gate before the alknet rewrite consumes the crate. ### The suites 1. **Server seam round-trips** — for each `TlsIdentity` variant (X509 via generated PEM files in a tempdir, RawKey via `Ed25519SecretKey::generate`, SelfSigned): `TlsServerConfig::new` → `for_noq()` (all-features) / `for_tcp_tls()` / `rustls_config()` all succeed; the rustls config carries the expected ALPN list and `max_early_data_size`. 2. **Client seam round-trips** — `TlsClientConfig::new` per credentials cell → `for_noq()` / `into_rustls_config()`; `enable_early_data` pinned true; ALPN single-value list. 3. **Verifier-selection matrix** — the full `local_identity` × `remote_identity` construction matrix (the port-client unit assertions promoted to integration level, exercising the public API only). 4. **The nine-scheme exact-list pin** — the regression-proof shape (exact vec equality, not membership). 5. **Root-store fallback** — `load_platform_root_cert_store` (or the config built from it) yields a non-empty store even when the platform store is empty (webpki-roots merge; assert non-emptiness and count > 0). 6. **ACME lifecycle** (`--features acme`): construct an `Acme` identity with staging URL + tempdir cache; assert spawn-and-return semantics, `acme-tls/1` in the ALPN list, and the resolver wiring (no network I/O — do NOT hit Let's Encrypt). 7. **Feature-matrix gate**: `default = []` compiles + tests pass; `--features noq`; `--features tcp`; `--features acme`; `--all-features` (the gate is also a CI-verifiable convention, but the suite includes the combination that exercises every feature-gated accessor). ### Out of scope (explicitly) Real handshakes (transport crates' job — the scope boundary), network ACME orders, iroh (no iroh dep exists here), SOCKS5, dispatch. ## Work 1. Write the `tests/` files (one per suite group is fine). 2. Promote any in-module assertions that exercise cross-module behavior to integration level (keep the in-module ones too). 3. Wire the exact-list and `enable_early_data` pins. ## Verification - [x] Full suite green across all five feature combos - [x] The nine-scheme test asserts exact list equality - [x] `enable_early_data == true` asserted on a client config - [x] ACME test performs no network I/O (staging URL is constructed, never contacted) - [x] `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`, `cargo doc --no-deps` (doc comments render) ## Acceptance Criteria - [x] All 7 suites exist and pass - [x] The suite runs green with a single `cargo test --all-features` - [x] `tests/` layout documented in the task Summary ## References - docs/architecture/decisions/006-module-layout-and-tests.md (the test plan) - docs/architecture/server.md + client.md (the invariants) - Prior art: the extracted crate's in-module tests (`crates/alknet-tls/src/*.rs` `#[cfg(test)]` blocks) ## Notes > Agent fills this during implementation. ## Summary > Agent fills this on completion. ### The `tests/` layout | File | Suites covered | |------|----------------| | `tests/server_seams.rs` | Suites 1 + 7(server half): `TlsServerConfig::new` per X509 (rcgen PEM pair in tempdir) / RawKey / SelfSigned → `rustls_config()` + `for_tcp_tls()` (tcp-gated) + `for_noq()` (noq-gated); ALPN + `max_early_data_size = u32::MAX` asserted per path; the Acme-no-feature → `TlsError::AcmeConfig` cell (`cfg(not(feature = "acme"))`) | | `tests/client_seams.rs` | Suite 2: `TlsClientConfig::new` per credentials cell (pinned / CA / empty) → `into_rustls_config()` + `for_noq()` (noq-gated); `enable_early_data == true` + single-value ALPN asserted | | `tests/invariant_pins.rs` | Suites 3 + 4 + 5: the nine-scheme exact-list pin (exact vec equality, order included); the verifier-selection matrix (Some → `FingerprintPinVerifier`, None → `WebPkiServerVerifier` — the fail-closed structure, asserted via the config's derived Debug) + client-auth presentation matrix (RawKey → `only_raw_public_keys()`, X509 → loaded chain, SelfSigned/None → nothing, Acme → `TlsError::AcmeConfig`), all through the public API; root-store fallback non-empty | | `tests/acme_lifecycle.rs` | Suite 6 (`#![cfg(feature = "acme")]`): spawn-and-return semantics, `acme-tls/1` in the ALPN list, resolver wiring, `max_early_data_size` on the ACME branch — blackhole directory URL (`http://127.0.0.1:9`), staging URL constructed (never contacted) | Suite 7 (the feature-matrix gate) is the verification command block below — every combo is run, not just asserted. ### Notes / decisions - `rustls::ClientConfig`'s `verifier` and `rustls::ServerConfig`'s verifier are `pub(super)` at rustls 0.23.44 — the verifier identity is asserted through the derived Debug output (the verifier's type name appears in it); the client-auth resolver IS public (`client_auth_cert_resolver`), so the presentation matrix asserts it directly (`has_certs()` / `only_raw_public_keys()`). - `alktls::TlsClientConfig.rustls_config` is `pub(crate)` — integration level goes through `into_rustls_config()`. - The Acme-no-feature test is `cfg(not(feature = "acme"))` (it asserts the error only exists when the feature is absent; with the feature the same identity legitimately constructs — that path is covered by `acme_lifecycle`). - Real handshakes, network ACME, iroh, SOCKS5, dispatch: out of scope per the task (the scope boundary holds). ### Verification - All five feature combos green: default (68 lib + 13 int), `noq` (72 + 16), `tcp` (71 + 13), `acme` (68 + 11), `--all-features` (75 + 17). - `cargo clippy --all-targets --all-features -- -D warnings` ✓, `cargo fmt --check` ✓, `cargo doc --no-deps` ✓. - The ACME tests perform no network I/O (staging URL constructed, never contacted; the lifecycle test uses `127.0.0.1:9` — a blackhole).