CE — Testing Guide
Test layers
| Layer | Command | Infrastructure |
|---|---|---|
| Unit tests | cargo test --workspace | None |
| Local integration | cargo test -p ce-node -- --nocapture | None |
| Hetzner E2E | cargo test -p ce-deploy -- --ignored --nocapture | Hetzner account + SSH key |
Unit tests
Run with cargo test --workspace. These are fast (sub-second) and test logic in isolation.
ce-identity tests
generate_creates_key_file— key file written on first runreload_returns_same_node_id— persistent identity across restartssign_and_verify_succeeds— happy pathverify_rejects_tampered_message— wrong data rejectedverify_rejects_wrong_key— wrong key rejectednode_id_hex_is_64_chars— format checknode_id_hex_roundtrips— hex decode roundtrip
ce-chain tests
try_reorg_switches_to_longer_fork— longer competing chain replaces ours (longest-chain rule)- Balance, heartbeat epoch, and tx lookups now use O(1) incremental caches instead of O(n) full scans
try_reorg_ignores_equal_length_fork— equal-length fork does not trigger reorgtry_reorg_rejects_invalid_block_in_fork— corrupt block in fork aborts reorgtry_reorg_no_connection_returns_false— orphaned blocks with no common ancestor rejectedhash_is_deterministic— same input → same hashhash_changes_with_nonce— nonce affects hashdifficulty_1_bit— mines to 1 leading zero bitdifficulty_8_bits_requires_zero_byte— mines to 8 leading zero bitsgenesis_structure— genesis block shapeappend_valid_block— happy pathappend_rejects_wrong_index— index continuityappend_rejects_wrong_prev_hash— chain linkingappend_rejects_invalid_tx_sig— tx signature validationthree_blocks_chain— sequential appendsbalance_starts_zero— no balance before miningbalance_from_block_reward— miner earns rewardbalance_with_transfer— transfer debit + creditblock_reward_halving_schedule— halving at 210k blockstx_verify_valid— valid sig acceptedtx_verify_rejects_tampered_amount— tampered kind rejectedtx_id_is_stable— deterministic IDsave_and_load_roundtrip— JSON persistenceload_or_genesis_returns_genesis_when_missing— missing file fallback
ce-chain Heartbeat tests
heartbeat_happy_path— host emits heartbeat, cell balance debited, epoch recordedheartbeat_rejects_replay— same or earlier epoch rejected; higher epoch acceptedheartbeat_rejects_insufficient_balance— cell with zero balance cannot be billedheartbeat_rejects_self_pay— host == cell forbiddenheartbeat_rejects_wrong_signer— heartbeat must be signed by the named host
ce-protocol tests
roundtrip_and_verify— build, encode, decode, verifyrequires_burn— flags payload-without-burn-proof
Local integration tests (crates/ce-node/tests/local_cluster.rs)
Run with cargo test -p ce-node -- --nocapture. These start real Node instances in-process.
Each test allocates ports from an atomic counter starting at 14100 to avoid conflicts.
single_node_mines— one node mines ≥1 block in 3 secondstwo_nodes_sync— two nodes reach similar chain heights within 5 secondstx_pool_propagates— transactions flow between nodesapi_health_check— GET /health returns 200api_status_endpoint— GET /status returns valid JSONapi_job_bid_rejects_zero_balance— POST /jobs/bid returns 402 when the calling node has zero balance (mine: false)signal_propagates_between_nodes— node A POSTs /signals/send with a burn_proof referencing one of its mined txs; non-mining node B sees the signal at GET /signals within 5 s of post (full CEP-1 + ce-mesh + chain-validation round trip)job_lifecycle(ignored, requires Docker) — full two-node job lifecycle: bid → host starts container → container exits → payer co-signs settlement → JobSettle confirmed on-chain → balances verified
Hetzner E2E tests (crates/ce-deploy/tests/e2e.rs)
Prerequisites
- Hetzner Cloud account with an API token (read+write)
- An SSH key registered in your Hetzner project
- Build the release binary:
cargo build --release - Set environment variables:
export HETZNER_API_TOKEN=hcloud-xxxxxxxxxx
export CE_SSH_KEY_NAME=my-hetzner-key-name
export CE_SSH_KEY_PATH=~/.ssh/id_ed25519Run
# All E2E tests
cargo test -p ce-deploy -- --ignored --nocapture
# Specific test
cargo test -p ce-deploy -- --ignored three_nodes_reach_consensus --nocaptureTest descriptions
three_nodes_reach_consensus
- Provisions 3
cx22servers in Nuremberg - Deploys CE binary to each
- Starts node 0 first, then 1 and 2 with bootstrap from node 0
- Waits for all nodes to reach height 5
- Asserts all nodes are within 2 blocks of each other
- Checks /health and /status on all nodes
- Tears down all servers
transaction_propagates_across_mesh
- 2-node cluster
- Waits for node 0 to accumulate mining balance
- Submits a job (POST /jobs/run) as payer
- Asserts 201 response
- Stops the job
- Tears down
late_join_node_syncs
- 2-node cluster builds chain to height 10
- Provisions a 3rd server
- Starts CE on it with bootstrap from node 0
- Verifies late-join node syncs to within 2 blocks
- Tears down 3rd server, then cluster
Cost
Each cx22 server is ~€0.007/hour. A full E2E run takes 5–15 minutes. Three tests × three servers each × 15 min ≈ €0.01 total. Servers are always deleted at teardown.
Writing new tests
Unit test (library crate)
Add to #[cfg(test)] mod tests in src/lib.rs. Use difficulty = 1 for any chain operations to keep them fast.
Integration test (node crate)
Add to crates/ce-node/tests/. Use alloc_ports() for port allocation. Mark slow tests with #[ignore] if they take > 10s.
E2E test (Hetzner)
Add to crates/ce-deploy/tests/e2e.rs. Always mark #[ignore]. Always call cluster.destroy().await in cleanup, even on failure. Use anyhow::Result return type and ? for error propagation.