mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-10-31 06:33:44 +00:00
refactor(rules): require explicit preset selection
Remove lossy default-action writes that can replace china-proxy with gfwlist. Preserve legacy preference reads and derive the default action from the selected preset. Update tests to use explicit presets and verify identity, legacy compatibility, and no-op reselection.
This commit is contained in:
@@ -31,7 +31,6 @@ final class ControllerProxyRuntimeAdapter: ProxyRuntimeAdapting {
|
||||
|
||||
var proxyMode: ProxyMode { controller.proxyMode }
|
||||
|
||||
var ruleDefaultAction: RuleDefaultAction { controller.ruleDefaultAction }
|
||||
var rulePreset: RulePreset { controller.rulePreset }
|
||||
|
||||
var skippedInvalidServerCount: Int { controller.skippedServers.count }
|
||||
@@ -96,10 +95,6 @@ final class ControllerProxyRuntimeAdapter: ProxyRuntimeAdapting {
|
||||
func setRulePreset(_ preset: RulePreset) async {
|
||||
await controller.setRulePreset(preset)
|
||||
}
|
||||
|
||||
func setRuleDefaultAction(_ action: RuleDefaultAction) async {
|
||||
await controller.setRuleDefaultAction(action)
|
||||
}
|
||||
}
|
||||
|
||||
/// HTTP 导出能力的唯一派生点:监听设置 → 可复制的 http/https 双导出行。
|
||||
|
||||
@@ -73,7 +73,7 @@ struct ProxyControlSnapshot: Equatable, Sendable {
|
||||
var systemProxyInspection = SystemProxyInspectionFacts()
|
||||
/// 当前已应用的代理模式(持久化成功的模式)。
|
||||
let proxyMode: ProxyMode
|
||||
/// 规则模式子选项:未匹配默认动作;仅规则模式呈现。
|
||||
/// 规则模式内选择的规则预设;仅规则模式呈现。
|
||||
var rulePreset: RulePreset = .gfwlist
|
||||
var ruleDefaultAction: RuleDefaultAction { rulePreset.defaultAction }
|
||||
/// 可用模式由 Domain 单点策略投影;UI 不自行判断可用性。
|
||||
@@ -119,8 +119,7 @@ protocol ProxyRuntimeAdapting: AnyObject {
|
||||
var systemProxyInspection: SystemProxyInspectionFacts { get }
|
||||
/// 当前已应用的代理模式。
|
||||
var proxyMode: ProxyMode { get }
|
||||
/// 规则模式子选项。
|
||||
var ruleDefaultAction: RuleDefaultAction { get }
|
||||
/// 规则模式内选择的规则预设。
|
||||
var rulePreset: RulePreset { get }
|
||||
/// 最近一次激活预检或目录收敛跳过的无效服务器数量。
|
||||
var skippedInvalidServerCount: Int { get }
|
||||
@@ -149,7 +148,6 @@ protocol ProxyRuntimeAdapting: AnyObject {
|
||||
func recheckSystemProxy() async
|
||||
func setProxyMode(_ mode: ProxyMode) async
|
||||
/// 规则模式子选项命令。
|
||||
func setRuleDefaultAction(_ action: RuleDefaultAction) async
|
||||
func setRulePreset(_ preset: RulePreset) async
|
||||
}
|
||||
|
||||
@@ -259,13 +257,7 @@ final class ProxyControlWorkflow: ObservableObject {
|
||||
return republish()
|
||||
}
|
||||
|
||||
/// 切换规则模式子选项:完成后整体重发布。
|
||||
@discardableResult
|
||||
func setRuleDefaultAction(_ action: RuleDefaultAction) async -> ProxyControlSnapshot {
|
||||
await runtime.setRuleDefaultAction(action)
|
||||
return republish()
|
||||
}
|
||||
|
||||
/// 切换规则预设:完成后整体重发布。
|
||||
@discardableResult
|
||||
func setRulePreset(_ preset: RulePreset) async -> ProxyControlSnapshot {
|
||||
await runtime.setRulePreset(preset)
|
||||
|
||||
@@ -9,12 +9,7 @@ extension ProxyRuntimeController {
|
||||
await transitionMode(mode, preset: settings.rulePreset)
|
||||
}
|
||||
|
||||
/// 规则模式子选项:切换「未匹配时代理/直连」并重部署 ACL。
|
||||
/// 失败保留最新模式与子选项,发布后失败停止服务并暂停系统代理。
|
||||
func setRuleDefaultAction(_ action: RuleDefaultAction) async {
|
||||
await setRulePreset(RulePreset(legacy: action))
|
||||
}
|
||||
|
||||
/// 切换规则预设;失败保留最新意图,发布后失败停止服务并暂停系统代理。
|
||||
func setRulePreset(_ preset: RulePreset) async {
|
||||
guard preset != settings.rulePreset else { return }
|
||||
await transitionMode(proxyMode, preset: preset)
|
||||
|
||||
@@ -123,7 +123,6 @@ final class ProxyRuntimeController {
|
||||
var proxyMode: ProxyMode { didSet { factChanges.markChanged() } }
|
||||
|
||||
/// 规则模式子选项:从设置快照投影。
|
||||
var ruleDefaultAction: RuleDefaultAction { settings.ruleDefaultAction }
|
||||
var rulePreset: RulePreset { settings.rulePreset }
|
||||
|
||||
init(
|
||||
|
||||
@@ -29,11 +29,6 @@ struct ProxyControlPreferences {
|
||||
nonmutating set { defaults.set(newValue.rawValue, forKey: "proxyControl.rulePreset") }
|
||||
}
|
||||
|
||||
var ruleDefaultAction: RuleDefaultAction {
|
||||
get { rulePreset.defaultAction }
|
||||
nonmutating set { rulePreset = RulePreset(legacy: newValue) }
|
||||
}
|
||||
|
||||
var agentEnabled: Bool {
|
||||
get { defaults.object(forKey: "proxyControl.agentEnabled") as? Bool ?? false }
|
||||
nonmutating set { defaults.set(newValue, forKey: "proxyControl.agentEnabled") }
|
||||
|
||||
@@ -14,8 +14,7 @@ struct ProxyRuntimeSettings: Equatable, Sendable {
|
||||
/// 规则模式预设,首次启动默认为仅 GFW 列表内目标代理。
|
||||
var rulePreset: RulePreset
|
||||
var ruleDefaultAction: RuleDefaultAction {
|
||||
get { rulePreset.defaultAction }
|
||||
set { rulePreset = RulePreset(legacy: newValue) }
|
||||
rulePreset.defaultAction
|
||||
}
|
||||
/// 用户保存的代理服务意图,默认关闭,与当前运行健康状态独立。
|
||||
var agentEnabled: Bool
|
||||
@@ -27,15 +26,14 @@ struct ProxyRuntimeSettings: Equatable, Sendable {
|
||||
listen: ProxyListeningConfiguration = ProxyListeningConfiguration(),
|
||||
proxyExceptions: String = ProxyRuntimeSettings.defaultProxyExceptions,
|
||||
preferredMode: ProxyModeKind = .rule,
|
||||
ruleDefaultAction: RuleDefaultAction = .directWhenUnmatched,
|
||||
rulePreset: RulePreset? = nil,
|
||||
rulePreset: RulePreset = .gfwlist,
|
||||
agentEnabled: Bool = false,
|
||||
systemProxyEnabled: Bool = false
|
||||
) {
|
||||
self.listen = listen
|
||||
self.proxyExceptions = proxyExceptions
|
||||
self.preferredMode = preferredMode
|
||||
self.rulePreset = rulePreset ?? RulePreset(legacy: ruleDefaultAction)
|
||||
self.rulePreset = rulePreset
|
||||
self.agentEnabled = agentEnabled
|
||||
self.systemProxyEnabled = systemProxyEnabled
|
||||
}
|
||||
|
||||
@@ -19,10 +19,6 @@ final class FakeProxyRuntime: ProxyRuntimeAdapting {
|
||||
var systemProxyInspection = SystemProxyInspectionFacts()
|
||||
var proxyMode: ProxyMode
|
||||
var rulePreset: RulePreset = .chinaDirect
|
||||
var ruleDefaultAction: RuleDefaultAction {
|
||||
get { rulePreset.defaultAction }
|
||||
set { rulePreset = RulePreset(legacy: newValue) }
|
||||
}
|
||||
var skippedInvalidServerCount = 0
|
||||
var activeTargetID: NodeID?
|
||||
var httpExportCapability = HTTPExportCapability(
|
||||
@@ -39,7 +35,6 @@ final class FakeProxyRuntime: ProxyRuntimeAdapting {
|
||||
private(set) var approvalCommandCount = 0
|
||||
private(set) var modeCommands: [ProxyMode] = []
|
||||
private(set) var rulePresetCommands: [RulePreset] = []
|
||||
private(set) var ruleDefaultActionCommands: [RuleDefaultAction] = []
|
||||
|
||||
init(
|
||||
facts: ProxyRuntimeFacts = ProxyRuntimeFacts(status: .off, isOn: false),
|
||||
@@ -74,10 +69,6 @@ final class FakeProxyRuntime: ProxyRuntimeAdapting {
|
||||
func setRulePreset(_ preset: RulePreset) async {
|
||||
rulePresetCommands.append(preset)
|
||||
}
|
||||
|
||||
func setRuleDefaultAction(_ action: RuleDefaultAction) async {
|
||||
ruleDefaultActionCommands.append(action)
|
||||
}
|
||||
}
|
||||
|
||||
/// 可编程目标事实替身:路径摘要可编程、查询全记录。
|
||||
@@ -260,27 +251,20 @@ final class ProxyControlWorkflowTests: XCTestCase {
|
||||
XCTAssertEqual(after.runtime, ProxyRuntimeFacts(status: .running, isOn: true))
|
||||
}
|
||||
|
||||
func testRuleDefaultActionCommandForwardsAndRepublishes() async {
|
||||
runtime.ruleDefaultAction = .proxyWhenUnmatched
|
||||
_ = await workflow.setRuleDefaultAction(.directWhenUnmatched)
|
||||
XCTAssertEqual(runtime.ruleDefaultActionCommands, [.directWhenUnmatched])
|
||||
// 命令完成后 workflow 从 runtime 重读事实。
|
||||
XCTAssertEqual(workflow.snapshot.ruleDefaultAction, .proxyWhenUnmatched)
|
||||
runtime.ruleDefaultAction = .directWhenUnmatched
|
||||
runtime.emitChange()
|
||||
XCTAssertEqual(workflow.snapshot.ruleDefaultAction, .directWhenUnmatched)
|
||||
XCTAssertEqual(workflow.snapshot.proxyMode, runtime.proxyMode)
|
||||
}
|
||||
|
||||
func testExplicitChinaProxyPresetCommandPreservesItsIdentityInPublishedFacts() async {
|
||||
_ = await workflow.setRulePreset(.chinaProxy)
|
||||
XCTAssertEqual(runtime.rulePresetCommands, [.chinaProxy])
|
||||
XCTAssertEqual(workflow.snapshot.rulePreset, .chinaDirect)
|
||||
runtime.rulePreset = .chinaProxy
|
||||
runtime.emitChange()
|
||||
XCTAssertEqual(workflow.snapshot.rulePreset, .chinaProxy)
|
||||
XCTAssertEqual(workflow.snapshot.ruleDefaultAction, .directWhenUnmatched)
|
||||
XCTAssertEqual(workflow.snapshot.proxyMode, runtime.proxyMode)
|
||||
func testRulePresetCommandsForwardAndRepublishWithoutLosingIdentity() async {
|
||||
for preset in RulePreset.allCases {
|
||||
let before = runtime.rulePreset
|
||||
let after = await workflow.setRulePreset(preset)
|
||||
XCTAssertEqual(runtime.rulePresetCommands.last, preset)
|
||||
// 命令完成后从 runtime 重读事实,不提前发布请求值。
|
||||
XCTAssertEqual(after.rulePreset, before)
|
||||
runtime.rulePreset = preset
|
||||
runtime.emitChange()
|
||||
XCTAssertEqual(workflow.snapshot.rulePreset, preset)
|
||||
XCTAssertEqual(workflow.snapshot.ruleDefaultAction, preset.defaultAction)
|
||||
XCTAssertEqual(workflow.snapshot.proxyMode, runtime.proxyMode)
|
||||
}
|
||||
XCTAssertEqual(runtime.rulePresetCommands, RulePreset.allCases)
|
||||
}
|
||||
|
||||
func testResyncOnLaunchForwardsAndRepublishes() async {
|
||||
|
||||
@@ -115,9 +115,9 @@ struct RuntimeConvergenceTests {
|
||||
#expect(fixture.agent.registerCount > count)
|
||||
}
|
||||
|
||||
@Test(arguments: [false, true])
|
||||
@Test(arguments: [false, true], RulePreset.allCases)
|
||||
func unchangedModeACLDoesNotReprobeHealthyListeners(
|
||||
systemProxyEnabled: Bool
|
||||
systemProxyEnabled: Bool, preset: RulePreset
|
||||
) async throws {
|
||||
let fixture = try Fixture(systemProxyEnabled: systemProxyEnabled)
|
||||
defer { fixture.cleanUp() }
|
||||
@@ -129,10 +129,16 @@ struct RuntimeConvergenceTests {
|
||||
let probes = fixture.probe.ports.count
|
||||
let signals = fixture.signals.signalsSent.filter { $0.signal != 0 }.count
|
||||
|
||||
// 全局模式仍保存规则默认动作,但它不改变全局 ACL。
|
||||
await fixture.controller.setRuleDefaultAction(.directWhenUnmatched)
|
||||
// 全局模式仍保存规则预设,但它不改变全局 ACL。
|
||||
await fixture.controller.setRulePreset(preset)
|
||||
let generation = fixture.controller.modeChangeGeneration
|
||||
await fixture.controller.setRulePreset(preset)
|
||||
|
||||
#expect(fixture.controller.controlPreferences.ruleDefaultAction == .directWhenUnmatched)
|
||||
#expect(fixture.controller.rulePreset == preset)
|
||||
#expect(fixture.controller.modeChangeGeneration == generation)
|
||||
if preset != .gfwlist {
|
||||
#expect(fixture.controller.controlPreferences.rulePreset == preset)
|
||||
}
|
||||
#expect(fixture.controller.proxyMode == .global)
|
||||
#expect(fixture.controller.state == .running)
|
||||
#expect(try Data(contentsOf: fixture.runtimeStore.fileURL) == bytes)
|
||||
|
||||
@@ -62,7 +62,7 @@ final class CustomRuleControllerTests: ProxyRuntimeControllerTests {
|
||||
let settings = ProxyRuntimeSettings(
|
||||
listen: ActivationFixture.listen,
|
||||
preferredMode: .rule,
|
||||
ruleDefaultAction: .proxyWhenUnmatched,
|
||||
rulePreset: .chinaDirect,
|
||||
agentEnabled: true)
|
||||
let controller = makeControllerWithCustomRules(
|
||||
store: store, settings: settings, proxyMode: .rule)
|
||||
@@ -92,7 +92,7 @@ final class CustomRuleControllerTests: ProxyRuntimeControllerTests {
|
||||
let (store, _) = try makeCustomRuleStore(preset: .gfwlist)
|
||||
let settings = ProxyRuntimeSettings(
|
||||
listen: ActivationFixture.listen, preferredMode: .rule,
|
||||
ruleDefaultAction: .directWhenUnmatched, agentEnabled: true)
|
||||
rulePreset: .gfwlist, agentEnabled: true)
|
||||
let controller = makeControllerWithCustomRules(
|
||||
store: store, settings: settings, proxyMode: .rule)
|
||||
try await controller.activate(seeded.server)
|
||||
@@ -130,7 +130,7 @@ final class CustomRuleControllerTests: ProxyRuntimeControllerTests {
|
||||
let settings = ProxyRuntimeSettings(
|
||||
listen: ActivationFixture.listen,
|
||||
preferredMode: .rule,
|
||||
ruleDefaultAction: .proxyWhenUnmatched,
|
||||
rulePreset: .chinaDirect,
|
||||
agentEnabled: true)
|
||||
let controller = makeControllerWithCustomRules(
|
||||
store: store, settings: settings, proxyMode: .rule)
|
||||
@@ -160,7 +160,7 @@ final class CustomRuleControllerTests: ProxyRuntimeControllerTests {
|
||||
let settings = ProxyRuntimeSettings(
|
||||
listen: ActivationFixture.listen,
|
||||
preferredMode: .rule,
|
||||
ruleDefaultAction: .proxyWhenUnmatched,
|
||||
rulePreset: .chinaDirect,
|
||||
agentEnabled: true,
|
||||
systemProxyEnabled: true)
|
||||
let controller = makeControllerWithCustomRules(
|
||||
|
||||
@@ -62,7 +62,7 @@ final class CustomRuleDeletionControllerTests: ProxyRuntimeControllerTests {
|
||||
store: store,
|
||||
settings: ProxyRuntimeSettings(
|
||||
listen: ActivationFixture.listen, preferredMode: .rule,
|
||||
ruleDefaultAction: .proxyWhenUnmatched, agentEnabled: true), proxyMode: .rule)
|
||||
rulePreset: .chinaDirect, agentEnabled: true), proxyMode: .rule)
|
||||
try await controller.activate(seeded.server)
|
||||
let workflow = deletionWorkflow(controller)
|
||||
await workflow.refresh()
|
||||
@@ -95,7 +95,7 @@ final class CustomRuleDeletionControllerTests: ProxyRuntimeControllerTests {
|
||||
store: store,
|
||||
settings: ProxyRuntimeSettings(
|
||||
listen: ActivationFixture.listen, preferredMode: mode.kind,
|
||||
ruleDefaultAction: .proxyWhenUnmatched, agentEnabled: true), proxyMode: mode)
|
||||
rulePreset: .chinaDirect, agentEnabled: true), proxyMode: mode)
|
||||
try await controller.activate(seeded.server)
|
||||
let workflow = deletionWorkflow(controller)
|
||||
await workflow.refresh()
|
||||
@@ -121,7 +121,7 @@ final class CustomRuleDeletionControllerTests: ProxyRuntimeControllerTests {
|
||||
store: store,
|
||||
settings: ProxyRuntimeSettings(
|
||||
listen: ActivationFixture.listen, preferredMode: .rule,
|
||||
ruleDefaultAction: .proxyWhenUnmatched, agentEnabled: true), proxyMode: .rule)
|
||||
rulePreset: .chinaDirect, agentEnabled: true), proxyMode: .rule)
|
||||
try await controller.activate(seeded.server)
|
||||
let workflow = deletionWorkflow(controller)
|
||||
await workflow.refresh()
|
||||
|
||||
@@ -35,7 +35,7 @@ final class CustomRuleEditingControllerTests: ProxyRuntimeControllerTests {
|
||||
store: store,
|
||||
settings: ProxyRuntimeSettings(
|
||||
listen: ActivationFixture.listen, preferredMode: .rule,
|
||||
ruleDefaultAction: .proxyWhenUnmatched, agentEnabled: true), proxyMode: .rule)
|
||||
rulePreset: .chinaDirect, agentEnabled: true), proxyMode: .rule)
|
||||
try await controller.activate(seeded.server)
|
||||
let workflow = editingWorkflow(controller)
|
||||
await workflow.refresh()
|
||||
@@ -71,7 +71,7 @@ final class CustomRuleEditingControllerTests: ProxyRuntimeControllerTests {
|
||||
store: store,
|
||||
settings: ProxyRuntimeSettings(
|
||||
listen: ActivationFixture.listen, preferredMode: .rule,
|
||||
ruleDefaultAction: .proxyWhenUnmatched, agentEnabled: true), proxyMode: .rule)
|
||||
rulePreset: .chinaDirect, agentEnabled: true), proxyMode: .rule)
|
||||
try await controller.activate(seeded.server)
|
||||
let workflow = editingWorkflow(controller)
|
||||
await workflow.refresh()
|
||||
|
||||
@@ -272,7 +272,7 @@ extension RuleEnablementControllerTests {
|
||||
try store.save([rule])
|
||||
let settings = ProxyRuntimeSettings(
|
||||
listen: ActivationFixture.listen, preferredMode: .rule,
|
||||
ruleDefaultAction: .proxyWhenUnmatched, agentEnabled: true)
|
||||
rulePreset: .chinaDirect, agentEnabled: true)
|
||||
let deploymentWaiting = expectation(description: "deployment health retry paused")
|
||||
let deploymentGate = RuleRuntimeDelayGate(started: deploymentWaiting)
|
||||
defer { deploymentGate.resume() }
|
||||
@@ -338,7 +338,7 @@ extension RuleEnablementControllerTests {
|
||||
try store.save([rule])
|
||||
let settings = ProxyRuntimeSettings(
|
||||
listen: ActivationFixture.listen, preferredMode: .rule,
|
||||
ruleDefaultAction: .proxyWhenUnmatched, agentEnabled: true)
|
||||
rulePreset: .chinaDirect, agentEnabled: true)
|
||||
let controller = makeRuleEnablementController(
|
||||
store: store, settings: settings, proxyMode: .rule)
|
||||
try await controller.activate(seeded.server)
|
||||
|
||||
@@ -8,7 +8,8 @@ extension RuleEnablementControllerTests {
|
||||
let (store, _) = try makeCustomRuleStore()
|
||||
let controller = makeControllerWithCustomRules(
|
||||
store: store,
|
||||
settings: ProxyRuntimeSettings(listen: ActivationFixture.listen, agentEnabled: true),
|
||||
settings: ProxyRuntimeSettings(
|
||||
listen: ActivationFixture.listen, rulePreset: .chinaDirect, agentEnabled: true),
|
||||
proxyMode: .rule, ruleApplicationDelay: { await Task.yield() })
|
||||
try await controller.activate(seeded.server)
|
||||
let runtimeStore = RuntimeFileStore(fileURL: runtime.contract)
|
||||
@@ -29,6 +30,11 @@ extension RuleEnablementControllerTests {
|
||||
XCTAssertTrue(china.contains("preset-only.example"))
|
||||
XCTAssertTrue(china.contains("[proxy_list]"))
|
||||
XCTAssertEqual(controller.controlPreferences.rulePreset, .chinaProxy)
|
||||
let generation = controller.modeChangeGeneration
|
||||
await controller.setRulePreset(.chinaProxy)
|
||||
XCTAssertEqual(controller.rulePreset, .chinaProxy)
|
||||
XCTAssertEqual(controller.modeChangeGeneration, generation)
|
||||
XCTAssertEqual(try activeACLContent(runtimeStore), china)
|
||||
await controller.setRulePreset(.chinaDirect)
|
||||
XCTAssertEqual(try activeACLContent(runtimeStore), before)
|
||||
await controller.setRulePreset(.gfwlist)
|
||||
|
||||
@@ -117,6 +117,7 @@ extension RulePresetWorkflowTests {
|
||||
return RuleDocumentCommit(outcome: .saved, document: document)
|
||||
}, builtinSnapshots: BuiltinRuleSnapshots(loader: { rulesFixture($0) }))
|
||||
}
|
||||
let originalPreset = workspaces.selectedPreset
|
||||
let original = workspaces.current
|
||||
await original.refresh()
|
||||
var draft = try #require(original.makeCustomRuleDraft())
|
||||
@@ -136,7 +137,7 @@ extension RulePresetWorkflowTests {
|
||||
#expect(await save.value == .committed(.saved))
|
||||
await original.analysisTask?.value
|
||||
#expect(other.snapshot == untouched)
|
||||
workspaces.selectedPreset = .chinaDirect
|
||||
workspaces.selectedPreset = originalPreset
|
||||
#expect(workspaces.current.snapshot.rows.first?.content == "delayed.example")
|
||||
#expect(workspaces.current.snapshot.rows.first?.action == .direct)
|
||||
#expect(workspaces.current.snapshot.commitOutcome == .saved)
|
||||
|
||||
@@ -16,6 +16,7 @@ struct ProxyControlPreferencesTests {
|
||||
preferences: ProxyControlPreferences(defaults: defaults))
|
||||
|
||||
#expect(settings.preferredMode == .rule)
|
||||
#expect(settings.rulePreset == .gfwlist)
|
||||
#expect(settings.ruleDefaultAction == .directWhenUnmatched)
|
||||
#expect(!settings.agentEnabled)
|
||||
#expect(!settings.systemProxyEnabled)
|
||||
@@ -32,7 +33,7 @@ struct ProxyControlPreferencesTests {
|
||||
defer { defaults.removePersistentDomain(forName: suite) }
|
||||
let preferences = ProxyControlPreferences(defaults: defaults)
|
||||
preferences.preferredMode = .direct
|
||||
preferences.ruleDefaultAction = .directWhenUnmatched
|
||||
preferences.rulePreset = .chinaProxy
|
||||
preferences.agentEnabled = true
|
||||
preferences.systemProxyEnabled = true
|
||||
preferences.proxyExceptions = "example.com, localhost"
|
||||
@@ -44,6 +45,7 @@ struct ProxyControlPreferencesTests {
|
||||
let settings = ProxyRuntimeSettings(listen: listen, preferences: reopened)
|
||||
|
||||
#expect(settings.preferredMode == .direct)
|
||||
#expect(settings.rulePreset == .chinaProxy)
|
||||
#expect(settings.ruleDefaultAction == .directWhenUnmatched)
|
||||
#expect(settings.agentEnabled)
|
||||
#expect(settings.systemProxyEnabled)
|
||||
@@ -51,4 +53,27 @@ struct ProxyControlPreferencesTests {
|
||||
#expect(settings.listen.socksPort == 23001)
|
||||
#expect(settings.listen.httpPort == 23002)
|
||||
}
|
||||
|
||||
@Test(arguments: RuleDefaultAction.allCases)
|
||||
func legacyDefaultActionIsReadUntilAnExplicitPresetIsSaved(_ action: RuleDefaultAction) throws {
|
||||
let suite = "proxy-control-legacy-\(UUID().uuidString)"
|
||||
let defaults = try #require(UserDefaults(suiteName: suite))
|
||||
defer { defaults.removePersistentDomain(forName: suite) }
|
||||
defaults.set(action.rawValue, forKey: "proxyControl.ruleDefaultAction")
|
||||
let preferences = ProxyControlPreferences(defaults: defaults)
|
||||
let expected: RulePreset = action == .proxyWhenUnmatched ? .chinaDirect : .gfwlist
|
||||
#expect(preferences.rulePreset == expected)
|
||||
#expect(defaults.object(forKey: "proxyControl.rulePreset") == nil)
|
||||
|
||||
for preset in RulePreset.allCases {
|
||||
preferences.rulePreset = preset
|
||||
let reopened = ProxyControlPreferences(defaults: defaults)
|
||||
let settings = ProxyRuntimeSettings(
|
||||
listen: ProxyListeningConfiguration(), preferences: reopened)
|
||||
#expect(settings.rulePreset == preset)
|
||||
#expect(settings.ruleDefaultAction == preset.defaultAction)
|
||||
#expect(defaults.string(forKey: "proxyControl.ruleDefaultAction") == action.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user