mirror of
https://github.com/xweiba/location-spoofer.git
synced 2026-09-21 22:30:46 +08:00
202 lines
9.0 KiB
Swift
202 lines
9.0 KiB
Swift
import XCTest
|
|
@testable import PaopaoLocationSpoofer
|
|
|
|
@MainActor
|
|
final class ThirdPartyProxyManagerTests: XCTestCase {
|
|
func testQueryDistinguishesConnectedWithoutCoordinate() async throws {
|
|
let requester = FakeThirdPartyRequester(body: #"{"success":false,"error":"无已保存的坐标"}"#)
|
|
let manager = ThirdPartyProxyManager(requester: requester)
|
|
|
|
let response = try await manager.query()
|
|
|
|
XCTAssertFalse(response.success)
|
|
XCTAssertEqual(manager.connectionState, .connected(active: false))
|
|
XCTAssertEqual(requester.lastURL?.query, "action=query")
|
|
}
|
|
|
|
func testSaveUsesFavoriteWGS84AndAcceptsMatchingResponse() async throws {
|
|
let favorite = FavoriteLocation(
|
|
name: "深圳湾",
|
|
latitude: 22.494,
|
|
longitude: 113.951,
|
|
accuracy: 20,
|
|
mapCoordinateSystem: .gcj02
|
|
)
|
|
let wgs84 = favorite.coordinatePair.wgs84
|
|
let body = String(format: #"{"success":true,"longitude":%.8f,"latitude":%.8f,"accuracy":20}"#,
|
|
locale: Locale(identifier: "en_US_POSIX"), wgs84.longitude, wgs84.latitude)
|
|
let requester = FakeThirdPartyRequester(body: body)
|
|
let manager = ThirdPartyProxyManager(requester: requester)
|
|
|
|
_ = try await manager.save(favorite)
|
|
|
|
let components = URLComponents(url: try XCTUnwrap(requester.lastURL), resolvingAgainstBaseURL: false)
|
|
let values = Dictionary(uniqueKeysWithValues: (components?.queryItems ?? []).map { ($0.name, $0.value ?? "") })
|
|
let latitude = try XCTUnwrap(Double(values["lat"] ?? ""))
|
|
let longitude = try XCTUnwrap(Double(values["lon"] ?? ""))
|
|
XCTAssertEqual(latitude, wgs84.latitude, accuracy: 0.000_000_01)
|
|
XCTAssertEqual(longitude, wgs84.longitude, accuracy: 0.000_000_01)
|
|
XCTAssertEqual(values["acc"], "20")
|
|
XCTAssertEqual(manager.connectionState, .connected(active: true))
|
|
}
|
|
|
|
func testConnectionUsesLegacySaveQueryEndpoint() async throws {
|
|
let requester = FakeThirdPartyRequester(body: #"{"success":false,"error":"无已保存的坐标"}"#)
|
|
let manager = ThirdPartyProxyManager(requester: requester)
|
|
|
|
let response = try await manager.query()
|
|
|
|
XCTAssertFalse(response.success)
|
|
XCTAssertEqual(requester.requestedURLs.map(\.path), ["/wloc-settings/save"])
|
|
XCTAssertEqual(requester.requestedURLs.first?.query, "action=query")
|
|
}
|
|
|
|
func testLegacyModuleCanStillSaveBasicCoordinates() async throws {
|
|
let favorite = FavoriteLocation(
|
|
name: "深圳湾",
|
|
latitude: 22.494,
|
|
longitude: 113.951,
|
|
accuracy: 20,
|
|
mapCoordinateSystem: .gcj02
|
|
)
|
|
let wgs84 = favorite.coordinatePair.wgs84
|
|
let body = String(
|
|
format: #"{"success":true,"longitude":%.8f,"latitude":%.8f,"accuracy":20}"#,
|
|
locale: Locale(identifier: "en_US_POSIX"),
|
|
wgs84.longitude,
|
|
wgs84.latitude
|
|
)
|
|
let requester = FakeThirdPartyRequester(body: body)
|
|
let manager = ThirdPartyProxyManager(requester: requester)
|
|
|
|
let response = try await manager.save(favorite)
|
|
|
|
XCTAssertTrue(response.success)
|
|
XCTAssertEqual(manager.connectionState, .connected(active: true))
|
|
XCTAssertEqual(requester.requestedURLs.map(\.path), ["/wloc-settings/save"])
|
|
let queryComponents = URLComponents(
|
|
url: try XCTUnwrap(requester.requestedURLs.first),
|
|
resolvingAgainstBaseURL: false
|
|
)
|
|
let values = Dictionary(
|
|
uniqueKeysWithValues: (queryComponents?.queryItems ?? []).map { ($0.name, $0.value ?? "") }
|
|
)
|
|
XCTAssertEqual(values["lon"], String(format: "%.8f", locale: Locale(identifier: "en_US_POSIX"), wgs84.longitude))
|
|
XCTAssertEqual(values["lat"], String(format: "%.8f", locale: Locale(identifier: "en_US_POSIX"), wgs84.latitude))
|
|
XCTAssertEqual(values["acc"], "20")
|
|
XCTAssertEqual(values["randomRadius"], "0")
|
|
}
|
|
|
|
func testBrokenSaveQueryFailsWithoutCheckingVersion() async {
|
|
let requester = FakeThirdPartyRequester(body: "not-json")
|
|
let manager = ThirdPartyProxyManager(requester: requester)
|
|
|
|
do {
|
|
_ = try await manager.query()
|
|
XCTFail("expected interception failure")
|
|
} catch {
|
|
XCTAssertEqual(error as? ThirdPartyProxyError, .moduleNotIntercepted)
|
|
}
|
|
XCTAssertEqual(requester.requestedURLs.map(\.path), ["/wloc-settings/save"])
|
|
}
|
|
|
|
func testSaveRejectsCoordinateMismatchWithoutMarkingActive() async {
|
|
let requester = FakeThirdPartyRequester(body: #"{"success":true,"longitude":1,"latitude":2,"accuracy":25}"#)
|
|
let manager = ThirdPartyProxyManager(requester: requester)
|
|
let favorite = FavoriteLocation(name: "深圳湾", latitude: 22.494, longitude: 113.951, accuracy: 25)
|
|
|
|
do {
|
|
_ = try await manager.save(favorite)
|
|
XCTFail("expected coordinate mismatch")
|
|
} catch {
|
|
XCTAssertEqual(error as? ThirdPartyProxyError, .coordinateMismatch)
|
|
}
|
|
XCTAssertEqual(manager.connectionState, .unknown)
|
|
XCTAssertNil(manager.activeSettings)
|
|
}
|
|
|
|
func testMalformedResponseIsNotTreatedAsSuccess() async {
|
|
let manager = ThirdPartyProxyManager(requester: FakeThirdPartyRequester(body: "not-json"))
|
|
do {
|
|
_ = try await manager.query()
|
|
XCTFail("expected interception failure")
|
|
} catch {
|
|
XCTAssertEqual(error as? ThirdPartyProxyError, .moduleNotIntercepted)
|
|
}
|
|
}
|
|
|
|
func testClientLinksUseProjectOwnedModulesAndVerificationLabels() {
|
|
XCTAssertEqual(
|
|
ThirdPartyProxyManager.interceptionHostnames,
|
|
[
|
|
"gs-loc.apple.com",
|
|
"gs-loc-cn.apple.com",
|
|
"gsp-ssl.ls.apple.com",
|
|
"bluedot.is.autonavi.com",
|
|
"bluedot.is.autonavi.com.gds.alibabadns.com"
|
|
]
|
|
)
|
|
XCTAssertNil(ThirdPartyProxyClient.shadowrocket.verificationText)
|
|
XCTAssertEqual(
|
|
ThirdPartyProxyClient.surge.verificationText,
|
|
String(localized: "配置已提供,尚未验证")
|
|
)
|
|
XCTAssertEqual(ThirdPartyProxyClient.egern.subscriptionURL, ThirdPartyProxyClient.surge.subscriptionURL)
|
|
XCTAssertTrue(ThirdPartyProxyClient.stash.subscriptionURL.absoluteString.hasPrefix(
|
|
"https://gh-proxy.org/https://raw.githubusercontent.com/xweiba/location-spoofer/main/Resources/ThirdPartyProxyModules/"
|
|
))
|
|
let stashComponents = URLComponents(
|
|
url: ThirdPartyProxyClient.stash.subscriptionURL,
|
|
resolvingAgainstBaseURL: false
|
|
)
|
|
let shadowrocketComponents = URLComponents(
|
|
url: ThirdPartyProxyClient.shadowrocket.subscriptionURL,
|
|
resolvingAgainstBaseURL: false
|
|
)
|
|
XCTAssertEqual(stashComponents?.path, "/https://raw.githubusercontent.com/xweiba/location-spoofer/main/Resources/ThirdPartyProxyModules/wloc.stoverride")
|
|
XCTAssertEqual(shadowrocketComponents?.path, "/https://raw.githubusercontent.com/xweiba/location-spoofer/main/Resources/ThirdPartyProxyModules/wloc.module")
|
|
XCTAssertEqual(stashComponents?.queryItems?.first?.value, ThirdPartyProxyClient.moduleSubscriptionVersion)
|
|
XCTAssertEqual(shadowrocketComponents?.queryItems?.first?.value, ThirdPartyProxyClient.moduleSubscriptionVersion)
|
|
XCTAssertEqual(ThirdPartyProxyClient.shadowrocket.launchURL?.scheme, "shadowrocket")
|
|
XCTAssertEqual(ThirdPartyProxyClient.surge.launchURL?.scheme, "surge")
|
|
XCTAssertEqual(ThirdPartyProxyClient.quantumultX.launchURL?.scheme, "quantumult-x")
|
|
XCTAssertEqual(ThirdPartyProxyClient.loon.launchURL?.scheme, "loon")
|
|
XCTAssertEqual(ThirdPartyProxyClient.stash.launchURL?.scheme, "stash")
|
|
XCTAssertEqual(ThirdPartyProxyClient.egern.launchURL?.scheme, "egern")
|
|
}
|
|
|
|
func testSelectedClientPersists() {
|
|
let suiteName = "ThirdPartyProxyClientStoreTests.\(UUID().uuidString)"
|
|
let defaults = UserDefaults(suiteName: suiteName)!
|
|
defer { defaults.removePersistentDomain(forName: suiteName) }
|
|
|
|
let store = ThirdPartyProxyClientStore(defaults: defaults)
|
|
XCTAssertEqual(store.selectedClient, .shadowrocket)
|
|
|
|
store.select(.stash)
|
|
XCTAssertEqual(ThirdPartyProxyClientStore(defaults: defaults).selectedClient, .stash)
|
|
}
|
|
}
|
|
|
|
private final class FakeThirdPartyRequester: ThirdPartyProxyRequesting {
|
|
private let data: Data
|
|
private(set) var lastURL: URL?
|
|
private(set) var requestedURLs: [URL] = []
|
|
|
|
init(body: String) {
|
|
data = Data(body.utf8)
|
|
}
|
|
|
|
func data(for request: URLRequest) async throws -> (Data, URLResponse) {
|
|
lastURL = request.url
|
|
requestedURLs.append(request.url!)
|
|
let response = HTTPURLResponse(
|
|
url: request.url!,
|
|
statusCode: 200,
|
|
httpVersion: "HTTP/1.1",
|
|
headerFields: ["Content-Type": "application/json"]
|
|
)!
|
|
return (data, response)
|
|
}
|
|
}
|