mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-08 10:54: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>
140 lines
6.3 KiB
Swift
140 lines
6.3 KiB
Swift
import XCTest
|
||
|
||
@testable import ShadowsocksX_NG2
|
||
|
||
extension ProxyControlWorkflowIntegrationTests {
|
||
func testPassiveNetworkChangesNeverReapplySystemProxy() async throws {
|
||
let server = try makeSeededCatalog()
|
||
let composition = makeProxies(probe: ProxyRuntimeFixture.FakeProbe.reachable())
|
||
_ = try await composition.catalog.activate(server)
|
||
_ = await composition.control.setAgentEnabled(true)
|
||
_ = await composition.control.setSystemProxyEnabled(true)
|
||
let writes = systemProxy.applied.count
|
||
XCTAssertEqual(writes, 1)
|
||
await emitAndWaitForInspection(
|
||
[.networkConfiguration, .proxyConfiguration, .networkPath], in: composition)
|
||
XCTAssertEqual(systemProxy.applied.count, writes)
|
||
XCTAssertEqual(systemProxy.clearCount, 0)
|
||
}
|
||
|
||
// MARK: - 系统代理开关
|
||
|
||
func testSystemProxyIntentAppliesThroughSnapshot() async throws {
|
||
let server = try makeSeededCatalog()
|
||
let composition = makeProxies(probe: ProxyRuntimeFixture.FakeProbe.reachable())
|
||
|
||
_ = try await composition.catalog.activate(server)
|
||
_ = await composition.control.setAgentEnabled(true)
|
||
let snapshot = await composition.control.setSystemProxyEnabled(true)
|
||
|
||
XCTAssertTrue(snapshot.systemProxyIntentEnabled)
|
||
XCTAssertEqual(snapshot.systemProxyApplication, .applied)
|
||
XCTAssertEqual(snapshot.runtime.status, .running, "agent 运行状态不受系统代理影响")
|
||
XCTAssertEqual(systemProxy.applied.count, 1)
|
||
}
|
||
|
||
/// 健康门禁:端点不健康时系统代理意图保持待应用(快照呈现 pending)。
|
||
func testSystemProxyIntentStaysPendingWhenEndpointsUnhealthy() async throws {
|
||
let server = try makeSeededCatalog()
|
||
let composition = makeProxies(probe: ProxyRuntimeFixture.FakeProbe.refusing())
|
||
|
||
_ = try await composition.catalog.activate(server)
|
||
_ = await composition.control.setAgentEnabled(true)
|
||
let snapshot = await composition.control.setSystemProxyEnabled(true)
|
||
|
||
XCTAssertTrue(snapshot.systemProxyIntentEnabled)
|
||
XCTAssertEqual(snapshot.systemProxyApplication, .paused, "待应用进入 snapshot")
|
||
XCTAssertEqual(snapshot.runtime.status, .launchFailed)
|
||
XCTAssertTrue(systemProxy.applied.isEmpty, "健康门未过不写系统设置")
|
||
}
|
||
|
||
/// 系统代理写入失败:typed fact 进入 snapshot,agent 继续运行。
|
||
func testSystemProxyFailureSurfacesTypedFactInSnapshot() async throws {
|
||
let server = try makeSeededCatalog()
|
||
systemProxy.applyError = SystemProxyError.applyFailed("denied")
|
||
let composition = makeProxies(probe: ProxyRuntimeFixture.FakeProbe.reachable())
|
||
|
||
_ = try await composition.catalog.activate(server)
|
||
_ = await composition.control.setAgentEnabled(true)
|
||
let snapshot = await composition.control.setSystemProxyEnabled(true)
|
||
|
||
XCTAssertTrue(snapshot.systemProxyIntentEnabled)
|
||
XCTAssertEqual(
|
||
snapshot.systemProxyApplication, .failed(.operation(.applyFailed)))
|
||
XCTAssertEqual(snapshot.runtime, ProxyRuntimeFacts(status: .running, isOn: true))
|
||
}
|
||
|
||
/// 关闭系统代理:清理匹配端点的设置;本地监听不停止。
|
||
func testSystemProxyOffKeepsAgentRunningAndReflectsIdleInSnapshot() async throws {
|
||
let server = try makeSeededCatalog()
|
||
let composition = makeProxies(probe: ProxyRuntimeFixture.FakeProbe.reachable())
|
||
_ = try await composition.catalog.activate(server)
|
||
_ = await composition.control.setAgentEnabled(true)
|
||
_ = await composition.control.setSystemProxyEnabled(true)
|
||
|
||
let snapshot = await composition.control.setSystemProxyEnabled(false)
|
||
|
||
XCTAssertFalse(snapshot.systemProxyIntentEnabled)
|
||
XCTAssertEqual(snapshot.systemProxyApplication, .idle)
|
||
XCTAssertEqual(snapshot.runtime, ProxyRuntimeFacts(status: .running, isOn: true))
|
||
XCTAssertEqual(systemProxy.clearCount, 1)
|
||
XCTAssertEqual(agent.unregisterCount, 0, "不注销 agent")
|
||
}
|
||
|
||
/// 无活动目标:agent 监听,但需要出口的系统代理意图保持待应用。
|
||
func testSystemProxyIntentStaysPendingWithoutActiveTarget() async throws {
|
||
_ = try makeSeededCatalog()
|
||
let composition = makeProxies(
|
||
probe: ProxyRuntimeFixture.FakeProbe.reachable(),
|
||
settings: ProxyRuntimeSettings(listen: ActivationFixture.listen, agentEnabled: false))
|
||
_ = await composition.control.setAgentEnabled(true)
|
||
|
||
let snapshot = await composition.control.setSystemProxyEnabled(true)
|
||
|
||
XCTAssertEqual(snapshot.runtime.status, .running, "agent 以空列表监听")
|
||
XCTAssertEqual(snapshot.systemProxyApplication, .paused, "无可用出口保持待应用")
|
||
XCTAssertTrue(systemProxy.applied.isEmpty)
|
||
XCTAssertNil(snapshot.activeTarget)
|
||
}
|
||
|
||
/// 待应用意图在目标激活后自动收敛。目录命令后的 snapshot 更新
|
||
/// 经主队列 hop 到达,有界等待收敛。
|
||
func testPendingSystemProxyIntentConvergesAfterTargetActivation() async throws {
|
||
let server = try makeSeededCatalog()
|
||
let composition = makeProxies(probe: ProxyRuntimeFixture.FakeProbe.reachable())
|
||
_ = await composition.control.setAgentEnabled(true)
|
||
let pending = await composition.control.setSystemProxyEnabled(true)
|
||
XCTAssertEqual(pending.systemProxyApplication, .paused)
|
||
|
||
_ = try await composition.catalog.activate(server)
|
||
let deadline = Date().addingTimeInterval(2)
|
||
while composition.control.snapshot.systemProxyApplication != .applied && Date() < deadline {
|
||
try await Task.sleep(nanoseconds: 10_000_000)
|
||
}
|
||
|
||
XCTAssertEqual(composition.control.snapshot.systemProxyApplication, .applied, "激活后自动收敛")
|
||
XCTAssertEqual(systemProxy.applied.count, 1)
|
||
}
|
||
|
||
// MARK: - HTTP 导出能力派生(生产 adapter 的唯一点)
|
||
|
||
func testHTTPExportCapabilityDerivesSafeCopyableLineForLoopback() {
|
||
XCTAssertEqual(
|
||
HTTPExportCapability(listen: ProxyListeningConfiguration()).copyableLine,
|
||
"export http_proxy=http://127.0.0.1:11087;export https_proxy=http://127.0.0.1:11087;")
|
||
}
|
||
|
||
/// 导出行恒指向本地监听回环 127.0.0.1,与 listenerMode 无关(ADR-0034)。
|
||
func testHTTPExportCapabilityAlwaysUsesIPv4LoopbackRegardlessOfListenerMode() {
|
||
for mode in ListenerMode.allCases {
|
||
var listen = ProxyListeningConfiguration(listenerMode: mode)
|
||
listen.httpPort = 8080
|
||
|
||
XCTAssertEqual(
|
||
HTTPExportCapability(listen: listen).copyableLine,
|
||
"export http_proxy=http://127.0.0.1:8080;export https_proxy=http://127.0.0.1:8080;",
|
||
"\(mode) 不改变导出行")
|
||
}
|
||
}
|
||
}
|