mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-24 21:45:20 +00:00
* Add comprehensive test coverage for ChatViewModel and BLEService This commit adds 14 new tests to improve the safety net for future refactoring of ChatViewModel and BLEService: New test files: - ChatViewModelTorTests: Tor lifecycle notification handlers (8 tests) - ChatViewModelDeliveryStatusTests: Delivery status state machine (6 tests) - ChatViewModelRefactoringTests: Command routing and message handling (4 tests) - BLEServiceCoreTests: Packet deduplication and stale broadcast filtering (2 tests) - PublicMessagePipelineTests: Message ordering and deduplication (4 tests) - MessageRouterTests: Transport selection and outbox behavior (4 tests) - PrivateChatManagerTests: Chat selection and read receipts (2 tests) - UnifiedPeerServiceTests: Fingerprint resolution and blocking (2 tests) - RelayControllerTests: TTL, handshake, and fragment relay logic (4 tests) Modified files: - MockTransport: Added peer snapshot publishing on connect/disconnect - TestHelpers: Added waitUntil polling helper for async tests - MockIdentityManager: Extended for new test scenarios Total: 454 tests across 64 suites (was 440) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix flaky test by using waitUntil instead of fixed sleep Replace fixed 200ms sleep with waitUntil helper for more reliable async assertion in routing tests. This prevents timing issues on CI. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
179 lines
5.4 KiB
Swift
179 lines
5.4 KiB
Swift
//
|
|
// ChatViewModelTorTests.swift
|
|
// bitchatTests
|
|
//
|
|
// Tests for ChatViewModel+Tor.swift Tor lifecycle notification handlers.
|
|
//
|
|
|
|
import Testing
|
|
import Foundation
|
|
@testable import bitchat
|
|
|
|
// MARK: - Test Helpers
|
|
|
|
@MainActor
|
|
private func makeTestableViewModel() -> (viewModel: ChatViewModel, transport: MockTransport) {
|
|
let keychain = MockKeychain()
|
|
let keychainHelper = MockKeychainHelper()
|
|
let idBridge = NostrIdentityBridge(keychain: keychainHelper)
|
|
let identityManager = MockIdentityManager(keychain)
|
|
let transport = MockTransport()
|
|
|
|
let viewModel = ChatViewModel(
|
|
keychain: keychain,
|
|
idBridge: idBridge,
|
|
identityManager: identityManager,
|
|
transport: transport
|
|
)
|
|
|
|
return (viewModel, transport)
|
|
}
|
|
|
|
// MARK: - Tor Notification Handler Tests
|
|
|
|
struct ChatViewModelTorTests {
|
|
|
|
// MARK: - handleTorWillStart Tests
|
|
|
|
@Test @MainActor
|
|
func handleTorWillStart_whenEnforced_setsAnnouncedFlag() async {
|
|
let (viewModel, _) = makeTestableViewModel()
|
|
|
|
// Precondition: flag should start false
|
|
#expect(!viewModel.torStatusAnnounced)
|
|
|
|
// Action: simulate Tor starting notification
|
|
viewModel.handleTorWillStart()
|
|
|
|
// Wait for Task to complete
|
|
try? await Task.sleep(nanoseconds: 100_000_000)
|
|
|
|
// Assert: flag should be set (torEnforced is true in tests)
|
|
#expect(viewModel.torStatusAnnounced)
|
|
}
|
|
|
|
@Test @MainActor
|
|
func handleTorWillStart_whenAlreadyAnnounced_doesNotDuplicate() async {
|
|
let (viewModel, _) = makeTestableViewModel()
|
|
|
|
// Setup: pre-set the flag
|
|
viewModel.torStatusAnnounced = true
|
|
|
|
// Switch to a geohash channel so messages would be visible
|
|
viewModel.switchLocationChannel(to: .location(GeohashChannel(level: .city, geohash: "u4pruydq")))
|
|
try? await Task.sleep(nanoseconds: 100_000_000)
|
|
|
|
let initialMessageCount = viewModel.messages.count
|
|
|
|
// Action: call handler again
|
|
viewModel.handleTorWillStart()
|
|
try? await Task.sleep(nanoseconds: 100_000_000)
|
|
|
|
// Assert: no new message added (flag was already true)
|
|
#expect(viewModel.messages.count == initialMessageCount)
|
|
}
|
|
|
|
// MARK: - handleTorWillRestart Tests
|
|
|
|
@Test @MainActor
|
|
func handleTorWillRestart_setsPendingFlag() async {
|
|
let (viewModel, _) = makeTestableViewModel()
|
|
|
|
// Precondition
|
|
#expect(!viewModel.torRestartPending)
|
|
|
|
// Action
|
|
viewModel.handleTorWillRestart()
|
|
try? await Task.sleep(nanoseconds: 100_000_000)
|
|
|
|
// Assert
|
|
#expect(viewModel.torRestartPending)
|
|
}
|
|
|
|
@Test @MainActor
|
|
func handleTorWillRestart_setsFlag_regardlessOfChannel() async {
|
|
let (viewModel, _) = makeTestableViewModel()
|
|
|
|
// Action: call handler (works regardless of channel)
|
|
viewModel.handleTorWillRestart()
|
|
try? await Task.sleep(nanoseconds: 100_000_000)
|
|
|
|
// Assert: flag should be set
|
|
#expect(viewModel.torRestartPending)
|
|
}
|
|
|
|
// MARK: - handleTorDidBecomeReady Tests
|
|
|
|
@Test @MainActor
|
|
func handleTorDidBecomeReady_afterRestart_clearsPendingFlag() async {
|
|
let (viewModel, _) = makeTestableViewModel()
|
|
|
|
// Setup: simulate restart pending state
|
|
viewModel.torRestartPending = true
|
|
|
|
// Action
|
|
viewModel.handleTorDidBecomeReady()
|
|
try? await Task.sleep(nanoseconds: 100_000_000)
|
|
|
|
// Assert: should clear pending flag
|
|
#expect(!viewModel.torRestartPending)
|
|
}
|
|
|
|
@Test @MainActor
|
|
func handleTorDidBecomeReady_initialStart_setsAnnouncedFlag() async {
|
|
let (viewModel, _) = makeTestableViewModel()
|
|
|
|
// Setup: not restarting, but initial ready not announced yet
|
|
viewModel.torRestartPending = false
|
|
viewModel.torInitialReadyAnnounced = false
|
|
|
|
// Action
|
|
viewModel.handleTorDidBecomeReady()
|
|
try? await Task.sleep(nanoseconds: 100_000_000)
|
|
|
|
// Assert: should set flag (torEnforced is true in tests)
|
|
#expect(viewModel.torInitialReadyAnnounced)
|
|
}
|
|
|
|
@Test @MainActor
|
|
func handleTorDidBecomeReady_alreadyAnnounced_noDuplicate() async {
|
|
let (viewModel, _) = makeTestableViewModel()
|
|
|
|
// Setup: already announced initial ready
|
|
viewModel.torRestartPending = false
|
|
viewModel.torInitialReadyAnnounced = true
|
|
viewModel.switchLocationChannel(to: .location(GeohashChannel(level: .city, geohash: "u4pruydq")))
|
|
try? await Task.sleep(nanoseconds: 100_000_000)
|
|
|
|
let initialMessageCount = viewModel.messages.count
|
|
|
|
// Action
|
|
viewModel.handleTorDidBecomeReady()
|
|
try? await Task.sleep(nanoseconds: 100_000_000)
|
|
|
|
// Assert: no new message
|
|
#expect(viewModel.messages.count == initialMessageCount)
|
|
}
|
|
|
|
// MARK: - handleTorPreferenceChanged Tests
|
|
|
|
@Test @MainActor
|
|
func handleTorPreferenceChanged_resetsAllFlags() async {
|
|
let (viewModel, _) = makeTestableViewModel()
|
|
|
|
// Setup: set all flags
|
|
viewModel.torStatusAnnounced = true
|
|
viewModel.torInitialReadyAnnounced = true
|
|
viewModel.torRestartPending = true
|
|
|
|
// Action
|
|
viewModel.handleTorPreferenceChanged(Notification(name: .init("test")))
|
|
try? await Task.sleep(nanoseconds: 100_000_000)
|
|
|
|
// Assert: all flags reset
|
|
#expect(!viewModel.torStatusAnnounced)
|
|
#expect(!viewModel.torInitialReadyAnnounced)
|
|
#expect(!viewModel.torRestartPending)
|
|
}
|
|
}
|