mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-24 22:25:18 +00:00
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>
197 lines
7.6 KiB
Swift
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)
|
|
}
|
|
}
|