import XCTest @testable import Rosetta @MainActor final class CallDisplayNameTests: XCTestCase { private let ownKey = "02-own" private let peerKey = "0263e9134d3abeb880bb5fa679954800a80d0c286c5c54c9452a996b0c7608db3" override func setUp() { super.setUp() CallManager.shared.resetForSessionEnd() CallManager.shared.bindAccount(publicKey: ownKey) } override func tearDown() { CallManager.shared.resetForSessionEnd() super.tearDown() } // MARK: - CallUiState.displayName fallback chain func testDisplayNamePrefersTitle() { let state = CallUiState( peerPublicKey: peerKey, peerTitle: "Alice", peerUsername: "alice" ) XCTAssertEqual(state.displayName, "Alice") } func testDisplayNameFallsBackToUsername() { let state = CallUiState( peerPublicKey: peerKey, peerTitle: "", peerUsername: "alice" ) XCTAssertEqual(state.displayName, "@alice") } func testDisplayNameFallsBackToKeyPrefix() { let state = CallUiState( peerPublicKey: peerKey, peerTitle: "", peerUsername: "" ) XCTAssertEqual(state.displayName, String(peerKey.prefix(12))) } func testDisplayNameReturnsUnknownWhenEmpty() { let state = CallUiState() XCTAssertEqual(state.displayName, "Unknown") } // MARK: - Outgoing call sets displayName before CallKit func testOutgoingCallSetsDisplayNameFromTitle() { let result = CallManager.shared.startOutgoingCall( toPublicKey: peerKey, title: "Alice", username: "alice" ) XCTAssertEqual(result, .started) // After startOutgoingCall, uiState should have peerTitle set XCTAssertEqual(CallManager.shared.uiState.peerTitle, "Alice") XCTAssertEqual(CallManager.shared.uiState.displayName, "Alice") } func testOutgoingCallWithEmptyTitleUsesUsername() { let result = CallManager.shared.startOutgoingCall( toPublicKey: peerKey, title: "", username: "alice" ) XCTAssertEqual(result, .started) XCTAssertEqual(CallManager.shared.uiState.displayName, "@alice") } func testOutgoingCallWithNoIdentityUsesKeyPrefix() { let result = CallManager.shared.startOutgoingCall( toPublicKey: peerKey, title: "", username: "" ) XCTAssertEqual(result, .started) // Should NOT be the full key — must be truncated prefix let displayName = CallManager.shared.uiState.displayName XCTAssertEqual(displayName, String(peerKey.prefix(12))) XCTAssertNotEqual(displayName, peerKey, "Full public key must NEVER be used as display name") } // MARK: - Incoming call name hydration func testIncomingCallDisplayNameDefaultsToKeyPrefix() { // Incoming calls start with empty title (signal packet has no name field) let packet = PacketSignalPeer( src: peerKey, dst: ownKey, sharedPublic: "", signalType: .call, roomId: "" ) CallManager.shared.testHandleSignalPacket(packet) XCTAssertEqual(CallManager.shared.uiState.phase, .incoming) // Without DialogRepository entry, name falls back to key prefix let displayName = CallManager.shared.uiState.displayName XCTAssertTrue(displayName.count <= 12 || displayName == "Unknown", "Incoming call display name should be short prefix, not full key: \(displayName)") } // MARK: - Server parity: signal packet has NO name field func testIncomingCallFromServerHasNoNameInPacket() { // Server PacketSignalPeer (0x1A) sends only src/dst public keys — no title. // Verify that after signal processing, displayName is NOT the full public key. let packet = PacketSignalPeer( src: peerKey, dst: ownKey, sharedPublic: "", signalType: .call, roomId: "" ) CallManager.shared.testHandleSignalPacket(packet) let displayName = CallManager.shared.uiState.displayName XCTAssertNotEqual(displayName, peerKey, "Server sends no name — displayName must NOT be full 66-char public key") XCTAssertTrue(displayName.count < 20, "Display name should be short (prefix or 'Unknown'), got: \(displayName)") } // MARK: - Outgoing call: displayName available BEFORE CallKit report func testDisplayNameSetBeforeCallKitReport() { // beginCallSession is called BEFORE startOutgoingCall(peerKey:displayName:) // so uiState.displayName must already be resolved when CallKit is invoked. let result = CallManager.shared.startOutgoingCall( toPublicKey: peerKey, title: "Bob", username: "bob" ) XCTAssertEqual(result, .started) // After startOutgoingCall, peerTitle should be "Bob" (set by beginCallSession) XCTAssertEqual(CallManager.shared.uiState.peerTitle, "Bob") // CallKit was called with displayName = "Bob" (verified by the flow: // beginCallSession sets peerTitle → startOutgoingCall reads uiState.displayName) } // MARK: - Full key never leaks as display name func testFullKeyNeverUsedInAnyScenario() { // Scenario 1: outgoing with title _ = CallManager.shared.startOutgoingCall( toPublicKey: peerKey, title: "Alice", username: "" ) XCTAssertNotEqual(CallManager.shared.uiState.displayName, peerKey) CallManager.shared.resetForSessionEnd() CallManager.shared.bindAccount(publicKey: ownKey) // Scenario 2: outgoing with username only _ = CallManager.shared.startOutgoingCall( toPublicKey: peerKey, title: "", username: "alice" ) XCTAssertNotEqual(CallManager.shared.uiState.displayName, peerKey) CallManager.shared.resetForSessionEnd() CallManager.shared.bindAccount(publicKey: ownKey) // Scenario 3: outgoing with nothing _ = CallManager.shared.startOutgoingCall( toPublicKey: peerKey, title: "", username: "" ) XCTAssertNotEqual(CallManager.shared.uiState.displayName, peerKey) CallManager.shared.resetForSessionEnd() CallManager.shared.bindAccount(publicKey: ownKey) // Scenario 4: incoming via signal let packet = PacketSignalPeer( src: peerKey, dst: ownKey, sharedPublic: "", signalType: .call, roomId: "" ) CallManager.shared.testHandleSignalPacket(packet) XCTAssertNotEqual(CallManager.shared.uiState.displayName, peerKey) } // MARK: - updateCallerName (CallKit update after hydration) func testUpdateCallerNameIsAvailable() { // Verify the method exists and doesn't crash with empty state CallKitManager.shared.updateCallerName("") // should be a no-op CallKitManager.shared.updateCallerName("Alice") // no UUID → no-op, but shouldn't crash } }