diff --git a/ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/ProxyControlAdapters.swift b/ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/ProxyControlAdapters.swift index 68110f2e..e0b0781e 100644 --- a/ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/ProxyControlAdapters.swift +++ b/ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/ProxyControlAdapters.swift @@ -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 双导出行。 diff --git a/ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/ProxyControlWorkflow.swift b/ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/ProxyControlWorkflow.swift index 895013c5..11dc6987 100644 --- a/ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/ProxyControlWorkflow.swift +++ b/ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/ProxyControlWorkflow.swift @@ -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) diff --git a/ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+Mode.swift b/ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+Mode.swift index d2068325..e3fca5ae 100644 --- a/ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+Mode.swift +++ b/ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController+Mode.swift @@ -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) diff --git a/ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController.swift b/ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController.swift index dc5011fc..0403b5cf 100644 --- a/ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController.swift +++ b/ShadowsocksX-NG2/App/Application/ProxyRuntime/ProxyRuntimeController.swift @@ -123,7 +123,6 @@ final class ProxyRuntimeController { var proxyMode: ProxyMode { didSet { factChanges.markChanged() } } /// 规则模式子选项:从设置快照投影。 - var ruleDefaultAction: RuleDefaultAction { settings.ruleDefaultAction } var rulePreset: RulePreset { settings.rulePreset } init( diff --git a/ShadowsocksX-NG2/Domain/Settings/ProxyControlPreferences.swift b/ShadowsocksX-NG2/Domain/Settings/ProxyControlPreferences.swift index 42178d37..30ba48d2 100644 --- a/ShadowsocksX-NG2/Domain/Settings/ProxyControlPreferences.swift +++ b/ShadowsocksX-NG2/Domain/Settings/ProxyControlPreferences.swift @@ -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") } diff --git a/ShadowsocksX-NG2/Domain/Settings/ProxyRuntimeSettings.swift b/ShadowsocksX-NG2/Domain/Settings/ProxyRuntimeSettings.swift index d4b21acc..d2218a11 100644 --- a/ShadowsocksX-NG2/Domain/Settings/ProxyRuntimeSettings.swift +++ b/ShadowsocksX-NG2/Domain/Settings/ProxyRuntimeSettings.swift @@ -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 } diff --git a/ShadowsocksX-NG2/Tests/ProxyControl/ProxyControlWorkflowTests.swift b/ShadowsocksX-NG2/Tests/ProxyControl/ProxyControlWorkflowTests.swift index 37ebc31e..2b119484 100644 --- a/ShadowsocksX-NG2/Tests/ProxyControl/ProxyControlWorkflowTests.swift +++ b/ShadowsocksX-NG2/Tests/ProxyControl/ProxyControlWorkflowTests.swift @@ -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 { diff --git a/ShadowsocksX-NG2/Tests/ProxyRuntime/RuntimeConvergenceTests.swift b/ShadowsocksX-NG2/Tests/ProxyRuntime/RuntimeConvergenceTests.swift index 5a38b570..dbcce613 100644 --- a/ShadowsocksX-NG2/Tests/ProxyRuntime/RuntimeConvergenceTests.swift +++ b/ShadowsocksX-NG2/Tests/ProxyRuntime/RuntimeConvergenceTests.swift @@ -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) diff --git a/ShadowsocksX-NG2/Tests/Rules/Custom/CustomRuleControllerTests.swift b/ShadowsocksX-NG2/Tests/Rules/Custom/CustomRuleControllerTests.swift index bdb1537a..be14584a 100644 --- a/ShadowsocksX-NG2/Tests/Rules/Custom/CustomRuleControllerTests.swift +++ b/ShadowsocksX-NG2/Tests/Rules/Custom/CustomRuleControllerTests.swift @@ -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( diff --git a/ShadowsocksX-NG2/Tests/Rules/Custom/CustomRuleDeletionControllerTests.swift b/ShadowsocksX-NG2/Tests/Rules/Custom/CustomRuleDeletionControllerTests.swift index 6fe8b0d0..dcc9ffcb 100644 --- a/ShadowsocksX-NG2/Tests/Rules/Custom/CustomRuleDeletionControllerTests.swift +++ b/ShadowsocksX-NG2/Tests/Rules/Custom/CustomRuleDeletionControllerTests.swift @@ -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() diff --git a/ShadowsocksX-NG2/Tests/Rules/Custom/CustomRuleEditingControllerTests.swift b/ShadowsocksX-NG2/Tests/Rules/Custom/CustomRuleEditingControllerTests.swift index 2013d22b..e4063f33 100644 --- a/ShadowsocksX-NG2/Tests/Rules/Custom/CustomRuleEditingControllerTests.swift +++ b/ShadowsocksX-NG2/Tests/Rules/Custom/CustomRuleEditingControllerTests.swift @@ -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() diff --git a/ShadowsocksX-NG2/Tests/Rules/RuleEnablementControllerTests.swift b/ShadowsocksX-NG2/Tests/Rules/RuleEnablementControllerTests.swift index 275cccf6..6223bf9c 100644 --- a/ShadowsocksX-NG2/Tests/Rules/RuleEnablementControllerTests.swift +++ b/ShadowsocksX-NG2/Tests/Rules/RuleEnablementControllerTests.swift @@ -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) diff --git a/ShadowsocksX-NG2/Tests/Rules/RulePresetControllerTests.swift b/ShadowsocksX-NG2/Tests/Rules/RulePresetControllerTests.swift index 81141217..a2a95b81 100644 --- a/ShadowsocksX-NG2/Tests/Rules/RulePresetControllerTests.swift +++ b/ShadowsocksX-NG2/Tests/Rules/RulePresetControllerTests.swift @@ -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) diff --git a/ShadowsocksX-NG2/Tests/Rules/RulePresetWorkflowTests.swift b/ShadowsocksX-NG2/Tests/Rules/RulePresetWorkflowTests.swift index fcf80a65..0d4a27b4 100644 --- a/ShadowsocksX-NG2/Tests/Rules/RulePresetWorkflowTests.swift +++ b/ShadowsocksX-NG2/Tests/Rules/RulePresetWorkflowTests.swift @@ -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) diff --git a/ShadowsocksX-NG2/Tests/Settings/ProxyControlPreferencesTests.swift b/ShadowsocksX-NG2/Tests/Settings/ProxyControlPreferencesTests.swift index e84390b6..cdd7e1e4 100644 --- a/ShadowsocksX-NG2/Tests/Settings/ProxyControlPreferencesTests.swift +++ b/ShadowsocksX-NG2/Tests/Settings/ProxyControlPreferencesTests.swift @@ -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) + } + } + }