Files
bitchat/bitchatTests/Services/NetworkActivationServiceTests.swift
T
ef848857b7 Remove dead code found by full Periphery audit; add scan config + advisory CI (#1410)
Periphery 3.7.4 audit of both schemes (macOS + iOS, intersected so
platform-specific code is never touched), with test targets indexed and
the share extension built. 277 dead declarations removed or demoted:
dead forwarding wrappers (ChatViewModel+Nostr/+PrivateChat), removed-
feature remnants (autocomplete command suggestions, back-swipe tuning,
MediaSendError, GeohashParticipantTracker), unused Tor dormancy
bindings, assign-only properties, unused parameters (renamed to _), and
redundant public accessibility. 13 orphaned localization keys deleted
across all 29 locales (old pre-#1392 location-notes UI, app_info
warnings).

Two real tests were flagged as unused because they never ran: Swift
Testing methods missing @Test (NostrProtocolTests.
testAckRoundTripNIP44V2_Delivered, NotificationStreamAssemblerTests.
testAssemblesCompressedLargeFrame). Re-armed both; they pass.

Deliberately kept, now recorded in .periphery.baseline.json: iOS-only
code invisible to the CI macOS scan, C FFI signatures, keep-alive
NWPathMonitor reference, InboundEventKey.eventID (dedup semantics),
wifiBulk capability bit (reserved for Wi-Fi bulk work, used by
BitFoundation package tests), and the String secureClear cluster
(exercised by package tests).

New: .periphery.yml config and an advisory Dead Code CI job (mirrors
the SwiftLint precedent from #1361) that fails on findings not in the
committed baseline.

Verified: full macOS app suite, BitFoundation (119) and BitLogger (13)
package tests green; periphery scan --strict exits clean.

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 11:24:19 +02:00

197 lines
7.6 KiB
Swift

import Combine
import XCTest
@testable import bitchat
@MainActor
final class NetworkActivationServiceTests: XCTestCase {
private let torPreferenceKey = "networkActivationService.userTorEnabled"
func test_start_leavesNetworkDisabledWithoutPermissionOrFavorites() {
let context = makeService(permission: .denied, favorites: [])
context.service.start()
XCTAssertFalse(context.service.activationAllowed)
XCTAssertEqual(context.torController.autoStartAllowedValues, [false])
XCTAssertEqual(context.proxyController.proxyModes, [false])
XCTAssertEqual(context.torController.startIfNeededCallCount, 0)
XCTAssertEqual(context.torController.shutdownCompletelyCallCount, 1)
XCTAssertEqual(context.relayController.connectCallCount, 0)
XCTAssertEqual(context.relayController.disconnectCallCount, 1)
}
func test_start_enablesTorAndRelaysWhenAuthorized() {
let context = makeService(permission: .authorized, favorites: [])
context.service.start()
XCTAssertTrue(context.service.activationAllowed)
XCTAssertEqual(context.torController.autoStartAllowedValues, [true])
XCTAssertEqual(context.proxyController.proxyModes, [true])
XCTAssertEqual(context.torController.startIfNeededCallCount, 1)
XCTAssertEqual(context.relayController.connectCallCount, 1)
XCTAssertEqual(context.relayController.disconnectCallCount, 0)
}
func test_start_respectsStoredTorPreferenceForDirectMode() {
let context = makeService(permission: .authorized, favorites: [])
context.storage.set(false, forKey: torPreferenceKey)
context.service.start()
XCTAssertTrue(context.service.activationAllowed)
XCTAssertFalse(context.service.userTorEnabled)
XCTAssertEqual(context.torController.autoStartAllowedValues, [false])
XCTAssertEqual(context.proxyController.proxyModes, [false])
XCTAssertEqual(context.torController.startIfNeededCallCount, 0)
XCTAssertEqual(context.torController.shutdownCompletelyCallCount, 1)
XCTAssertEqual(context.relayController.connectCallCount, 1)
}
func test_setUserTorEnabled_postsNotificationAndReconnectsOnTransportSwitch() {
let context = makeService(permission: .authorized, favorites: [])
let notified = expectation(description: "Tor preference notification")
let token = context.notificationCenter.addObserver(
forName: .TorUserPreferenceChanged,
object: nil,
queue: nil
) { note in
XCTAssertEqual(note.userInfo?["enabled"] as? Bool, false)
notified.fulfill()
}
context.service.start()
context.service.setUserTorEnabled(false)
wait(for: [notified], timeout: 1.0)
context.notificationCenter.removeObserver(token)
XCTAssertFalse(context.service.userTorEnabled)
XCTAssertEqual(context.storage.object(forKey: torPreferenceKey) as? Bool, false)
XCTAssertEqual(Array(context.proxyController.proxyModes.suffix(2)), [true, false])
XCTAssertEqual(Array(context.torController.autoStartAllowedValues.suffix(2)), [true, false])
XCTAssertEqual(context.relayController.disconnectCallCount, 1)
XCTAssertEqual(context.relayController.connectCallCount, 2)
}
func test_mutualFavoritesPublisher_reactivatesNetwork() async {
let context = makeService(permission: .denied, favorites: [])
context.service.start()
XCTAssertFalse(context.service.activationAllowed)
context.favoritesSubject.send([Data([0x01])])
let becameActive = await waitUntil { context.service.activationAllowed }
XCTAssertTrue(becameActive)
XCTAssertTrue(context.service.activationAllowed)
XCTAssertTrue(context.torController.autoStartAllowedValues.contains(true))
XCTAssertTrue(context.proxyController.proxyModes.contains(true))
XCTAssertGreaterThanOrEqual(context.torController.startIfNeededCallCount, 1)
XCTAssertGreaterThanOrEqual(context.relayController.connectCallCount, 1)
}
private func makeService(
permission: LocationChannelManager.PermissionState,
favorites: Set<Data>
) -> NetworkActivationTestContext {
let suiteName = "NetworkActivationServiceTests-\(UUID().uuidString)"
let storage = UserDefaults(suiteName: suiteName)!
storage.removePersistentDomain(forName: suiteName)
let permissionSubject = CurrentValueSubject<LocationChannelManager.PermissionState, Never>(permission)
let favoritesSubject = CurrentValueSubject<Set<Data>, Never>(favorites)
let torController = MockNetworkActivationTorController()
let relayController = MockNetworkActivationRelayController()
let proxyController = MockNetworkActivationProxyController()
let notificationCenter = NotificationCenter()
let service = NetworkActivationService(
storage: storage,
locationPermissionPublisher: permissionSubject.eraseToAnyPublisher(),
mutualFavoritesPublisher: favoritesSubject.eraseToAnyPublisher(),
permissionProvider: { permissionSubject.value },
mutualFavoritesProvider: { favoritesSubject.value },
reachabilityMonitor: AlwaysReachableMonitor(),
torController: torController,
relayController: relayController,
proxyController: proxyController,
notificationCenter: notificationCenter
)
return NetworkActivationTestContext(
service: service,
storage: storage,
favoritesSubject: favoritesSubject,
torController: torController,
relayController: relayController,
proxyController: proxyController,
notificationCenter: notificationCenter
)
}
private func waitUntil(
timeout: TimeInterval = 1.0,
condition: @escaping @MainActor () -> Bool
) async -> Bool {
let deadline = Date().addingTimeInterval(timeout)
while Date() < deadline {
if condition() {
return true
}
try? await Task.sleep(nanoseconds: 10_000_000)
}
return condition()
}
}
@MainActor
private struct NetworkActivationTestContext {
let service: NetworkActivationService
let storage: UserDefaults
let favoritesSubject: CurrentValueSubject<Set<Data>, Never>
let torController: MockNetworkActivationTorController
let relayController: MockNetworkActivationRelayController
let proxyController: MockNetworkActivationProxyController
let notificationCenter: NotificationCenter
}
@MainActor
private final class MockNetworkActivationTorController: NetworkActivationTorControlling {
private(set) var autoStartAllowedValues: [Bool] = []
private(set) var startIfNeededCallCount = 0
private(set) var shutdownCompletelyCallCount = 0
func setAutoStartAllowed(_ allowed: Bool) {
autoStartAllowedValues.append(allowed)
}
func startIfNeeded() {
startIfNeededCallCount += 1
}
func shutdownCompletely() {
shutdownCompletelyCallCount += 1
}
}
@MainActor
private final class MockNetworkActivationRelayController: NetworkActivationRelayControlling {
private(set) var connectCallCount = 0
private(set) var disconnectCallCount = 0
func connect() {
connectCallCount += 1
}
func disconnect() {
disconnectCallCount += 1
}
}
private final class MockNetworkActivationProxyController: NetworkActivationProxyControlling {
private(set) var proxyModes: [Bool] = []
func setProxyMode(useTor: Bool) {
proxyModes.append(useTor)
}
}