mirror of
https://github.com/xweiba/location-spoofer.git
synced 2026-09-27 09:01:54 +08:00
fix: 优化启动引导、坐标处理和日志管理
This commit is contained in:
@@ -3,130 +3,85 @@ import XCTest
|
||||
|
||||
@MainActor
|
||||
final class LocationActionCoordinatorTests: XCTestCase {
|
||||
func testApplyChecksTrustThenConnectsAndSendsCoordinates() async {
|
||||
let favorite = FavoriteLocation(name: "深圳湾", latitude: 22.494, longitude: 113.951, accuracy: 20)
|
||||
let coordinator = LocationActionCoordinator()
|
||||
func testApplyStartsProxyAndWritesTheFavoriteWGS84Pair() async {
|
||||
let proxy = FakeLocationActionProxy()
|
||||
let settings = FakeLocationActionSettingsStore()
|
||||
let coordinator = LocationActionCoordinator(proxy: proxy, settings: settings)
|
||||
let favorite = FavoriteLocation(
|
||||
name: "深圳湾",
|
||||
latitude: 22.494,
|
||||
longitude: 113.951,
|
||||
accuracy: 20,
|
||||
mapCoordinateSystem: .gcj02
|
||||
)
|
||||
|
||||
let applied = await coordinator.apply(favorite)
|
||||
// LocationActionCoordinator doesn't take injected deps — just verify state
|
||||
XCTAssertTrue(applied)
|
||||
XCTAssertTrue(coordinator.virtualLocationEnabled)
|
||||
XCTAssertTrue(proxy.isRunning)
|
||||
XCTAssertEqual(proxy.lastCoordinates?.latitude, favorite.coordinatePair.wgs84.latitude)
|
||||
XCTAssertEqual(proxy.lastCoordinates?.longitude, favorite.coordinatePair.wgs84.longitude)
|
||||
XCTAssertEqual(settings.saved?.latitude, favorite.coordinatePair.wgs84.latitude)
|
||||
XCTAssertEqual(settings.saved?.longitude, favorite.coordinatePair.wgs84.longitude)
|
||||
XCTAssertTrue(settings.saved?.enabled == true)
|
||||
}
|
||||
|
||||
func testClearDoesNotConnectAnInactiveProxy() async {
|
||||
let coordinator = LocationActionCoordinator()
|
||||
func testClearWritesDisabledCoordinatesAndClearsSettings() {
|
||||
let proxy = FakeLocationActionProxy(isRunning: true)
|
||||
let settings = FakeLocationActionSettingsStore()
|
||||
let coordinator = LocationActionCoordinator(proxy: proxy, settings: settings)
|
||||
|
||||
coordinator.clear()
|
||||
|
||||
XCTAssertEqual(proxy.lastCoordinates?.latitude, 0)
|
||||
XCTAssertEqual(proxy.lastCoordinates?.longitude, 0)
|
||||
XCTAssertFalse(proxy.lastCoordinates?.enabled ?? true)
|
||||
XCTAssertFalse(settings.saved?.enabled ?? true)
|
||||
XCTAssertFalse(coordinator.virtualLocationEnabled)
|
||||
}
|
||||
|
||||
func testBusyApplyRejectsASecondRequest() async {
|
||||
let coordinator = LocationActionCoordinator()
|
||||
func testApplyVerifiedRejectsInactiveProxyWithoutWritingCoordinates() {
|
||||
let proxy = FakeLocationActionProxy()
|
||||
let settings = FakeLocationActionSettingsStore()
|
||||
let coordinator = LocationActionCoordinator(proxy: proxy, settings: settings)
|
||||
let favorite = FavoriteLocation(name: "深圳湾", latitude: 22.494, longitude: 113.951, accuracy: 20)
|
||||
|
||||
// The coordinator serializes on MainActor. A completed first request may
|
||||
// legitimately make the next request a no-op rather than a concurrent rejection.
|
||||
let firstApplied = await coordinator.apply(favorite)
|
||||
let secondApplied = await coordinator.apply(favorite)
|
||||
XCTAssertTrue(firstApplied)
|
||||
XCTAssertTrue(secondApplied)
|
||||
XCTAssertFalse(coordinator.applyVerified(favorite))
|
||||
XCTAssertNil(proxy.lastCoordinates)
|
||||
XCTAssertNil(settings.saved)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class FakeTrust {
|
||||
var canModify: Bool
|
||||
let events: EventLog
|
||||
|
||||
init(canModify: Bool, events: EventLog) {
|
||||
self.canModify = canModify
|
||||
self.events = events
|
||||
private final class FakeLocationActionProxy: LocationActionProxying {
|
||||
struct Coordinates: Equatable {
|
||||
let latitude: Double
|
||||
let longitude: Double
|
||||
let enabled: Bool
|
||||
let accuracy: Int
|
||||
}
|
||||
|
||||
func refreshTrust() async {
|
||||
events.append("trust.refresh")
|
||||
var isRunning: Bool
|
||||
private(set) var lastCoordinates: Coordinates?
|
||||
|
||||
init(isRunning: Bool = false) {
|
||||
self.isRunning = isRunning
|
||||
}
|
||||
|
||||
func start() async throws {
|
||||
isRunning = true
|
||||
}
|
||||
|
||||
func setCoords(lat: Double, lon: Double, enabled: Bool, accuracy: Int) -> UInt64 {
|
||||
lastCoordinates = Coordinates(latitude: lat, longitude: lon, enabled: enabled, accuracy: accuracy)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
@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 }
|
||||
private final class FakeLocationActionSettingsStore: LocationActionSettingsStoring {
|
||||
private(set) var saved: WlocSettings?
|
||||
|
||||
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()
|
||||
}
|
||||
func save(_ settings: WlocSettings) { saved = settings }
|
||||
func clear() { saved = WlocSettings(longitude: 0, latitude: 0, accuracy: 25, enabled: false) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user