mirror of
https://github.com/xweiba/location-spoofer.git
synced 2026-09-21 22:30:46 +08:00
192 lines
7.3 KiB
Swift
192 lines
7.3 KiB
Swift
import CoreLocation
|
|
import XCTest
|
|
@testable import PaopaoLocationSpoofer
|
|
|
|
@MainActor
|
|
final class RealtimeLocationManagerTests: XCTestCase {
|
|
func testFreshCachedLocationReturnsImmediatelyWithoutRequestingAgain() async {
|
|
let driver = FakeRealtimeLocationDriver()
|
|
driver.location = CLLocation(
|
|
coordinate: .init(latitude: 30.42, longitude: 114.25),
|
|
altitude: 0,
|
|
horizontalAccuracy: 12,
|
|
verticalAccuracy: 10,
|
|
timestamp: Date()
|
|
)
|
|
let manager = RealtimeLocationManager(driver: driver, oneShotTimeoutNanoseconds: 1_000_000_000)
|
|
|
|
let coordinate = await manager.requestLocation()
|
|
|
|
XCTAssertEqual(coordinate?.latitude ?? 0, 30.42, accuracy: 0.000001)
|
|
XCTAssertEqual(driver.requestLocationCallCount, 0)
|
|
XCTAssertEqual(driver.startUpdatingCallCount, 0)
|
|
XCTAssertFalse(manager.isRequesting)
|
|
}
|
|
|
|
func testContinuationIsInstalledBeforeOneShotRequest() async {
|
|
let driver = FakeRealtimeLocationDriver()
|
|
let manager = RealtimeLocationManager(driver: driver, timeoutNanoseconds: 1_000_000_000)
|
|
driver.onRequestLocation = {
|
|
driver.emit(CLLocation(latitude: 22.54, longitude: 113.94))
|
|
}
|
|
|
|
let coordinate = await manager.requestLocation()
|
|
|
|
XCTAssertEqual(coordinate?.latitude ?? 0, 22.54, accuracy: 0.000001)
|
|
XCTAssertFalse(manager.isRequesting)
|
|
}
|
|
|
|
func testUndeterminedAuthorizationWaitsBeforeRequestingLocation() async {
|
|
let driver = FakeRealtimeLocationDriver()
|
|
driver.authorizationStatus = .notDetermined
|
|
let manager = RealtimeLocationManager(driver: driver, timeoutNanoseconds: 1_000_000_000)
|
|
|
|
let request = Task { await manager.requestLocation() }
|
|
while !manager.isRequesting { await Task.yield() }
|
|
|
|
XCTAssertEqual(driver.requestAuthorizationCallCount, 1)
|
|
XCTAssertEqual(driver.requestLocationCallCount, 0)
|
|
|
|
driver.emitAuthorization(.authorizedWhenInUse)
|
|
await Task.yield()
|
|
XCTAssertEqual(driver.requestLocationCallCount, 1)
|
|
|
|
driver.emit(CLLocation(latitude: 22.54, longitude: 113.94))
|
|
let coordinate = await request.value
|
|
XCTAssertEqual(coordinate?.latitude ?? 0, 22.54, accuracy: 0.000001)
|
|
}
|
|
|
|
func testOverlappingRequestIsRejectedWithoutReplacingFirstContinuation() async {
|
|
let driver = FakeRealtimeLocationDriver()
|
|
let manager = RealtimeLocationManager(driver: driver, timeoutNanoseconds: 1_000_000_000)
|
|
|
|
let first = Task { await manager.requestLocation() }
|
|
while !manager.isRequesting { await Task.yield() }
|
|
let second = await manager.requestLocation()
|
|
XCTAssertNil(second)
|
|
|
|
driver.emit(CLLocation(latitude: 31.23, longitude: 121.47))
|
|
let firstCoordinate = await first.value
|
|
XCTAssertEqual(firstCoordinate?.longitude ?? 0, 121.47, accuracy: 0.000001)
|
|
}
|
|
|
|
func testOneShotTimeoutTransitionsToContinuousFallback() async {
|
|
let driver = FakeRealtimeLocationDriver()
|
|
let manager = RealtimeLocationManager(driver: driver, oneShotTimeoutNanoseconds: 5_000_000, fallbackTimeoutNanoseconds: 1_000_000_000)
|
|
let fallbackStarted = expectation(description: "Continuous location fallback started")
|
|
driver.onStartUpdating = {
|
|
fallbackStarted.fulfill()
|
|
}
|
|
|
|
let request = Task { await manager.requestLocation() }
|
|
await fulfillment(of: [fallbackStarted], timeout: 1)
|
|
XCTAssertEqual(driver.startUpdatingCallCount, 1)
|
|
|
|
driver.emit(CLLocation(latitude: 39.90, longitude: 116.40))
|
|
let coordinate = await request.value
|
|
XCTAssertEqual(coordinate?.latitude ?? 0, 39.90, accuracy: 0.000001)
|
|
XCTAssertEqual(driver.stopUpdatingCallCount, 1)
|
|
}
|
|
|
|
func testInvalidAccuracyCannotCompleteRequest() async {
|
|
let driver = FakeRealtimeLocationDriver()
|
|
let manager = RealtimeLocationManager(driver: driver, timeoutNanoseconds: 1_000_000_000)
|
|
|
|
let request = Task { await manager.requestLocation() }
|
|
while !manager.isRequesting { await Task.yield() }
|
|
driver.emit(CLLocation(
|
|
coordinate: .init(latitude: 22.54, longitude: 113.94),
|
|
altitude: 0,
|
|
horizontalAccuracy: -1,
|
|
verticalAccuracy: 10,
|
|
timestamp: Date()
|
|
))
|
|
await Task.yield()
|
|
XCTAssertTrue(manager.isRequesting)
|
|
XCTAssertNil(manager.location)
|
|
|
|
driver.emit(CLLocation(latitude: 31.23, longitude: 121.47))
|
|
let coordinate = await request.value
|
|
XCTAssertEqual(coordinate?.longitude ?? 0, 121.47, accuracy: 0.000001)
|
|
}
|
|
|
|
func testDeniedLocationErrorFinishesWithoutStartingFallback() async {
|
|
let driver = FakeRealtimeLocationDriver()
|
|
let manager = RealtimeLocationManager(driver: driver, timeoutNanoseconds: 1_000_000_000)
|
|
|
|
let request = Task { await manager.requestLocation() }
|
|
while !manager.isRequesting { await Task.yield() }
|
|
driver.emitError(NSError(domain: kCLErrorDomain, code: CLError.denied.rawValue))
|
|
|
|
let coordinate = await request.value
|
|
XCTAssertNil(coordinate)
|
|
XCTAssertEqual(driver.startUpdatingCallCount, 0)
|
|
XCTAssertFalse(manager.isRequesting)
|
|
}
|
|
|
|
func testOldTimestampCannotCompleteNewRequest() async {
|
|
let driver = FakeRealtimeLocationDriver()
|
|
let manager = RealtimeLocationManager(driver: driver, timeoutNanoseconds: 1_000_000_000)
|
|
|
|
let request = Task { await manager.requestLocation() }
|
|
while !manager.isRequesting { await Task.yield() }
|
|
driver.emit(CLLocation(
|
|
coordinate: .init(latitude: 1, longitude: 2),
|
|
altitude: 0,
|
|
horizontalAccuracy: 10,
|
|
verticalAccuracy: 10,
|
|
timestamp: Date(timeIntervalSinceNow: -60)
|
|
))
|
|
await Task.yield()
|
|
XCTAssertTrue(manager.isRequesting)
|
|
|
|
driver.emit(CLLocation(latitude: 22.54, longitude: 113.94))
|
|
let coordinate = await request.value
|
|
XCTAssertEqual(coordinate?.latitude ?? 0, 22.54, accuracy: 0.000001)
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
private final class FakeRealtimeLocationDriver: RealtimeLocationDriving {
|
|
var location: CLLocation?
|
|
var authorizationStatus: CLAuthorizationStatus = .authorizedWhenInUse
|
|
weak var delegate: CLLocationManagerDelegate?
|
|
var onRequestLocation: (() -> Void)?
|
|
var onStartUpdating: (() -> Void)?
|
|
private(set) var requestAuthorizationCallCount = 0
|
|
private(set) var requestLocationCallCount = 0
|
|
private(set) var startUpdatingCallCount = 0
|
|
private(set) var stopUpdatingCallCount = 0
|
|
|
|
func requestWhenInUseAuthorization() {
|
|
requestAuthorizationCallCount += 1
|
|
}
|
|
|
|
func requestLocation() {
|
|
requestLocationCallCount += 1
|
|
onRequestLocation?()
|
|
}
|
|
|
|
func startUpdatingLocation() {
|
|
startUpdatingCallCount += 1
|
|
onStartUpdating?()
|
|
}
|
|
|
|
func stopUpdatingLocation() {
|
|
stopUpdatingCallCount += 1
|
|
}
|
|
|
|
func emitAuthorization(_ status: CLAuthorizationStatus) {
|
|
authorizationStatus = status
|
|
delegate?.locationManagerDidChangeAuthorization?(CLLocationManager())
|
|
}
|
|
|
|
func emit(_ location: CLLocation) {
|
|
delegate?.locationManager?(CLLocationManager(), didUpdateLocations: [location])
|
|
}
|
|
|
|
func emitError(_ error: Error) {
|
|
delegate?.locationManager?(CLLocationManager(), didFailWithError: error)
|
|
}
|
|
}
|