import CryptoKit import XCTest @testable import Rosetta @MainActor final class CallRoutingTests: XCTestCase { private let ownKey = "02-own" private let peerA = "02-peer-a" private let peerB = "02-peer-b" override func setUp() { super.setUp() CallManager.shared.resetForSessionEnd() CallManager.shared.bindAccount(publicKey: ownKey) } override func tearDown() { CallManager.shared.resetForSessionEnd() super.tearDown() } func testIncomingCallMovesToIncomingPhase() { let packet = PacketSignalPeer( src: peerA, dst: ownKey, sharedPublic: "", signalType: .call, roomId: "" ) CallManager.shared.testHandleSignalPacket(packet) XCTAssertEqual(CallManager.shared.uiState.phase, .incoming) XCTAssertEqual(CallManager.shared.uiState.peerPublicKey, peerA) } func testBusySignalEndsCurrentCallStateWithoutCrosstalk() { CallManager.shared.testSetUiState( CallUiState( phase: .outgoing, peerPublicKey: peerA, statusText: "Calling..." ) ) let packet = PacketSignalPeer( src: "", dst: "", sharedPublic: "", signalType: .endCallBecauseBusy, roomId: "" ) CallManager.shared.testHandleSignalPacket(packet) XCTAssertEqual(CallManager.shared.uiState.phase, .idle) XCTAssertEqual(CallManager.shared.uiState.statusText, "User is busy") XCTAssertEqual(CallManager.shared.uiState.peerPublicKey, "") } func testPeerDisconnectedSignalEndsCurrentCallState() { CallManager.shared.testSetUiState( CallUiState( phase: .active, peerPublicKey: peerA, statusText: "Call active" ) ) let packet = PacketSignalPeer( src: "", dst: "", sharedPublic: "", signalType: .endCallBecausePeerDisconnected, roomId: "" ) CallManager.shared.testHandleSignalPacket(packet) XCTAssertEqual(CallManager.shared.uiState.phase, .idle) XCTAssertEqual(CallManager.shared.uiState.statusText, "Peer disconnected") XCTAssertEqual(CallManager.shared.uiState.peerPublicKey, "") } func testInterleavingForeignSignalDoesNotOverrideActivePeer() { CallManager.shared.testSetUiState( CallUiState( phase: .outgoing, peerPublicKey: peerA, statusText: "Calling..." ) ) let foreignPacket = PacketSignalPeer( src: peerB, dst: ownKey, sharedPublic: "", signalType: .call, roomId: "" ) CallManager.shared.testHandleSignalPacket(foreignPacket) XCTAssertEqual(CallManager.shared.uiState.phase, .outgoing) XCTAssertEqual(CallManager.shared.uiState.peerPublicKey, peerA) XCTAssertEqual(CallManager.shared.uiState.statusText, "Calling...") } func testLegacyOutgoingFlowCallAcceptKeyExchangeActive() { let start = CallManager.shared.startOutgoingCall( toPublicKey: peerA, title: "Peer A", username: "peer_a" ) XCTAssertEqual(start, .started) let accept = PacketSignalPeer( src: peerA, dst: ownKey, sharedPublic: "", signalType: .accept, callId: "call-legacy-1", joinToken: "join-legacy-1", roomId: "" ) CallManager.shared.testHandleSignalPacket(accept) XCTAssertEqual(CallManager.shared.signalingMode, .legacy) XCTAssertEqual(CallManager.shared.callId, "call-legacy-1") XCTAssertEqual(CallManager.shared.joinToken, "join-legacy-1") XCTAssertEqual(CallManager.shared.uiState.phase, .keyExchange) let keyExchange = PacketSignalPeer( src: peerA, dst: ownKey, sharedPublic: makePeerPublicHex(), signalType: .keyExchange, roomId: "" ) CallManager.shared.testHandleSignalPacket(keyExchange) XCTAssertEqual(CallManager.shared.uiState.phase, .keyExchange) // Legacy ACTIVE arrives as signal code 4 with empty roomId. let active = PacketSignalPeer( src: peerA, dst: ownKey, sharedPublic: "", signalType: .createRoom, roomId: "" ) CallManager.shared.testHandleSignalPacket(active) XCTAssertEqual(CallManager.shared.uiState.phase, .webRtcExchange) } func testCreateRoomFallbackOutgoingFlowKeyExchangeBeforeAccept() { let start = CallManager.shared.startOutgoingCall( toPublicKey: peerA, title: "Peer A", username: "peer_a" ) XCTAssertEqual(start, .started) let keyExchange = PacketSignalPeer( src: peerA, dst: ownKey, sharedPublic: makePeerPublicHex(), signalType: .keyExchange, roomId: "" ) CallManager.shared.testHandleSignalPacket(keyExchange) XCTAssertEqual(CallManager.shared.signalingMode, .createRoom) XCTAssertEqual(CallManager.shared.uiState.phase, .webRtcExchange) } func testDeferredAcceptCompletesWhenMetadataArrivesLater() { CallManager.shared.setupIncomingCallFromPush( callerKey: peerA, callerName: "Peer A" ) XCTAssertEqual(CallManager.shared.uiState.phase, .incoming) let acceptResult = CallManager.shared.acceptIncomingCall() XCTAssertEqual(acceptResult, .started) XCTAssertTrue(CallManager.shared.pendingIncomingAccept) XCTAssertEqual(CallManager.shared.uiState.phase, .keyExchange) XCTAssertEqual(CallManager.shared.signalingMode, .undecided) let delayedCall = PacketSignalPeer( src: peerA, dst: ownKey, sharedPublic: "", signalType: .call, callId: "call-delayed-1", joinToken: "join-delayed-1", roomId: "" ) CallManager.shared.testHandleSignalPacket(delayedCall) XCTAssertEqual(CallManager.shared.signalingMode, .legacy) XCTAssertFalse(CallManager.shared.pendingIncomingAccept) XCTAssertEqual(CallManager.shared.callId, "call-delayed-1") XCTAssertEqual(CallManager.shared.joinToken, "join-delayed-1") XCTAssertEqual(CallManager.shared.uiState.phase, .keyExchange) } func testRingingTimeoutSignalTearsDownCall() { let start = CallManager.shared.startOutgoingCall( toPublicKey: peerA, title: "Peer A", username: "peer_a" ) XCTAssertEqual(start, .started) let timeoutPacket = PacketSignalPeer( src: "", dst: "", sharedPublic: "", signalType: .ringingTimeout, roomId: "" ) CallManager.shared.testHandleSignalPacket(timeoutPacket) XCTAssertEqual(CallManager.shared.uiState.phase, .idle) XCTAssertEqual(CallManager.shared.uiState.statusText, "No answer") } private func makePeerPublicHex() -> String { Curve25519.KeyAgreement.PrivateKey().publicKey.rawRepresentation.hexString } }