import BitFoundation import Foundation import Testing @testable import bitchat @Suite("BLE peer registry tests") struct BLEPeerRegistryTests { @Test("upserted announces track new, reconnect, and rename transitions") func upsertVerifiedAnnounceTracksTransitions() { var registry = BLEPeerRegistry() let peerID = PeerID(str: "1122334455667788") let firstSeen = Date(timeIntervalSince1970: 100) let first = registry.upsertVerifiedAnnounce( peerID: peerID, nickname: "alice", noisePublicKey: Data([1, 2, 3]), signingPublicKey: Data([4, 5, 6]), isConnected: true, now: firstSeen ) #expect(first.isNewPeer) #expect(!first.wasDisconnected) #expect(first.previousNickname == nil) #expect(registry.connectedPeerIDs == [peerID]) #expect(registry.nickname(for: peerID, connectedOnly: true) == "alice") registry.markDisconnected(peerID) let reconnect = registry.upsertVerifiedAnnounce( peerID: peerID, nickname: "alice-renamed", noisePublicKey: Data([1, 2, 3]), signingPublicKey: Data([4, 5, 6]), isConnected: true, now: firstSeen.addingTimeInterval(1) ) #expect(!reconnect.isNewPeer) #expect(reconnect.wasDisconnected) #expect(reconnect.previousNickname == "alice") #expect(registry.info(for: peerID)?.nickname == "alice-renamed") } @Test("registry preserves absent versus explicit empty capabilities") func capabilitiesPresenceIsPreserved() { var registry = BLEPeerRegistry() let oldPeer = PeerID(str: "1122334455667788") let modernPeer = PeerID(str: "8877665544332211") _ = registry.upsertVerifiedAnnounce( peerID: oldPeer, nickname: "old", noisePublicKey: Data(repeating: 0x11, count: 32), signingPublicKey: Data(repeating: 0x12, count: 32), isConnected: true, now: Date(), capabilities: nil ) _ = registry.upsertVerifiedAnnounce( peerID: modernPeer, nickname: "modern", noisePublicKey: Data(repeating: 0x21, count: 32), signingPublicKey: Data(repeating: 0x22, count: 32), isConnected: true, now: Date(), capabilities: [] ) #expect(registry.capabilities(for: oldPeer).isEmpty) #expect(!registry.capabilitiesWereExplicitlyAdvertised(for: oldPeer)) #expect(registry.capabilities(for: modernPeer).isEmpty) #expect(registry.capabilitiesWereExplicitlyAdvertised(for: modernPeer)) } @Test("reachability keeps recent verified offline peers only when mesh is attached") func reachabilityRequiresMeshAttachmentForOfflinePeers() { let offlinePeer = PeerID(str: "1122334455667788") let connectedPeer = PeerID(str: "8877665544332211") let now = Date() var isolatedRegistry = BLEPeerRegistry() isolatedRegistry.upsert(BLEPeerInfo( peerID: offlinePeer, nickname: "offline", isConnected: false, noisePublicKey: nil, signingPublicKey: nil, isVerifiedNickname: true, lastSeen: now )) #expect(!isolatedRegistry.isReachable(offlinePeer, now: now)) var attachedRegistry = isolatedRegistry attachedRegistry.upsert(BLEPeerInfo( peerID: connectedPeer, nickname: "connected", isConnected: true, noisePublicKey: nil, signingPublicKey: nil, isVerifiedNickname: true, lastSeen: now )) #expect(attachedRegistry.isReachable(offlinePeer, now: now)) } @Test("connectivity reconciliation disconnects inactive peers and prunes expired offline peers") func reconcileConnectivityUpdatesAndPrunesPeerState() { var registry = BLEPeerRegistry() let inactiveConnectedPeer = PeerID(str: "1122334455667788") let expiredOfflinePeer = PeerID(str: "8877665544332211") let now = Date() registry.upsert(BLEPeerInfo( peerID: inactiveConnectedPeer, nickname: "inactive", isConnected: true, noisePublicKey: nil, signingPublicKey: nil, isVerifiedNickname: true, lastSeen: now.addingTimeInterval(-TransportConfig.blePeerInactivityTimeoutSeconds - 1) )) registry.upsert(BLEPeerInfo( peerID: expiredOfflinePeer, nickname: "expired", isConnected: false, noisePublicKey: nil, signingPublicKey: nil, isVerifiedNickname: true, lastSeen: .distantPast )) let changes = registry.reconcileConnectivity(now: now, linkStates: [:]) #expect(changes.disconnectedPeerIDs == [inactiveConnectedPeer]) #expect(changes.removedPeers.map(\.peerID) == [expiredOfflinePeer]) #expect(registry.info(for: inactiveConnectedPeer)?.isConnected == false) #expect(registry.info(for: expiredOfflinePeer) == nil) } }