# Testing Strategy This page describes the testing structure, conventions, and current coverage for quicproquo. All tests run with `cargo test --workspace` and must pass before any code is merged. For the coding standards that tests must follow, see [Coding Standards](coding-standards.md). --- ## Test Organisation ### Unit Tests Unit tests live alongside the code they test, in `#[cfg(test)] mod tests` blocks at the bottom of each source file. They test individual functions and types in isolation. **quicproquo-core:** | Module | Tests | What they cover | |--------|-------|----------------| | `codec` | 7 tests | Length-prefixed frame encoding/decoding, edge cases (empty payload, max size, partial frame, exact boundary) | | `keypair` | 3 tests | Ed25519 keypair generation, public key extraction, deterministic re-derivation | | `group` | 2 tests | Group round-trip (create + add + join + send + recv), group\_id lifecycle | | `hybrid_kem` | 11 tests | Encapsulate/decapsulate round-trip, key generation, combiner correctness, wrong-key rejection, serialisation | **quicproquo-proto:** | Module | Tests | What they cover | |--------|-------|----------------| | `lib` | 3 tests | Cap'n Proto builder/reader round-trip, canonical serialisation, schema validation | ### Integration Tests Integration tests live in `crates/quicproquo-client/tests/` and test the full client-server interaction. Each test spawns a server using `tokio::spawn` within the same test binary, then runs client operations against it. | File | Milestone | What it covers | |------|-----------|---------------| | `auth_service.rs` | M2 | KeyPackage upload via AS, KeyPackage fetch (single-use consume semantics), identity key validation | | `mls_group.rs` | M3 | Full MLS round-trip: register state, create group, add member via Welcome, send encrypted message, receive and decrypt | ### Test Pattern All integration tests follow the same pattern: ```rust #[tokio::test] async fn test_something() { // 1. Start server in background let server_handle = tokio::spawn(async move { server::run(config).await.unwrap(); }); // 2. Wait for server to be ready tokio::time::sleep(Duration::from_millis(100)).await; // 3. Run client operations let result = client::do_something(server_addr).await; // 4. Assert assert!(result.is_ok()); // ... // 5. Cleanup server_handle.abort(); } ``` This pattern ensures tests are self-contained and do not require an external server process. --- ## Running Tests ### Full Workspace ```bash cargo test --workspace ``` This runs all unit tests and integration tests across all four crates. ### Single Crate ```bash cargo test -p quicproquo-core cargo test -p quicproquo-proto cargo test -p quicproquo-server cargo test -p quicproquo-client ``` ### Single Test ```bash cargo test -p quicproquo-core -- codec::tests::test_round_trip cargo test -p quicproquo-client --test mls_group ``` ### With Output ```bash cargo test --workspace -- --nocapture ``` --- ## Current Results All tests pass as of the M3 milestone on branch `feat/m1-noise-transport`. Summary: | Crate | Unit Tests | Integration Tests | Total | |-------|-----------|-------------------|-------| | `quicproquo-core` | 23 | -- | 23 | | `quicproquo-proto` | 3 | -- | 3 | | `quicproquo-server` | 0 | -- | 0 | | `quicproquo-client` | 0 | 5 | 5 | | **Total** | **26** | **5** | **31** | --- ## Test Conventions ### Naming Test functions use descriptive names that state what is being tested and the expected outcome: ```rust #[test] fn encode_decode_round_trip_preserves_payload() { ... } #[test] fn empty_payload_produces_length_zero_frame() { ... } #[test] fn fetch_consumes_keypackage_single_use() { ... } ``` ### Assertions - Use `assert_eq!` with both expected and actual values. - Use `assert!(result.is_ok(), "descriptive message: {result:?}")` for `Result` checks. - For crypto operations, assert on specific error variants, not just `is_err()`. ### No External Dependencies Tests must not depend on external services, network access, or filesystem state outside the test's temporary directory. The `tokio::spawn` pattern for client-server tests ensures everything runs in-process. ### Determinism Tests must be deterministic. If randomness is needed (e.g., key generation), the test must not depend on specific random values -- only on the properties of the output (correct length, successful round-trip, etc.). --- ## Planned Testing Enhancements The following testing improvements are planned for future milestones: ### Fuzzing Targets (M5+) Fuzz testing for parser and deserialisation code: - **Cap'n Proto message parser:** Feed arbitrary bytes to the Cap'n Proto reader and verify it either parses correctly or returns a typed error (no panics, no undefined behaviour). - **MLS message handler:** Feed arbitrary `MLSMessage` bytes to the `GroupMember::receive_message` path. Tool: `cargo-fuzz` with `libfuzzer`. ### Golden-Wire Fixtures (M5+) Serialised test vectors for regression testing across versions: - Capture the wire bytes of known-good Cap'n Proto messages (Envelope, Auth, Delivery structs) at the current version. - Store as `.bin` files in `tests/fixtures/`. - Each test deserialises the fixture and verifies the expected field values. - When the wire format changes, fixtures are updated with a version bump. This catches accidental wire-format regressions that would break client-server compatibility. ### N-1 Compatibility Tests (M5+) Test that a client built at version N can communicate with a server built at version N-1 (and vice versa): - Build two versions of the binary (current and previous release). - Run the older server with the newer client and verify all RPCs succeed. - Run the newer server with the older client and verify graceful degradation (legacy mode works, new features return clean errors). ### Criterion Benchmarks (M5) Performance benchmarks using [Criterion.rs](https://docs.rs/criterion/): - Key generation latency (Ed25519, X25519, ML-KEM-768). - MLS encap/decap (KeyPackage generation, Welcome processing). - Group-add latency scaling: 2, 10, 100, 1000 members. - Cap'n Proto serialise/deserialise throughput. Benchmarks run separately from tests (`cargo bench`) and are not part of the CI gate, but are tracked for regression detection. ### Docker-based E2E Tests (Phase 5) End-to-end tests using `testcontainers-rs` (see [Future Research: Testcontainers-rs](../roadmap/future-research.md#testcontainers-rs)): - Spin up server container from the Docker image. - Run client operations from the test process against the containerised server. - Verify real network boundaries, container startup, and multi-process interactions. --- ## Cross-references - [Coding Standards](coding-standards.md) -- quality requirements for test code - [Milestones](../roadmap/milestones.md) -- which tests were added at each milestone - [Production Readiness WBS](../roadmap/production-readiness.md) -- Phase 5 (E2E Harness and Security Tests) - [Future Research: Testcontainers-rs](../roadmap/future-research.md#testcontainers-rs) -- Docker-based testing