mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-08 10:44:50 +00:00
Every case here restates a rule another test already pins with the same or a wider input class: - the activation group case is byte-identical to its neighbour, minus an extra assertion; the rule snapshot version guard is re-driven with a second value of the same schemaVersion check; the global-mode deployment-failure case is a mode-literal copy of the strictly stronger direct-mode one; the clear- failure case duplicates the deferred-retry sibling - two RuntimeDocument projection cases only re-map locals, already asserted as full array equality; the IPv6-LAN endpoint case repeats the direct-mode case (the derivation reads only ports) - the three HTTP export cases varied only listenerMode while the derivation ignores it and one name contradicted its own expectation; one loop over all listener modes now states the real rule - the 0700/0600 permission baseline is one AtomicFileWriter behavior and stays pinned through RuntimeFileStoreTests, which also covers self-heal and atomic replace The command-boundary case keeps its mixed-scenario isolation check (four commands, four independent adapter counters) and drops only its unfalsifiable queriedTargetIDs claim. Co-Authored-By: DeepSeek Harness <noreply@deepseek.com>
100 lines
4.6 KiB
Swift
100 lines
4.6 KiB
Swift
import Combine
|
|
import XCTest
|
|
|
|
@testable import ShadowsocksX_NG2
|
|
|
|
/// 全局模式以 ACL 实现:proxy_all + 固定本地绕过;无活动服务器
|
|
/// 时 agent 仍监听、系统代理保持待应用;ACL 变化完整重启,失败保留最新意图。
|
|
final class ProxyRuntimeGlobalModeTests: ProxyRuntimeControllerTests {
|
|
override func makeDefaultRuleSnapshots() -> BuiltinRuleSnapshots {
|
|
BuiltinRuleSnapshots(loader: ProxyRuntimeFixture.controlFlowRuleSnapshot)
|
|
}
|
|
|
|
func testGlobalModeDeploysProxyAllACLAndProjectsSOCKSProxyWithFixedExceptions() async throws {
|
|
let seeded = try makeSeededCatalog()
|
|
let listeningStore = InMemoryProxyListeningStore()
|
|
let controller = makeController(
|
|
probe: ProxyRuntimeFixture.FakeProbe.reachable(),
|
|
listeningStore: listeningStore,
|
|
settings: ProxyRuntimeSettings(
|
|
listen: ActivationFixture.listen, agentEnabled: true, systemProxyEnabled: true))
|
|
try await controller.activate(seeded.server)
|
|
XCTAssertEqual(controller.systemProxyState, .applied)
|
|
let unregisterCount = agent.unregisterCount
|
|
let runtimeStore = RuntimeFileStore(fileURL: runtime.contract)
|
|
|
|
await controller.setProxyMode(.global)
|
|
|
|
XCTAssertEqual(controller.proxyMode, .global)
|
|
XCTAssertEqual(controller.settings.preferredMode, .global)
|
|
XCTAssertEqual(controller.controlPreferences.preferredMode, .global)
|
|
XCTAssertEqual(controller.state, .running)
|
|
XCTAssertEqual(
|
|
agent.unregisterCount, unregisterCount + 1, "ACL 变化触发完整 agent 重启")
|
|
let document = try XCTUnwrap(runtimeStore.loadDocument())
|
|
XCTAssertEqual(document.aclRuntime?.summary, "global")
|
|
let expectedACL = ProxyACLDocument.global(at: runtimeStore.aclFileURL)
|
|
XCTAssertTrue(expectedACL.content.hasPrefix("[proxy_all]\n[bypass_list]\n"))
|
|
XCTAssertEqual(
|
|
try Data(contentsOf: runtimeStore.aclFileURL),
|
|
Data(expectedACL.content.utf8),
|
|
"链接解析后的内容是 global 变体")
|
|
XCTAssertEqual(
|
|
systemProxy.applied.last?.socks,
|
|
.init(host: "127.0.0.1", port: ActivationFixture.listen.socksPort))
|
|
XCTAssertEqual(
|
|
systemProxy.applied.last?.http,
|
|
.init(host: "127.0.0.1", port: ActivationFixture.listen.httpPort))
|
|
XCTAssertTrue(
|
|
Set(FixedLocalProxyRanges.systemProxyExceptions).isSubset(
|
|
of: Set(systemProxy.applied.last?.exceptions ?? [])),
|
|
"系统例外包含固定本地范围")
|
|
XCTAssertEqual(controller.systemProxyState, .applied)
|
|
}
|
|
|
|
func testGlobalModeWithoutServerKeepsListeningAndPendingUntilTargetRecovers() async throws {
|
|
// 目录先播种、控制器再创建:bootstrap 快照读取器持有已提交目录,但尚无
|
|
// 活动目标——正是「全局模式 + 无可用出口」的起点。
|
|
let seeded = try makeSeededCatalog()
|
|
let listeningStore = InMemoryProxyListeningStore()
|
|
let controller = makeController(
|
|
probe: ProxyRuntimeFixture.FakeProbe.reachable(),
|
|
listeningStore: listeningStore,
|
|
settings: ProxyRuntimeSettings(
|
|
listen: ActivationFixture.listen, agentEnabled: true, systemProxyEnabled: true))
|
|
await controller.resyncOnLaunch()
|
|
XCTAssertEqual(controller.state, .running, "无活动目标时 agent 仍监听")
|
|
XCTAssertNil(controller.activeTargetID)
|
|
let unregisterCount = agent.unregisterCount
|
|
|
|
await controller.setProxyMode(.global)
|
|
|
|
XCTAssertEqual(controller.proxyMode, .global)
|
|
XCTAssertEqual(controller.state, .running, "全局模式空服务器列表仍提供本地监听")
|
|
XCTAssertEqual(
|
|
agent.unregisterCount, unregisterCount + 1, "ACL 变化触发完整 agent 重启")
|
|
let runtimeStore = RuntimeFileStore(fileURL: runtime.contract)
|
|
let document = try XCTUnwrap(runtimeStore.loadDocument())
|
|
XCTAssertTrue(document.servers.isEmpty, "无活动目标时服务器列表为空")
|
|
XCTAssertEqual(document.aclRuntime?.summary, "global")
|
|
XCTAssertEqual(
|
|
controller.systemProxyState, .paused,
|
|
"无可用代理出口时系统代理意图保持待应用")
|
|
XCTAssertTrue(systemProxy.applied.isEmpty, "不得把系统代理指向无出口入口")
|
|
|
|
// 服务器有效且端点健康后自动应用已开启的系统代理意图。
|
|
try await controller.activate(seeded.server)
|
|
|
|
XCTAssertEqual(controller.state, .running)
|
|
XCTAssertEqual(
|
|
controller.systemProxyState, .applied,
|
|
"服务器有效且健康后自动收敛待应用意图")
|
|
XCTAssertEqual(
|
|
systemProxy.applied.last?.socks,
|
|
.init(host: "127.0.0.1", port: ActivationFixture.listen.socksPort))
|
|
XCTAssertEqual(
|
|
systemProxy.applied.last?.http,
|
|
.init(host: "127.0.0.1", port: ActivationFixture.listen.httpPort))
|
|
}
|
|
}
|