bitchat/bitchatTests
ecgang 53b74bd1e9 Let private groups restore, and stop claiming geoDM restore works
Two gaps a review found in the launch-restore predicate.

A private group could never restore. A group id is `group_` plus 32 hex, so
both peer lookups guard on `isShort` and return empty without ever consulting
the group — leaving `isDirectChatRestorable` to refuse every group, silently,
every time. That is backwards: `startPrivateChat` gates group re-entry on
nothing at all, because a group is local state rather than a claim about
reaching a peer, so there is no phantom to guard against. Groups are now
admitted on their own branch, ahead of the peer terms and behind the block
veto. One test pins the admission and a second pins that neither peer term is
consulted, so a group cannot be let in by an unrelated term happening to
match.

The geoDM reasoning was also describing behaviour the code does not have. It
claimed a geoDM restores on its own favorite record; in fact
`FavoritesPersistenceService` is keyed by Noise public key alone, and
`getFavoriteStatus(forPeerID:)` matches by rebuilding `PeerID(publicKey:)`,
which carries no prefix and so can never equal a `nostr_`-prefixed id. A
geoDM therefore never restores today. The branch is the right shape for the
day a Nostr-keyed lookup exists; until then it resolves to false. Documented
rather than fixed, because adding that lookup means new favorites plumbing
and this change does not touch that service.

Mutation-proven: removing the group branch fails both group tests and
nothing else, while the blocked-group test keeps passing on its own veto.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 13:30:15 -07: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