Фикс: бэкграунд звонки — аудио, имя на CallKit, deactivation order, UUID race
This commit is contained in:
193
RosettaTests/CallDisplayNameTests.swift
Normal file
193
RosettaTests/CallDisplayNameTests.swift
Normal file
@@ -0,0 +1,193 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user