Stabilize ChatViewModel test Tor lifecycle

This commit is contained in:
Dev 2026-06-04 13:23:22 +03:00
parent 6893c8377d
commit 14fbbc6c5f
6 changed files with 44 additions and 14 deletions

View File

@ -90,6 +90,16 @@ import UIKit
#endif
import UniformTypeIdentifiers
struct ChatViewModelTorLifecycle {
var torEnforced: @MainActor () -> Bool
var isAutoStartAllowed: @MainActor () -> Bool
static let live = ChatViewModelTorLifecycle(
torEnforced: { TorManager.shared.torEnforced },
isAutoStartAllowed: { TorManager.shared.isAutoStartAllowed() }
)
}
/// Manages the application state and business logic for BitChat.
/// Acts as the primary coordinator between UI components and backend services,
/// implementing the BitchatDelegate protocol to handle network events.
@ -268,6 +278,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, CommandContextProv
let idBridge: NostrIdentityBridge
let identityManager: SecureIdentityStateManagerProtocol
let ndrService: NdrNostrService
let torLifecycle: ChatViewModelTorLifecycle
var nostrRelayManager: NostrRelayManager?
private let userDefaults = UserDefaults.standard
@ -397,14 +408,16 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, CommandContextProv
keychain: KeychainManagerProtocol,
idBridge: NostrIdentityBridge,
identityManager: SecureIdentityStateManagerProtocol,
ndrService: NdrNostrService? = nil
ndrService: NdrNostrService? = nil,
torLifecycle: ChatViewModelTorLifecycle = .live
) {
self.init(
keychain: keychain,
idBridge: idBridge,
identityManager: identityManager,
transport: BLEService(keychain: keychain, idBridge: idBridge, identityManager: identityManager),
ndrService: ndrService
ndrService: ndrService,
torLifecycle: torLifecycle
)
}
@ -416,13 +429,15 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, CommandContextProv
idBridge: NostrIdentityBridge,
identityManager: SecureIdentityStateManagerProtocol,
transport: Transport,
ndrService: NdrNostrService? = nil
ndrService: NdrNostrService? = nil,
torLifecycle: ChatViewModelTorLifecycle = .live
) {
let resolvedNdrService = ndrService ?? NdrNostrService.shared
self.keychain = keychain
self.idBridge = idBridge
self.identityManager = identityManager
self.ndrService = resolvedNdrService
self.torLifecycle = torLifecycle
self.meshService = transport
self.publicMessagePipeline = PublicMessagePipeline()
@ -502,12 +517,12 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, CommandContextProv
}
// Announce Tor status (geohash-only; do not show in mesh chat). Only when auto-start is allowed.
if TorManager.shared.torEnforced && !torStatusAnnounced && TorManager.shared.isAutoStartAllowed() {
if torLifecycle.torEnforced() && !torStatusAnnounced && torLifecycle.isAutoStartAllowed() {
torStatusAnnounced = true
addGeohashOnlySystemMessage(
String(localized: "system.tor.starting", comment: "System message when Tor is starting")
)
} else if !TorManager.shared.torEnforced && !torStatusAnnounced {
} else if !torLifecycle.torEnforced() && !torStatusAnnounced {
torStatusAnnounced = true
addGeohashOnlySystemMessage(
String(localized: "system.tor.dev_bypass", comment: "System message when Tor bypass is enabled in development")

View File

@ -15,7 +15,7 @@ extension ChatViewModel {
@objc func handleTorWillStart() {
Task { @MainActor in
if !self.torStatusAnnounced && TorManager.shared.torEnforced {
if !self.torStatusAnnounced && self.torLifecycle.torEnforced() {
self.torStatusAnnounced = true
// Post only in geohash channels (queue if not active)
self.addGeohashOnlySystemMessage(
@ -44,7 +44,7 @@ extension ChatViewModel {
String(localized: "system.tor.restarted", comment: "System message when Tor has restarted")
)
self.torRestartPending = false
} else if TorManager.shared.torEnforced && !self.torInitialReadyAnnounced {
} else if self.torLifecycle.torEnforced() && !self.torInitialReadyAnnounced {
// Initial start completed
self.addGeohashOnlySystemMessage(
String(localized: "system.tor.started", comment: "System message when Tor has started")

View File

@ -25,12 +25,17 @@ private func makeTestableViewModel() -> (viewModel: ChatViewModel, transport: Mo
let idBridge = NostrIdentityBridge(keychain: keychainHelper)
let identityManager = MockIdentityManager(keychain)
let transport = MockTransport()
let torLifecycle = ChatViewModelTorLifecycle(
torEnforced: { true },
isAutoStartAllowed: { false }
)
let viewModel = ChatViewModel(
keychain: keychain,
idBridge: idBridge,
identityManager: identityManager,
transport: transport
transport: transport,
torLifecycle: torLifecycle
)
return (viewModel, transport)

View File

@ -318,7 +318,7 @@ struct ChatViewModelPrivateChatTests {
@Test @MainActor
func sendPrivateMessage_delegatesToTransport() async {
let (viewModel, transport) = makeTestableViewModel()
let recipientID = PeerID(str: "RECIPIENT")
let recipientID = PeerID(str: "0000000000000001")
// Set up connected peer for routing
transport.connectedPeers.insert(recipientID)
@ -326,9 +326,9 @@ struct ChatViewModelPrivateChatTests {
viewModel.sendPrivateMessage("Secret message", to: recipientID)
// The message routing depends on connection state and other factors
// At minimum, it should not crash
#expect(true) // If we get here without crash, the test passes
#expect(transport.sentPrivateMessages.count == 1)
#expect(transport.sentPrivateMessages.first?.content == "Secret message")
#expect(transport.sentPrivateMessages.first?.peerID == recipientID)
}
}

View File

@ -18,12 +18,17 @@ private func makeTestableViewModel() -> (viewModel: ChatViewModel, transport: Mo
let idBridge = NostrIdentityBridge(keychain: keychainHelper)
let identityManager = MockIdentityManager(keychain)
let transport = MockTransport()
let torLifecycle = ChatViewModelTorLifecycle(
torEnforced: { true },
isAutoStartAllowed: { false }
)
let viewModel = ChatViewModel(
keychain: keychain,
idBridge: idBridge,
identityManager: identityManager,
transport: transport
transport: transport,
torLifecycle: torLifecycle
)
return (viewModel, transport)

View File

@ -286,12 +286,17 @@ struct ChatViewModelPresenceHandlingTests {
let idBridge = NostrIdentityBridge(keychain: keychainHelper)
let identityManager = MockIdentityManager(keychain)
let transport = MockTransport()
let torLifecycle = ChatViewModelTorLifecycle(
torEnforced: { true },
isAutoStartAllowed: { false }
)
let viewModel = ChatViewModel(
keychain: keychain,
idBridge: idBridge,
identityManager: identityManager,
transport: transport
transport: transport,
torLifecycle: torLifecycle
)
return (viewModel, transport)