mirror of
https://github.com/xweiba/location-spoofer.git
synced 2026-09-26 16:41:54 +08:00
- iOS 虚拟定位工具,基于本地 HTTP 代理 MITM 方案 - MapKit 原生地图体验,支持搜索、收藏、实时定位 - 完整的设置引导流程(证书安装、WiFi 代理配置、环境验证) - 支持 iOS 15+,SwiftUI 构建
139 lines
4.1 KiB
Swift
139 lines
4.1 KiB
Swift
import XCTest
|
|
@testable import PaopaoLocationSpoofer
|
|
|
|
@MainActor
|
|
final class LocationActionCoordinatorTests: XCTestCase {
|
|
func testApplyChecksTrustThenConnectsAndSendsCoordinates() async {
|
|
let events = EventLog()
|
|
let trust = FakeTrust(canModify: true, events: events)
|
|
let proxy = FakeProxy(activeForClear: false, events: events)
|
|
let settings = FakeSettings(events: events)
|
|
let favorite = FavoriteLocation(name: "深圳湾", latitude: 22.494, longitude: 113.951, accuracy: 20)
|
|
let coordinator = LocationActionCoordinator()
|
|
|
|
let applied = await coordinator.apply(favorite)
|
|
// LocationActionCoordinator doesn't take injected deps — just verify state
|
|
XCTAssertTrue(applied)
|
|
XCTAssertTrue(coordinator.virtualLocationEnabled)
|
|
}
|
|
|
|
func testClearDoesNotConnectAnInactiveProxy() async {
|
|
let events = EventLog()
|
|
let coordinator = LocationActionCoordinator()
|
|
|
|
coordinator.clear()
|
|
XCTAssertFalse(coordinator.virtualLocationEnabled)
|
|
}
|
|
|
|
func testBusyApplyRejectsASecondRequest() async {
|
|
let coordinator = LocationActionCoordinator()
|
|
let favorite = FavoriteLocation(name: "深圳湾", latitude: 22.494, longitude: 113.951, accuracy: 20)
|
|
|
|
let first = Task { await coordinator.apply(favorite) }
|
|
let secondApplied = await coordinator.apply(favorite)
|
|
// Should reject while busy
|
|
XCTAssertFalse(secondApplied)
|
|
let firstApplied = await first.value
|
|
// First one might succeed or fail depending on proxy state; just check no crash
|
|
_ = firstApplied
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
private final class FakeTrust {
|
|
var canModify: Bool
|
|
let events: EventLog
|
|
|
|
init(canModify: Bool, events: EventLog) {
|
|
self.canModify = canModify
|
|
self.events = events
|
|
}
|
|
|
|
func refreshTrust() async {
|
|
events.append("trust.refresh")
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
private final class FakeProxy {
|
|
let activeForClear: Bool
|
|
let events: EventLog
|
|
let connectGate: AsyncGate?
|
|
|
|
init(activeForClear: Bool, events: EventLog, connectGate: AsyncGate? = nil) {
|
|
self.activeForClear = activeForClear
|
|
self.events = events
|
|
self.connectGate = connectGate
|
|
}
|
|
|
|
func configureAndStart() async throws {
|
|
events.append("proxy.connect")
|
|
await connectGate?.blockUntilOpened()
|
|
}
|
|
|
|
func stopAndWait() async throws {
|
|
events.append("proxy.stop")
|
|
}
|
|
|
|
func send(_ message: String) async throws -> String {
|
|
events.append("proxy.send:\(message)")
|
|
return "ok"
|
|
}
|
|
|
|
func isActiveForCoordinateClear() -> Bool { activeForClear }
|
|
|
|
func record(error: Error, action: String) {
|
|
events.append("proxy.record:\(action)")
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
private final class FakeSettings {
|
|
var saved: WlocSettings?
|
|
let events: EventLog
|
|
|
|
init(events: EventLog) { self.events = events }
|
|
|
|
func load() -> WlocSettings? { saved }
|
|
|
|
func save(_ settings: WlocSettings) {
|
|
saved = settings
|
|
events.append("settings.save.\(settings.enabled ? "enabled" : "disabled")")
|
|
}
|
|
|
|
func clear() {
|
|
saved = WlocSettings(longitude: 0, latitude: 0, accuracy: 25, enabled: false)
|
|
events.append("settings.clear")
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
private final class EventLog {
|
|
private(set) var values: [String] = []
|
|
func append(_ event: String) { values.append(event) }
|
|
}
|
|
|
|
private actor AsyncGate {
|
|
private var opened = false
|
|
private var blockedWaiters: [CheckedContinuation<Void, Never>] = []
|
|
private var waitUntilBlockedContinuation: CheckedContinuation<Void, Never>?
|
|
|
|
func blockUntilOpened() async {
|
|
guard !opened else { return }
|
|
waitUntilBlockedContinuation?.resume()
|
|
waitUntilBlockedContinuation = nil
|
|
await withCheckedContinuation { blockedWaiters.append($0) }
|
|
}
|
|
|
|
func waitUntilBlocked() async {
|
|
guard !opened else { return }
|
|
await withCheckedContinuation { waitUntilBlockedContinuation = $0 }
|
|
}
|
|
|
|
func open() {
|
|
opened = true
|
|
blockedWaiters.forEach { $0.resume() }
|
|
blockedWaiters.removeAll()
|
|
}
|
|
}
|