Files
location-spoofer/Tests/PaopaoLocationSpooferTests/RealtimeLocationManagerTests.swift
T

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)
}
}