mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-10-26 11:43:48 +00:00
474 lines
19 KiB
Swift
474 lines
19 KiB
Swift
import Testing
|
|
import XCTest
|
|
|
|
@testable import ShadowsocksX_NG2
|
|
|
|
@MainActor
|
|
struct LANCommandEnablementTests {
|
|
@Test(arguments: [false, true])
|
|
func copyRejectsLostIPIncludingAButtonRenderedBeforeNetworkNotification(
|
|
notificationDelivered: Bool
|
|
) {
|
|
let runtime = FakeProxyRuntime()
|
|
let interfaces = FakeLocalInterfaceFacts()
|
|
runtime.listenFacts.lan.isEnabled = true
|
|
interfaces.interfaces = [FakeLocalInterfaceFacts.wifi(addresses: [.ipv4("192.0.2.2")])]
|
|
let workflow = ProxyControlWorkflow(
|
|
runtime: runtime, targetFacts: FakeTargetFacts(), interfaceFacts: interfaces)
|
|
let address = workflow.snapshot.commandAddressPicker.candidates[1]
|
|
workflow.selectCommandAddress(address)
|
|
interfaces.interfaces = []
|
|
if notificationDelivered { interfaces.emitChange() }
|
|
#expect(throws: TerminalCommandFailure.addressUnavailable) {
|
|
try workflow.prepareTerminalCommandsForCopy(expectedAddress: address.identity)
|
|
}
|
|
#expect(workflow.snapshot.commandAddressPicker.selected.address == "127.0.0.1")
|
|
#expect(workflow.snapshot.terminalCommandShells == [.zshBash, .fish, .nushell])
|
|
#expect(runtime.agentCommands.isEmpty)
|
|
#expect(runtime.systemProxyCommands.isEmpty)
|
|
}
|
|
|
|
@Test func windowsFormatsRequireEnabledLANAndSelectedNetworkIPWithoutRequiringRunningProxy() {
|
|
let runtime = FakeProxyRuntime()
|
|
let interfaces = FakeLocalInterfaceFacts()
|
|
let workflow = ProxyControlWorkflow(
|
|
runtime: runtime, targetFacts: FakeTargetFacts(), interfaceFacts: interfaces)
|
|
#expect(workflow.snapshot.terminalCommandShells == [.zshBash, .fish, .nushell])
|
|
runtime.listenFacts.lan.isEnabled = true
|
|
interfaces.interfaces = [FakeLocalInterfaceFacts.wifi(addresses: [.ipv4("192.0.2.2")])]
|
|
runtime.emitChange()
|
|
#expect(workflow.snapshot.terminalCommandShells == [.zshBash, .fish, .nushell])
|
|
workflow.selectCommandAddress(workflow.snapshot.commandAddressPicker.candidates[1])
|
|
runtime.runtimeFacts = ProxyRuntimeFacts(status: .off, isOn: false)
|
|
runtime.emitChange()
|
|
#expect(
|
|
workflow.snapshot.terminalCommandShells == [
|
|
.zshBash, .fish, .nushell, .windowsCMD, .windowsPowerShell,
|
|
])
|
|
runtime.listenFacts.lan.isEnabled = false
|
|
runtime.emitChange()
|
|
#expect(workflow.snapshot.terminalCommandShells == [.zshBash, .fish, .nushell])
|
|
#expect(runtime.agentCommands.isEmpty)
|
|
#expect(runtime.systemProxyCommands.isEmpty)
|
|
}
|
|
|
|
@Test func disablingLANRetainsIPv6ModeAndFallsBackFromSelectedNetworkAddress() {
|
|
let runtime = FakeProxyRuntime()
|
|
let interfaces = FakeLocalInterfaceFacts()
|
|
let workflow = ProxyControlWorkflow(
|
|
runtime: runtime, targetFacts: FakeTargetFacts(), interfaceFacts: interfaces)
|
|
runtime.listenFacts.lan.isEnabled = true
|
|
runtime.listenFacts.lan.mode = .allIPv6Interfaces
|
|
interfaces.interfaces = [
|
|
FakeLocalInterfaceFacts.wifi(addresses: [.ipv6("2001:db8::1")])
|
|
]
|
|
runtime.emitChange()
|
|
let address = workflow.snapshot.commandAddressPicker.candidates[1]
|
|
_ = workflow.selectCommandAddress(address)
|
|
#expect(workflow.snapshot.commandAddressPicker.selected.address == "2001:db8::1")
|
|
runtime.listenFacts.lan.isEnabled = false
|
|
runtime.emitChange()
|
|
#expect(runtime.listenFacts.lan.mode == .allIPv6Interfaces)
|
|
let picker = workflow.snapshot.commandAddressPicker
|
|
#expect(!picker.isVisible)
|
|
#expect(picker.candidates.map(\.address) == ["127.0.0.1"])
|
|
#expect(picker.selected.address == "127.0.0.1")
|
|
#expect(
|
|
workflow.snapshot.terminalProxyEnvironmentCommands.zshBash.contains("http://127.0.0.1:11087"))
|
|
}
|
|
}
|
|
|
|
/// 首页命令地址选择器 contract。候选派生:启用状态与三种监听方式的地址族
|
|
/// 过滤与默认值、严格接口类型过滤、名称回退、排序合并与网络变化刷新。选择
|
|
/// 生命周期与命令生成见 `ProxyCommandAddressLifecycleTests`。复用
|
|
/// ProxyControlWorkflowTests 的可编程替身。
|
|
@MainActor
|
|
final class ProxyCommandAddressWorkflowTests: XCTestCase {
|
|
private var runtime: FakeProxyRuntime!
|
|
private var interfaceFacts: FakeLocalInterfaceFacts!
|
|
private var workflow: ProxyControlWorkflow!
|
|
|
|
override func setUp() async throws {
|
|
try await super.setUp()
|
|
runtime = FakeProxyRuntime()
|
|
interfaceFacts = FakeLocalInterfaceFacts()
|
|
workflow = ProxyControlWorkflow(
|
|
runtime: runtime, targetFacts: FakeTargetFacts(), interfaceFacts: interfaceFacts)
|
|
}
|
|
|
|
/// 把已保存监听事实切到指定方式并触发重观察。
|
|
private func setListenerMode(_ mode: ListenerMode) {
|
|
runtime.listenFacts = RuntimeListenFacts(
|
|
listenerMode: .disabled, socksPort: 11086, httpPort: 11087)
|
|
runtime.listenFacts.lan.isEnabled = mode != .disabled
|
|
if let lanMode = LANListenerMode(rawValue: mode.rawValue) {
|
|
runtime.listenFacts.lan.mode = lanMode
|
|
}
|
|
runtime.emitChange()
|
|
}
|
|
|
|
func testLocalhostModeHidesPickerWithLoopbackOnlyCandidates() {
|
|
setListenerMode(.disabled)
|
|
interfaceFacts.interfaces = [
|
|
FakeLocalInterfaceFacts.wifi(addresses: [.ipv4("192.168.1.10")]),
|
|
FakeLocalInterfaceFacts.loopback(),
|
|
]
|
|
interfaceFacts.emitChange()
|
|
|
|
let picker = workflow.snapshot.commandAddressPicker
|
|
XCTAssertFalse(picker.isVisible, "仅本机监听方式隐藏下拉框")
|
|
XCTAssertEqual(
|
|
picker.candidates,
|
|
[TerminalCommandAddress(bsdName: "lo0", address: "127.0.0.1", displayName: "lo0")],
|
|
"仅本机方式候选只有 IPv4 回环")
|
|
XCTAssertEqual(picker.selected.address, "127.0.0.1")
|
|
}
|
|
|
|
func testIPv4ModeFiltersFamiliesAndAllowedInterfaceTypes() {
|
|
setListenerMode(.allIPv4Interfaces)
|
|
interfaceFacts.interfaces = [
|
|
FakeLocalInterfaceFacts.wifi(
|
|
addresses: [.ipv4("192.168.1.10"), .ipv6("2001:db8::1"), .ipv6("fe80::1", linkLocal: true)]
|
|
),
|
|
FakeLocalInterfaceFacts.ethernet(addresses: [.ipv4("10.0.0.5")]),
|
|
FakeLocalInterfaceFacts.vpn(),
|
|
FakeLocalInterfaceFacts.virtual(),
|
|
FakeLocalInterfaceFacts.loopback(),
|
|
]
|
|
interfaceFacts.emitChange()
|
|
|
|
let picker = workflow.snapshot.commandAddressPicker
|
|
XCTAssertTrue(picker.isVisible)
|
|
XCTAssertEqual(
|
|
picker.candidates.map(\.address),
|
|
["127.0.0.1", "10.0.0.5", "192.168.1.10"],
|
|
"回环置顶,其余按接口名称排序;IPv6、VPN、其他类型与回环事实不进候选")
|
|
XCTAssertEqual(picker.selected.address, "127.0.0.1", "默认兼容回环地址")
|
|
}
|
|
|
|
func testDualStackModeIncludesBothLoopbacksAndBothFamilies() {
|
|
setListenerMode(.allIPv4AndIPv6Interfaces)
|
|
interfaceFacts.interfaces = [
|
|
FakeLocalInterfaceFacts.wifi(
|
|
addresses: [
|
|
.ipv6("2001:db8::1"), .ipv4("192.168.1.10"), .ipv4("169.254.9.9"),
|
|
.ipv6("fe80::1", linkLocal: true),
|
|
])
|
|
]
|
|
interfaceFacts.emitChange()
|
|
|
|
let picker = workflow.snapshot.commandAddressPicker
|
|
XCTAssertEqual(
|
|
picker.candidates.map(\.address),
|
|
["127.0.0.1", "169.254.9.9", "192.168.1.10", "2001:db8::1"],
|
|
"双栈提供两个回环(127.0.0.1 在前)、允许 IPv4 链路本地、排除 IPv6 链路本地,同接口 IPv4 先于 IPv6")
|
|
XCTAssertEqual(picker.selected.address, "127.0.0.1")
|
|
}
|
|
|
|
func testTemporaryIPv6ExcludedAndAnnotationsKeptAsSeparateFacts() {
|
|
setListenerMode(.allIPv4AndIPv6Interfaces)
|
|
interfaceFacts.interfaces = [
|
|
FakeLocalInterfaceFacts.wifi(
|
|
addresses: [
|
|
.ipv6("240e::8150", flags: [.autoconf, .secured]),
|
|
.ipv6("240e::855", flags: [.autoconf, .temporary]),
|
|
.ipv6("240e::17", flags: [.dynamic]),
|
|
])
|
|
]
|
|
interfaceFacts.emitChange()
|
|
|
|
let picker = workflow.snapshot.commandAddressPicker
|
|
XCTAssertEqual(
|
|
picker.candidates.map(\.address),
|
|
["127.0.0.1", "240e::17", "240e::8150"],
|
|
"RFC 4941 隐私临时地址被过滤(会轮换);其余按地址稳定排序")
|
|
let secured = picker.candidates.first { $0.address == "240e::8150" }
|
|
XCTAssertEqual(secured?.displayName, "Wi-Fi")
|
|
XCTAssertEqual(secured?.annotation, "autoconf secured", "注记作为独立呈现事实")
|
|
XCTAssertEqual(
|
|
picker.candidates.first { $0.address == "240e::17" }?.annotation,
|
|
"dynamic", "DHCPv6 地址注记 dynamic")
|
|
}
|
|
|
|
func testIPv6OnlyModeDefaultsToV1AndFiltersFamilies() {
|
|
setListenerMode(.allIPv6Interfaces)
|
|
interfaceFacts.interfaces = [
|
|
FakeLocalInterfaceFacts.wifi(
|
|
addresses: [.ipv4("192.168.1.10"), .ipv6("2001:db8::1")])
|
|
]
|
|
interfaceFacts.emitChange()
|
|
|
|
let picker = workflow.snapshot.commandAddressPicker
|
|
XCTAssertEqual(
|
|
picker.candidates.map(\.address), ["127.0.0.1", "2001:db8::1"],
|
|
"仅 IPv6 方式默认 ::1 且候选只有 IPv6")
|
|
XCTAssertEqual(picker.selected.address, "127.0.0.1")
|
|
}
|
|
|
|
func testDisplayNamesUseLocalizedNameWithBSDNameFallback() {
|
|
setListenerMode(.allIPv4Interfaces)
|
|
interfaceFacts.interfaces = [
|
|
FakeLocalInterfaceFacts.wifi(localizedName: "Wi-Fi", addresses: [.ipv4("192.168.1.10")]),
|
|
FakeLocalInterfaceFacts.ethernet(localizedName: nil, addresses: [.ipv4("10.0.0.5")]),
|
|
]
|
|
interfaceFacts.emitChange()
|
|
|
|
XCTAssertEqual(
|
|
workflow.snapshot.commandAddressPicker.candidates.map(\.displayName),
|
|
["lo0", "en1", "Wi-Fi"],
|
|
"名称缺失回退 BSD 名,回环显示 lo0")
|
|
}
|
|
|
|
func testSameInterfaceDuplicateIPMergesAcrossInterfacesKept() {
|
|
setListenerMode(.allIPv4Interfaces)
|
|
interfaceFacts.interfaces = [
|
|
FakeLocalInterfaceFacts.wifi(
|
|
localizedName: "Wi-Fi",
|
|
addresses: [.ipv4("192.168.1.10"), .ipv4("192.168.1.10")]),
|
|
FakeLocalInterfaceFacts.ethernet(
|
|
bsdName: "en2", localizedName: "Ethernet 2", addresses: [.ipv4("192.168.1.10")]),
|
|
]
|
|
interfaceFacts.emitChange()
|
|
|
|
let picker = workflow.snapshot.commandAddressPicker
|
|
XCTAssertEqual(
|
|
picker.candidates.map(\.identity),
|
|
[
|
|
TerminalCommandAddressIdentity(bsdName: "lo0", address: "127.0.0.1"),
|
|
TerminalCommandAddressIdentity(bsdName: "en2", address: "192.168.1.10"),
|
|
TerminalCommandAddressIdentity(bsdName: "en0", address: "192.168.1.10"),
|
|
],
|
|
"同接口重复 IP 合并;不同接口同 IP 保留接口身份")
|
|
}
|
|
|
|
func testInterfaceChangeNotificationRepublishesCandidates() {
|
|
setListenerMode(.allIPv4Interfaces)
|
|
|
|
interfaceFacts.interfaces = [
|
|
FakeLocalInterfaceFacts.wifi(addresses: [.ipv4("192.168.1.10")])
|
|
]
|
|
interfaceFacts.emitChange()
|
|
|
|
XCTAssertEqual(
|
|
workflow.snapshot.commandAddressPicker.candidates.map(\.address),
|
|
["127.0.0.1", "192.168.1.10"], "接口事实变化后候选随网络刷新")
|
|
}
|
|
}
|
|
|
|
/// 选择生命周期与命令生成:两种 shell 共用选中地址(端口取已
|
|
/// 保存值、IPv6 方括号、no_proxy 不变)、会话保留与新会话重置、失效回退
|
|
/// (地址消失/监听方式切换/复制前刷新/枚举失败)与命令边界。
|
|
@MainActor
|
|
final class ProxyCommandAddressLifecycleTests: XCTestCase {
|
|
private var runtime: FakeProxyRuntime!
|
|
private var interfaceFacts: FakeLocalInterfaceFacts!
|
|
private var workflow: ProxyControlWorkflow!
|
|
|
|
override func setUp() async throws {
|
|
try await super.setUp()
|
|
runtime = FakeProxyRuntime()
|
|
interfaceFacts = FakeLocalInterfaceFacts()
|
|
workflow = ProxyControlWorkflow(
|
|
runtime: runtime, targetFacts: FakeTargetFacts(), interfaceFacts: interfaceFacts)
|
|
}
|
|
|
|
private func setListenerMode(_ mode: ListenerMode) {
|
|
runtime.listenFacts = RuntimeListenFacts(
|
|
listenerMode: .disabled, socksPort: 11086, httpPort: 11087)
|
|
runtime.listenFacts.lan.isEnabled = mode != .disabled
|
|
if let lanMode = LANListenerMode(rawValue: mode.rawValue) {
|
|
runtime.listenFacts.lan.mode = lanMode
|
|
}
|
|
runtime.emitChange()
|
|
}
|
|
|
|
func testSelectionDrivesBothShellsAndSavedPorts() {
|
|
runtime.listenFacts = RuntimeListenFacts(
|
|
listenerMode: .disabled, socksPort: 11086, httpPort: 11087)
|
|
runtime.listenFacts.lan.isEnabled = true
|
|
runtime.listenFacts.lan.mode = .allIPv4Interfaces
|
|
runtime.listenFacts.lan.socksPort = 12086
|
|
runtime.listenFacts.lan.httpPort = 12087
|
|
runtime.emitChange()
|
|
interfaceFacts.interfaces = [
|
|
FakeLocalInterfaceFacts.wifi(addresses: [.ipv4("192.168.1.10")])
|
|
]
|
|
interfaceFacts.emitChange()
|
|
|
|
let wifi = workflow.snapshot.commandAddressPicker.candidates[1]
|
|
_ = workflow.selectCommandAddress(wifi)
|
|
|
|
let commands = workflow.snapshot.terminalProxyEnvironmentCommands
|
|
XCTAssertEqual(
|
|
commands.zshBash,
|
|
"export http_proxy='http://192.168.1.10:12087'; "
|
|
+ "export https_proxy='http://192.168.1.10:12087'; "
|
|
+ "export all_proxy='socks5://192.168.1.10:12086'; "
|
|
+ "export no_proxy='localhost,127.0.0.1,::1,.local';",
|
|
"HTTP/HTTPS/SOCKS 端点共用选中地址,端口取已保存值,no_proxy 保持不变")
|
|
XCTAssertEqual(
|
|
commands.fish,
|
|
"set -gx http_proxy 'http://192.168.1.10:12087'; "
|
|
+ "set -gx https_proxy 'http://192.168.1.10:12087'; "
|
|
+ "set -gx all_proxy 'socks5://192.168.1.10:12086'; "
|
|
+ "set -gx no_proxy 'localhost,127.0.0.1,::1,.local';",
|
|
"fish 语法保持 set -gx 形式且端点共用同一地址")
|
|
XCTAssertTrue(runtime.agentCommands.isEmpty, "命令选址不触发运行时命令")
|
|
XCTAssertTrue(runtime.systemProxyCommands.isEmpty)
|
|
XCTAssertTrue(runtime.modeCommands.isEmpty)
|
|
}
|
|
|
|
func testIPv6SelectionProducesBracketedURLs() {
|
|
setListenerMode(.allIPv4AndIPv6Interfaces)
|
|
interfaceFacts.interfaces = [
|
|
FakeLocalInterfaceFacts.wifi(
|
|
addresses: [.ipv6("2001:db8::1", flags: [.autoconf, .secured])])
|
|
]
|
|
interfaceFacts.emitChange()
|
|
|
|
let v6Candidate = workflow.snapshot.commandAddressPicker.candidates[1]
|
|
_ = workflow.selectCommandAddress(v6Candidate)
|
|
|
|
let commands = workflow.snapshot.terminalProxyEnvironmentCommands
|
|
XCTAssertTrue(
|
|
commands.zshBash.contains("export http_proxy='http://[2001:db8::1]:11089'"),
|
|
"IPv6 URL 使用方括号与已保存 HTTP 端口")
|
|
XCTAssertTrue(
|
|
commands.fish.contains("set -gx all_proxy 'socks5://[2001:db8::1]:11088'"))
|
|
XCTAssertFalse(
|
|
commands.zshBash.contains("autoconf"), "类型注记只进标签,不进命令")
|
|
}
|
|
|
|
func testSelectionSurvivesLeavingAndReenteringHome() {
|
|
setListenerMode(.allIPv4Interfaces)
|
|
interfaceFacts.interfaces = [
|
|
FakeLocalInterfaceFacts.wifi(addresses: [.ipv4("192.168.1.10")])
|
|
]
|
|
interfaceFacts.emitChange()
|
|
let wifi = workflow.snapshot.commandAddressPicker.candidates[1]
|
|
_ = workflow.selectCommandAddress(wifi)
|
|
|
|
_ = workflow.refreshTerminalCommands() // 离开再进入首页
|
|
|
|
XCTAssertEqual(
|
|
workflow.snapshot.commandAddressPicker.selected.identity, wifi.identity,
|
|
"同一会话内选择跨首页离开/返回保留")
|
|
}
|
|
|
|
func testNewSessionResetsSelectionToDefaultLoopback() {
|
|
setListenerMode(.allIPv4Interfaces)
|
|
interfaceFacts.interfaces = [
|
|
FakeLocalInterfaceFacts.wifi(addresses: [.ipv4("192.168.1.10")])
|
|
]
|
|
interfaceFacts.emitChange()
|
|
let wifi = workflow.snapshot.commandAddressPicker.candidates[1]
|
|
_ = workflow.selectCommandAddress(wifi)
|
|
|
|
let freshWorkflow = ProxyControlWorkflow(
|
|
runtime: runtime,
|
|
targetFacts: FakeTargetFacts(),
|
|
interfaceFacts: interfaceFacts)
|
|
|
|
XCTAssertEqual(
|
|
freshWorkflow.snapshot.commandAddressPicker.selected.address, "127.0.0.1",
|
|
"新会话恢复默认回环地址,不沿用先前选择")
|
|
}
|
|
|
|
func testDisappearedAddressFallsBackToLoopback() {
|
|
setListenerMode(.allIPv4Interfaces)
|
|
interfaceFacts.interfaces = [
|
|
FakeLocalInterfaceFacts.wifi(addresses: [.ipv4("192.168.1.10")])
|
|
]
|
|
interfaceFacts.emitChange()
|
|
let wifi = workflow.snapshot.commandAddressPicker.candidates[1]
|
|
_ = workflow.selectCommandAddress(wifi)
|
|
|
|
interfaceFacts.interfaces = []
|
|
interfaceFacts.emitChange()
|
|
|
|
XCTAssertEqual(
|
|
workflow.snapshot.commandAddressPicker.selected.address, "127.0.0.1",
|
|
"地址消失回退默认回环")
|
|
}
|
|
|
|
func testListenerModeSwitchToIncompatibleFamilyFallsBack() {
|
|
setListenerMode(.allIPv4AndIPv6Interfaces)
|
|
interfaceFacts.interfaces = [
|
|
FakeLocalInterfaceFacts.wifi(addresses: [.ipv6("2001:db8::1")])
|
|
]
|
|
interfaceFacts.emitChange()
|
|
let v6Candidate = workflow.snapshot.commandAddressPicker.candidates[1]
|
|
_ = workflow.selectCommandAddress(v6Candidate)
|
|
|
|
setListenerMode(.allIPv4Interfaces)
|
|
|
|
XCTAssertEqual(
|
|
workflow.snapshot.commandAddressPicker.selected.address, "127.0.0.1",
|
|
"监听方式不再支持该地址族时回退")
|
|
XCTAssertEqual(
|
|
workflow.snapshot.commandAddressPicker.candidates.map(\.address),
|
|
["127.0.0.1"],
|
|
"切换监听方式立即重新过滤候选")
|
|
}
|
|
|
|
func testEnumerationFailureKeepsLoopbackCandidatesAndCopy() {
|
|
setListenerMode(.allIPv4Interfaces)
|
|
interfaceFacts.interfaces = [
|
|
FakeLocalInterfaceFacts.wifi(addresses: [.ipv4("192.168.1.10")])
|
|
]
|
|
interfaceFacts.emitChange()
|
|
let wifi = workflow.snapshot.commandAddressPicker.candidates[1]
|
|
_ = workflow.selectCommandAddress(wifi)
|
|
|
|
interfaceFacts.interfaces = nil
|
|
interfaceFacts.emitChange()
|
|
|
|
let picker = workflow.snapshot.commandAddressPicker
|
|
XCTAssertEqual(
|
|
picker.candidates.map(\.address), ["127.0.0.1"], "枚举失败仅提供兼容回环候选")
|
|
XCTAssertEqual(picker.selected.address, "127.0.0.1")
|
|
XCTAssertTrue(
|
|
workflow.snapshot.terminalProxyEnvironmentCommands.zshBash.contains("127.0.0.1"),
|
|
"枚举失败仍可复制回环命令")
|
|
}
|
|
|
|
func testCopyPreparationRefreshesFallsBackAndMatchesProjection() {
|
|
runtime.listenFacts = RuntimeListenFacts(
|
|
listenerMode: .disabled, socksPort: 11086, httpPort: 11087)
|
|
runtime.listenFacts.lan.isEnabled = true
|
|
runtime.listenFacts.lan.mode = .allIPv4Interfaces
|
|
runtime.emitChange()
|
|
interfaceFacts.interfaces = [
|
|
FakeLocalInterfaceFacts.wifi(addresses: [.ipv4("192.168.1.10")])
|
|
]
|
|
interfaceFacts.emitChange()
|
|
let wifi = workflow.snapshot.commandAddressPicker.candidates[1]
|
|
_ = workflow.selectCommandAddress(wifi)
|
|
|
|
// 复制前地址消失且变化通知丢失:复制前刷新发现失效。
|
|
interfaceFacts.interfaces = []
|
|
let prepared = workflow.refreshTerminalCommands()
|
|
|
|
XCTAssertFalse(
|
|
prepared.zshBash.contains("192.168.1.10"), "复制内容不包含已消失的地址")
|
|
XCTAssertTrue(prepared.zshBash.contains("127.0.0.1"))
|
|
XCTAssertEqual(
|
|
prepared, workflow.snapshot.terminalProxyEnvironmentCommands,
|
|
"复制前刷新产生的变化同步至提示投影")
|
|
XCTAssertEqual(
|
|
workflow.snapshot.commandAddressPicker.selected.address, "127.0.0.1",
|
|
"回退结果更新界面选择")
|
|
}
|
|
|
|
func testProxyOffStillPreparesCommands() {
|
|
setListenerMode(.allIPv4Interfaces)
|
|
runtime.runtimeFacts = ProxyRuntimeFacts(status: .off, isOn: false)
|
|
runtime.emitChange()
|
|
|
|
let prepared = workflow.refreshTerminalCommands()
|
|
|
|
XCTAssertTrue(prepared.zshBash.contains("127.0.0.1"), "代理未运行时命令仍可准备")
|
|
XCTAssertEqual(runtime.resyncCount, 0, "刷新不触碰运行时生命周期")
|
|
}
|
|
}
|