Files
bitchat/bitchatTests/Mocks/MockBLEService.swift
T
ef848857b7 Remove dead code found by full Periphery audit; add scan config + advisory CI (#1410)
Periphery 3.7.4 audit of both schemes (macOS + iOS, intersected so
platform-specific code is never touched), with test targets indexed and
the share extension built. 277 dead declarations removed or demoted:
dead forwarding wrappers (ChatViewModel+Nostr/+PrivateChat), removed-
feature remnants (autocomplete command suggestions, back-swipe tuning,
MediaSendError, GeohashParticipantTracker), unused Tor dormancy
bindings, assign-only properties, unused parameters (renamed to _), and
redundant public accessibility. 13 orphaned localization keys deleted
across all 29 locales (old pre-#1392 location-notes UI, app_info
warnings).

Two real tests were flagged as unused because they never ran: Swift
Testing methods missing @Test (NostrProtocolTests.
testAckRoundTripNIP44V2_Delivered, NotificationStreamAssemblerTests.
testAssemblesCompressedLargeFrame). Re-armed both; they pass.

Deliberately kept, now recorded in .periphery.baseline.json: iOS-only
code invisible to the CI macOS scan, C FFI signatures, keep-alive
NWPathMonitor reference, InboundEventKey.eventID (dedup semantics),
wifiBulk capability bit (reserved for Wi-Fi bulk work, used by
BitFoundation package tests), and the String secureClear cluster
(exercised by package tests).

New: .periphery.yml config and an advisory Dead Code CI job (mirrors
the SwiftLint precedent from #1361) that fails on findings not in the
committed baseline.

Verified: full macOS app suite, BitFoundation (119) and BitLogger (13)
package tests green; periphery scan --strict exits clean.

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 11:24:19 +02:00

272 lines
9.5 KiB
Swift

//
// MockBLEService.swift
// bitchatTests
//
// This is free and unencumbered software released into the public domain.
// For more information, see <https://unlicense.org>
//
import Foundation
import CoreBluetooth
@testable import BitFoundation // to avoid unnecessary public's
@testable import bitchat
/// In-memory BLE test harness used by E2E/Integration tests.
///
/// Design:
/// - Global `registry` maps `peerID` -> service instance, and `adjacency` tracks
/// simulated connections between peers. Tests call `simulateConnectedPeer` /
/// `simulateDisconnectedPeer` to manage topology.
/// - `resetTestBus()` clears global state and is called in test `setUp()`.
/// - `messageDeliveryHandler` and `packetDeliveryHandler` let tests observe messages/packets
/// as they flow, enabling scenarios like manual encryption/relay.
/// - A thread-safe `seenMessageIDs` set prevents double-delivery races during flooding.
///
/// Flooding:
/// - `autoFloodEnabled` is disabled by default; Integration tests enable it in `setUp()` to
/// simulate broadcast propagation across the mesh. E2E tests keep it off and perform explicit
/// relays when needed.
final class MockBLEService: NSObject {
private let bus: MockBLEBus
// MARK: - Properties matching BLEService
weak var delegate: BitchatDelegate?
var myPeerID = PeerID(str: "MOCK1234")
var myNickname: String = "MockUser"
// Test-specific properties
var sentMessages: [(message: BitchatMessage, packet: BitchatPacket)] = []
var sentPackets: [BitchatPacket] = []
var connectedPeers: Set<PeerID> = []
var messageDeliveryHandler: ((BitchatMessage) -> Void)?
var packetDeliveryHandler: ((BitchatPacket) -> Void)?
// Compatibility properties for old tests
var mockNickname: String {
get { return myNickname }
set { myNickname = newValue }
}
var peerID: PeerID {
return myPeerID
}
// MARK: - Initialization
init(bus: MockBLEBus) {
self.bus = bus
}
// MARK: - In-memory test bus (for E2E/Integration)
/// Registers this instance on first use.
private func registerIfNeeded() {
bus.register(self, for: myPeerID)
}
/// Returns adjacent neighbors based on the current simulated topology.
private func neighbors() -> [MockBLEService] {
bus.neighbors(of: myPeerID)
}
func startServices() {
// Mock implementation - do nothing
}
func stopServices() {
// Mock implementation - do nothing
}
func isPeerConnected(_ peerID: PeerID) -> Bool {
return connectedPeers.contains(peerID)
}
func getPeerNicknames() -> [PeerID: String] {
var nicknames: [PeerID: String] = [:]
for peer in connectedPeers {
nicknames[peer] = "MockPeer_\(peer)"
}
return nicknames
}
/// Keep local echo synchronous so Swift Testing confirmations observe it deterministically.
private func deliverLocalEcho(_ message: BitchatMessage) {
delegate?.didReceiveMessage(message)
}
func sendMessage(_ content: String, mentions: [String] = [], to recipientID: String? = nil, messageID: String? = nil, timestamp: Date? = nil) {
let message = BitchatMessage(
id: messageID ?? UUID().uuidString,
sender: myNickname,
content: content,
timestamp: timestamp ?? Date(),
isRelay: false,
originalSender: nil,
isPrivate: recipientID != nil,
recipientNickname: nil,
senderPeerID: myPeerID,
mentions: mentions.isEmpty ? nil : mentions
)
if let payload = message.toBinaryPayload() {
let packet = BitchatPacket(
type: 0x01,
senderID: myPeerID.id.data(using: .utf8)!,
recipientID: recipientID?.data(using: .utf8),
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: payload,
signature: nil,
ttl: 3
)
sentMessages.append((message, packet))
sentPackets.append(packet)
deliverLocalEcho(message)
// Surface raw packet to tests that intercept/relay/encrypt
packetDeliveryHandler?(packet)
// Deliver public messages to adjacent peers via bus
if recipientID == nil {
for neighbor in neighbors() {
neighbor.simulateIncomingPacket(packet)
}
}
}
}
func sendPrivateMessage(_ content: String, to recipientPeerID: PeerID, recipientNickname: String, messageID: String) {
let message = BitchatMessage(
id: messageID,
sender: myNickname,
content: content,
timestamp: Date(),
isRelay: false,
originalSender: nil,
isPrivate: true,
recipientNickname: recipientNickname,
senderPeerID: myPeerID,
mentions: nil
)
if let payload = message.toBinaryPayload() {
let packet = BitchatPacket(
type: 0x01,
senderID: myPeerID.id.data(using: .utf8)!,
recipientID: recipientPeerID.id.data(using: .utf8)!,
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: payload,
signature: nil,
ttl: 3
)
sentMessages.append((message, packet))
sentPackets.append(packet)
deliverLocalEcho(message)
// Surface raw packet to tests that intercept/relay/encrypt
packetDeliveryHandler?(packet)
// If directly connected to recipient, deliver only to them.
if bus.isDirectNeighbor(myPeerID, recipientPeerID),
let target = bus.service(for: recipientPeerID) {
target.simulateIncomingPacket(packet)
} else {
// Not directly connected: deliver to neighbors for relay
for neighbor in neighbors() where neighbor.peerID != recipientPeerID {
neighbor.simulateIncomingPacket(packet)
}
}
}
}
// MARK: - Test Helper Methods
func simulateConnectedPeer(_ peerID: PeerID) {
registerIfNeeded()
bus.connect(myPeerID, peerID)
connectedPeers.insert(peerID)
delegate?.didConnectToPeer(peerID)
delegate?.didUpdatePeerList(Array(connectedPeers))
}
func simulateDisconnectedPeer(_ peerID: PeerID) {
bus.disconnect(myPeerID, peerID)
connectedPeers.remove(peerID)
delegate?.didDisconnectFromPeer(peerID)
delegate?.didUpdatePeerList(Array(connectedPeers))
}
func simulateIncomingMessage(_ message: BitchatMessage) {
delegate?.didReceiveMessage(message)
// Also surface via test handler for E2E/Integration
messageDeliveryHandler?(message)
}
private var seenMessageIDs: Set<String> = []
private let seenLock = NSLock()
func simulateIncomingPacket(_ packet: BitchatPacket) {
// Process through the actual handling logic
if let message = BitchatMessage(packet.payload) {
var shouldDeliver = false
seenLock.lock()
if !seenMessageIDs.contains(message.id) {
seenMessageIDs.insert(message.id)
shouldDeliver = true
}
seenLock.unlock()
if shouldDeliver {
delegate?.didReceiveMessage(message)
// Also surface via test handler for E2E/Integration
messageDeliveryHandler?(message)
// Optional flooding for integration-style broadcast tests.
// When enabled, propagate a public broadcast across the entire connected
// component regardless of the original TTL to better emulate large-network
// broadcast expectations. De-duplication via seenMessageIDs prevents loops.
if bus.autoFloodEnabled,
packet.recipientID == nil,
!message.isPrivate {
let nextTTL = packet.ttl > 0 ? packet.ttl - 1 : 0
for neighbor in neighbors() {
// Avoid immediate echo loopback to sender if known
if let sender = message.senderPeerID, sender == neighbor.peerID { continue }
var relay = packet
relay.ttl = nextTTL
neighbor.simulateIncomingPacket(relay)
}
}
}
}
packetDeliveryHandler?(packet)
}
func getConnectedPeers() -> [PeerID] {
return Array(connectedPeers)
}
// MARK: - Compatibility methods for old tests
func sendPrivateMessage(_ content: String, to recipientPeerID: PeerID, recipientNickname: String, messageID: String? = nil) {
sendPrivateMessage(content, to: recipientPeerID, recipientNickname: recipientNickname, messageID: messageID ?? UUID().uuidString)
}
}
// MARK: - Helpers
extension MockBLEService {
convenience init(peerID: PeerID, nickname: String, bus: MockBLEBus) {
self.init(bus: bus)
myPeerID = peerID
mockNickname = nickname
}
func simulateConnection(with otherPeer: MockBLEService) {
simulateConnectedPeer(otherPeer.myPeerID)
otherPeer.simulateConnectedPeer(myPeerID)
}
}