Files
bitchat/bitchatTests
jackandClaude Fable 5 b481a70458 Prekeys: authenticate bundle packets, fix consume-republish, deflake CI
Fixes the prekey-bundle PR review + CI failure:

- CI root cause: the receive queue (mesh.message) is concurrent, so a
  gossiped prekey bundle can be processed before the announce that binds
  its owner's signing key. The old handler dropped such bundles outright,
  so under CI parallel load the bundle was permanently lost and the
  cache/gossip tests flaked (verifiedBundleEntersGossipStore,
  prekeySealedMailTravelsViaCourierAndOpens). Bundles that arrive before
  their binding are now retained per-owner (bounded) and re-attempted when
  the verified announce lands, atomically to avoid a check-then-act race.

- Authenticate the OUTER prekey-bundle packet (Codex P2 / review MEDIUM):
  require senderID == PeerID(bundle.noiseStaticPublicKey) and verify the
  packet's Ed25519 signature (covers senderID + timestamp) against the
  owner's bound signing key, in addition to the inner bundle signature.
  Stops replay under a fresh timestamp / fake senderID.

- Key the gossip prekey-bundle store/dedup by the bundle's authenticated
  identity (noiseStaticPublicKey), not the unauthenticated packet
  senderID, so one valid bundle sprayed under many fabricated sender IDs
  can't multiply entries and exhaust the 200-owner cap.

- Bump published-bundle generatedAt strictly on consume (Codex P1):
  consuming a prekey shrinks the published bundle, so it now republishes
  with a strictly newer generatedAt and re-gossips, so peers replace the
  cached copy and stop assigning the consumed ID before its 48h grace.

- Guard the panic/clear detached Application Support tree-deletes behind
  TestEnvironment.isRunningTests: the SPM test process shares that tree,
  so the wipe could land mid-test and flake file-dependent tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:27:41 +02:00
..

Test Harness Guide

This test suite uses an in-memory networking harness to make end-to-end and integration tests deterministic, fast, and race-free without touching production code.

In-Memory Bus

  • File: bitchatTests/Mocks/MockBLEService.swift
  • Registry/Adjacency: Global registry maps peerID to a MockBLEService instance; adjacency records simulated links between peers.
  • Setup: Call MockBLEService.resetTestBus() in setUp() to clear state between tests.
  • Topology: Use simulateConnectedPeer(_:) and simulateDisconnectedPeer(_:) to add/remove links. connectFullMesh() helpers in tests build larger topologies.
  • Handlers: Tests can observe data via messageDeliveryHandler (decoded BitchatMessage) and packetDeliveryHandler (raw BitchatPacket).
  • Deduplication: A thread-safe seenMessageIDs prevents duplicate deliveries during flooding/relays.

Broadcast Flooding

  • Flag: MockBLEService.autoFloodEnabled
  • Intent: When true, public broadcasts propagate across the entire connected component (ignores TTL for reach) while still deduping to prevent loops.
  • Usage: Enabled in Integration tests (setUp) to simulate large-network broadcast; disabled in E2E tests to keep routing explicit and verify TTL behavior (see PublicChatE2ETests.testZeroTTLNotRelayed).

Rehandshake Flow (Noise)

  • Why: The legacy NACK recovery path was removed; recovery now relies on Noise session rehandshake after decrypt failure or desync.
  • Manager: NoiseSessionManager manages per-peer sessions.
  • Pattern: On decrypt failure, proactively clear the local session and re-initiate a handshake. The peer accepts and replaces their session.
  • Test: IntegrationTests.testRehandshakeAfterDecryptionFailure
    • Corrupts ciphertext to induce a decrypt error.
    • Calls removeSession(for:) on the initiators manager before initiateHandshake(with:) to avoid alreadyEstablished.
    • Verifies encrypt/decrypt succeeds post-rehandshake.

Tips

  • Determinism: Add small async delays only where handler installation/topology changes could race the first send.
  • Scoping: Keep autoFloodEnabled toggled only within Integration tests; always reset in tearDown() to avoid cross-test contamination.
  • Direct vs Relay: Private messages target a specific peer when adjacent; otherwise they are surfaced to neighbors for relay and, if known, also delivered to the target.

Quick Start

  • Create nodes and connect them:
    • let svc = MockBLEService(); svc.myPeerID = "PEER1"
    • svc.simulateConnectedPeer("PEER2")
  • Observe messages:
    • svc.messageDeliveryHandler = { msg in /* asserts */ }
  • Enable broadcast flooding for Integration suites only:
    • MockBLEService.autoFloodEnabled = true