mirror of
https://github.com/xweiba/location-spoofer.git
synced 2026-09-26 00:21:55 +08:00
chore: prepare v1.0.5 release
This commit is contained in:
@@ -62,11 +62,23 @@ final class AppRemoteConfigurationTests: XCTestCase {
|
||||
func testFallbackMatchesCurrentProjectPolicy() {
|
||||
let configuration = AppRemoteConfiguration.fallback
|
||||
|
||||
XCTAssertEqual(configuration.latestVersion, "1.0.4")
|
||||
XCTAssertEqual(configuration.latestVersion, "1.0.5")
|
||||
XCTAssertEqual(configuration.minimumSupportedVersion, "1.0.0")
|
||||
XCTAssertFalse(configuration.requestsCommunityPrompt(for: .shadowrocket))
|
||||
for client in ThirdPartyProxyClient.allCases where client != .shadowrocket {
|
||||
XCTAssertTrue(configuration.requestsCommunityPrompt(for: client))
|
||||
}
|
||||
}
|
||||
|
||||
func testUpdateResourcesPreferDomesticMirrorAndRetainOfficialFallback() {
|
||||
XCTAssertEqual(AppRemoteConfigurationService.configurationURLs.first?.host, "gh-proxy.org")
|
||||
XCTAssertEqual(
|
||||
AppRemoteConfigurationService.configurationURLs.last?.host,
|
||||
"raw.githubusercontent.com"
|
||||
)
|
||||
|
||||
let releaseNotesURLs = AppRemoteConfigurationService.releaseNotesURLs(version: "1.0.5")
|
||||
XCTAssertEqual(releaseNotesURLs.first?.host, "gh-proxy.org")
|
||||
XCTAssertEqual(releaseNotesURLs.last?.host, "raw.githubusercontent.com")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,73 @@ final class ThirdPartyProxyManagerTests: XCTestCase {
|
||||
XCTAssertTrue(version.isCompatible)
|
||||
}
|
||||
|
||||
func testConnectionUsesLegacySaveQueryEndpoint() async throws {
|
||||
let requester = FakeThirdPartyRequester(body: #"{"success":false,"error":"无已保存的坐标"}"#)
|
||||
let manager = ThirdPartyProxyManager(requester: requester)
|
||||
|
||||
let response = try await manager.validateConnection()
|
||||
|
||||
XCTAssertFalse(response.success)
|
||||
XCTAssertFalse(manager.moduleUpdateRecommended)
|
||||
XCTAssertEqual(requester.requestedURLs.map(\.path), ["/wloc-settings/save"])
|
||||
XCTAssertEqual(requester.requestedURLs.first?.query, "action=query")
|
||||
}
|
||||
|
||||
func testMissingVersionDisablesOnlyAdvancedFeatures() async {
|
||||
let requester = FakeThirdPartyRequester(
|
||||
body: #"{"success":false,"error":"无已保存的坐标"}"#,
|
||||
versionBody: "not-json"
|
||||
)
|
||||
let manager = ThirdPartyProxyManager(requester: requester)
|
||||
|
||||
let isAvailable = await manager.refreshAdvancedFeatureAvailability()
|
||||
|
||||
XCTAssertFalse(isAvailable)
|
||||
XCTAssertTrue(manager.moduleUpdateRecommended)
|
||||
XCTAssertEqual(manager.connectionState, .unknown)
|
||||
XCTAssertEqual(requester.requestedURLs.map(\.path), ["/wloc-settings/version"])
|
||||
}
|
||||
|
||||
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, versionBody: "not-json")
|
||||
let manager = ThirdPartyProxyManager(requester: requester)
|
||||
|
||||
let response = try await manager.save(favorite)
|
||||
|
||||
XCTAssertTrue(response.success)
|
||||
XCTAssertFalse(manager.moduleUpdateRecommended)
|
||||
XCTAssertEqual(manager.connectionState, .connected(active: true))
|
||||
XCTAssertEqual(requester.requestedURLs.map(\.path), ["/wloc-settings/save"])
|
||||
XCTAssertNil(requester.requestedURLs.first?.query)
|
||||
}
|
||||
|
||||
func testBrokenSaveQueryFailsWithoutCheckingVersion() async {
|
||||
let requester = FakeThirdPartyRequester(body: "not-json")
|
||||
let manager = ThirdPartyProxyManager(requester: requester)
|
||||
|
||||
do {
|
||||
_ = try await manager.validateConnection()
|
||||
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)
|
||||
@@ -78,6 +145,10 @@ final class ThirdPartyProxyManagerTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testClientLinksUseProjectOwnedMirrorModulesAndVerificationLabels() {
|
||||
XCTAssertEqual(
|
||||
ThirdPartyProxyManager.interceptionHostnamesText,
|
||||
"gs-loc.apple.com, gs-loc-cn.apple.com"
|
||||
)
|
||||
XCTAssertNil(ThirdPartyProxyClient.shadowrocket.verificationText)
|
||||
XCTAssertTrue(ThirdPartyProxyClient.surge.verificationText?.contains("尚未验证") == true)
|
||||
XCTAssertEqual(ThirdPartyProxyClient.egern.subscriptionURL, ThirdPartyProxyClient.surge.subscriptionURL)
|
||||
@@ -121,14 +192,19 @@ private final class FakeThirdPartyRequester: ThirdPartyProxyRequesting {
|
||||
private let data: Data
|
||||
private let versionData: Data
|
||||
private(set) var lastURL: URL?
|
||||
private(set) var requestedURLs: [URL] = []
|
||||
|
||||
init(body: String) {
|
||||
init(
|
||||
body: String,
|
||||
versionBody: String = #"{"success":true,"moduleVersion":"1.0.0","protocolVersion":1,"capabilities":["wifi","cellTower","arpc","marker","synthetic","bare","motionSimulation"]}"#
|
||||
) {
|
||||
data = Data(body.utf8)
|
||||
versionData = Data(#"{"success":true,"moduleVersion":"1.0.0","protocolVersion":1,"capabilities":["wifi","cellTower","arpc","marker","synthetic","bare","motionSimulation"]}"#.utf8)
|
||||
versionData = Data(versionBody.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,
|
||||
|
||||
@@ -40,6 +40,10 @@ grep -Fq '清除:GET ?action=clear' "$SETUP" \
|
||||
|
||||
for file in wloc.module wloc.sgmodule wloc.conf wloc.lpx wloc.stoverride; do
|
||||
test -s "$MODULES/$file" || fail "missing bundled module: $file"
|
||||
grep -q 'gs-loc.apple.com' "$MODULES/$file" \
|
||||
|| fail "$file must include gs-loc.apple.com in its MITM hostnames"
|
||||
grep -q 'gs-loc-cn.apple.com' "$MODULES/$file" \
|
||||
|| fail "$file must include gs-loc-cn.apple.com in its MITM hostnames"
|
||||
done
|
||||
|
||||
grep -q 'wloc.sgmodule' "$MANAGER" || fail "Surge/Egern module mapping is missing"
|
||||
@@ -48,7 +52,13 @@ grep -q 'shadowrocket://' "$MANAGER" || fail "Shadowrocket launch URL is missing
|
||||
for scheme in surge quantumult-x loon stash egern; do
|
||||
grep -q "${scheme}://" "$MANAGER" || fail "$scheme launch URL is missing"
|
||||
done
|
||||
grep -q '复制解密域名' "$SETUP" || fail "Shadowrocket MITM hostname copy action is missing"
|
||||
grep -q '复制两个解密域名' "$SETUP" || fail "all clients must expose both MITM hostnames"
|
||||
grep -q 'gs-loc.apple.com 和 gs-loc-cn.apple.com' "$SETUP" \
|
||||
|| fail "setup guidance must name both Apple location hostnames"
|
||||
grep -q 'ThirdPartyProxyManager.interceptionHostnamesText' "$SETUP" \
|
||||
|| fail "setup hostname copy actions must use the shared two-host value"
|
||||
grep -q 'ThirdPartyProxyManager.interceptionHostnamesText' "$SETTINGS" \
|
||||
|| fail "Settings must expose the shared two-host copy action"
|
||||
grep -q '配置 → 模块' "$SETUP" || fail "Shadowrocket module import guidance is missing"
|
||||
grep -q 'HTTPS 解密' "$SETUP" || fail "Shadowrocket HTTPS decryption guidance is missing"
|
||||
! grep -q '当前可测试' "$SETUP" || fail "Shadowrocket must not show the obsolete current-test label"
|
||||
@@ -64,10 +74,43 @@ grep -q 'Label("查看诊断日志"' "$SETUP" \
|
||||
|| fail "third-party connection failure must expose the diagnostics action"
|
||||
! grep -q 'setupActionError = error.localizedDescription' "$SETUP" \
|
||||
|| fail "third-party connection failure must not use the generic failure alert"
|
||||
grep -q 'let response = try await thirdPartyProxy.query()' "$SETUP" \
|
||||
|| fail "the import page must query the third-party module"
|
||||
grep -A15 'let response = try await thirdPartyProxy.query()' "$SETUP" | grep -q 'onComplete()' \
|
||||
grep -q 'let response = try await thirdPartyProxy.validateConnection()' "$SETUP" \
|
||||
|| fail "the import page must validate the legacy save/query endpoint"
|
||||
grep -A20 'let response = try await thirdPartyProxy.validateConnection()' "$SETUP" \
|
||||
| grep -q 'refreshAdvancedFeatureAvailability()' \
|
||||
|| fail "the import page must also probe advanced module capabilities"
|
||||
grep -A20 'let response = try await thirdPartyProxy.validateConnection()' "$SETUP" \
|
||||
| grep -q 'motionSimulation.setEnabled(false)' \
|
||||
|| fail "an incompatible module must turn motion simulation off without failing basic setup"
|
||||
grep -A20 'let response = try await thirdPartyProxy.validateConnection()' "$SETUP" | grep -q 'onComplete()' \
|
||||
|| fail "a successful third-party connection test must close setup immediately"
|
||||
grep -q 'components.queryItems = \[URLQueryItem(name: "action", value: "query")\]' "$MANAGER" \
|
||||
|| fail "the connection test must preserve the established save?action=query contract"
|
||||
grep -q 'thirdPartyProxy.moduleUpdateRecommended' "$SETTINGS" \
|
||||
|| fail "Settings must react to legacy module compatibility mode"
|
||||
grep -q '当前模块版本较旧,基础坐标功能仍可继续使用' "$SETTINGS" \
|
||||
|| fail "Settings must explain that legacy modules retain basic coordinate support"
|
||||
! grep -A8 'Toggle("运动状态模拟"' "$SETTINGS" | grep -q 'thirdPartyProxy.moduleUpdateRecommended' \
|
||||
|| fail "legacy module compatibility must turn motion simulation off without disabling its toggle"
|
||||
grep -q 'refreshAdvancedFeatureAvailability' "$SETTINGS" \
|
||||
|| fail "Settings must probe advanced module availability independently"
|
||||
grep -A18 'guard thirdPartyProxy.activeSettings?.success == true else' "$SETTINGS" \
|
||||
| grep -q 'refreshAdvancedFeatureAvailability()' \
|
||||
|| fail "enabling inactive third-party motion simulation must validate the version endpoint first"
|
||||
grep -A18 'guard thirdPartyProxy.activeSettings?.success == true else' "$SETTINGS" \
|
||||
| grep -q 'motionSimulation.setEnabled(true)' \
|
||||
|| fail "inactive third-party motion simulation may enable only after version validation succeeds"
|
||||
grep -q 'proxyOperationAlertTitle = "无法开启运动状态模拟"' "$SETTINGS" \
|
||||
|| fail "motion simulation must show a dedicated failure alert when version validation fails"
|
||||
grep -q '请重新导入最新模块脚本后再开启' "$SETTINGS" \
|
||||
|| fail "motion simulation failure must tell the user to update the module script"
|
||||
test "$(grep -c 'presentMotionSimulationModuleUpdateAlert()' "$SETTINGS")" -ge 3 \
|
||||
|| fail "inactive and active motion simulation paths must share the module-update alert"
|
||||
grep -q 'onChange(of: thirdPartyProxy.moduleUpdateRecommended)' "$SETTINGS" \
|
||||
|| fail "Settings must react when an installed module becomes incompatible"
|
||||
grep -A5 'private func disableUnsupportedThirdPartyMotionSimulation()' "$SETTINGS" \
|
||||
| grep -q 'motionSimulation.setEnabled(false)' \
|
||||
|| fail "unsupported third-party modules must force motion simulation off before disabling the toggle"
|
||||
! grep -q 'thirdPartyTestResult?.success' "$SETUP" \
|
||||
|| fail "a successful third-party test must not leave a separate completion state"
|
||||
grep -q 'setupStep = \.thirdPartyImport' "$ROOT/App/SetupCoordinator.swift" \
|
||||
@@ -112,7 +155,7 @@ test -s "$ROOT/docs/onboarding-screenshots/shadowrocket/shadowrocket-module-impo
|
||||
grep -q 'presentSuccessfulOperationTip(.activation)' "$ROOT/App/MapHomeView.swift" || fail "third-party save must present the activation tip"
|
||||
grep -q 'presentSuccessfulOperationTip(.deactivation)' "$ROOT/App/MapHomeView.swift" || fail "third-party clear must present the deactivation tip"
|
||||
grep -q 'if spoofState == .active' "$ROOT/App/MapHomeView.swift" || fail "manual help must follow the shared spoof state"
|
||||
grep -q 'MARKETING_VERSION: "1.0.4"' "$ROOT/project.yml" || fail "marketing version must be 1.0.4"
|
||||
grep -q 'CURRENT_PROJECT_VERSION: "5"' "$ROOT/project.yml" || fail "build version must be 5"
|
||||
grep -q 'MARKETING_VERSION: "1.0.5"' "$ROOT/project.yml" || fail "marketing version must be 1.0.5"
|
||||
grep -q 'CURRENT_PROJECT_VERSION: "6"' "$ROOT/project.yml" || fail "build version must be 6"
|
||||
|
||||
echo "PASS: third-party proxy mode contract"
|
||||
|
||||
@@ -25,7 +25,7 @@ import sys
|
||||
with open(sys.argv[1], encoding="utf-8") as handle:
|
||||
config = json.load(handle)
|
||||
|
||||
assert config["latestVersion"] == "1.0.4"
|
||||
assert config["latestVersion"] == "1.0.5"
|
||||
assert config["minimumSupportedVersion"] == "1.0.0"
|
||||
assert "shadowrocket" not in config["communityPromptClients"]
|
||||
assert set(config["communityPromptClients"]) == {
|
||||
@@ -39,6 +39,10 @@ grep -q 'timeoutIntervalForRequest = 1.5' "$CONFIG" \
|
||||
|| fail "remote configuration requests must use a short timeout"
|
||||
grep -q 'timeoutIntervalForResource = 2' "$CONFIG" \
|
||||
|| fail "remote configuration resource loading must use a short timeout"
|
||||
grep -q 'gh-proxy.org/https://raw.githubusercontent.com/xweiba/location-spoofer/main/version.txt' "$CONFIG" \
|
||||
|| fail "update detection must prefer the domestic GitHub Raw mirror"
|
||||
grep -q 'static let configurationURLs = \[' "$CONFIG" \
|
||||
|| fail "update detection must retain multiple configuration sources"
|
||||
! grep -q 'data.count' "$CONFIG" \
|
||||
|| fail "the client must not impose a remote configuration file-size limit"
|
||||
grep -q 'docs/releases/v\\(version).md' "$CONFIG" \
|
||||
@@ -49,6 +53,12 @@ grep -q '.task { await checkForUpdates() }' "$CONTENT" \
|
||||
|| fail "update detection must run asynchronously outside bootstrap"
|
||||
! grep -A90 'private func bootstrap() async' "$CONTENT" | grep -q 'fetch()' \
|
||||
|| fail "remote configuration must not block the startup gate"
|
||||
grep -Fq 'Label("检查更新"' "$SETTINGS" \
|
||||
|| fail "Settings must expose a manual update check"
|
||||
grep -Fq 'title: Text("已是最新版本")' "$SETTINGS" \
|
||||
|| fail "manual update checks must report the current-version result"
|
||||
grep -Fq 'title: Text("检查更新失败")' "$SETTINGS" \
|
||||
|| fail "manual update checks must report network failures"
|
||||
|
||||
grep -q '社区分享成功配置?' "$MAP" \
|
||||
|| fail "non-Shadowrocket success must offer community contribution"
|
||||
|
||||
Reference in New Issue
Block a user