Files
mobile-ios/RosettaTests/CallDisplayNameTests.swift

194 lines
7.1 KiB
Swift

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
}
}