Files
quicproquo/docs/src/contributing/testing.md
Chris Nennemann 853ca4fec0 chore: rename project quicnprotochat -> quicproquo (binaries: qpq)
Rename the entire workspace:
- Crate packages: quicnprotochat-{core,proto,server,client,gui,p2p,mobile} -> quicproquo-*
- Binary names: quicnprotochat -> qpq, quicnprotochat-server -> qpq-server,
  quicnprotochat-gui -> qpq-gui
- Default files: *-state.bin -> qpq-state.bin, *-server.toml -> qpq-server.toml,
  *.db -> qpq.db
- Environment variable prefix: QUICNPROTOCHAT_* -> QPQ_*
- App identifier: chat.quicnproto.gui -> chat.quicproquo.gui
- Proto package: quicnprotochat.bench -> quicproquo.bench
- All documentation, Docker, CI, and script references updated

HKDF domain-separation strings and P2P ALPN remain unchanged for
backward compatibility with existing encrypted state and wire protocol.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:11:51 +01:00

7.0 KiB

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.


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:

#[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

cargo test --workspace

This runs all unit tests and integration tests across all four crates.

Single Crate

cargo test -p quicproquo-core
cargo test -p quicproquo-proto
cargo test -p quicproquo-server
cargo test -p quicproquo-client

Single Test

cargo test -p quicproquo-core -- codec::tests::test_round_trip
cargo test -p quicproquo-client --test mls_group

With Output

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:

#[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:

  • 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):

  • 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