mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-08 11:14:51 +00:00
132 lines
6.5 KiB
Swift
132 lines
6.5 KiB
Swift
import Foundation
|
|
import Testing
|
|
|
|
@testable import ShadowsocksX_NG2
|
|
|
|
/// Server-form behavior through workflow and draft interfaces.
|
|
struct PluginFormCatalogTests {
|
|
@Test(arguments: [
|
|
PluginCatalogSnapshot.Availability.available, .missing, .notExecutable, .unreadable,
|
|
])
|
|
@MainActor func formRetainsAvailabilityForNewlySelectedPlugin(
|
|
availability: PluginCatalogSnapshot.Availability
|
|
) throws {
|
|
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
|
defer { try? FileManager.default.removeItem(at: root) }
|
|
let snapshot = PluginCatalogSnapshot(entries: [
|
|
.init(
|
|
program: "tool", path: "/private/tool", source: .user,
|
|
availability: availability, managedInfo: nil)
|
|
])
|
|
let workflow = makeCatalogWorkflow(
|
|
coordinator: CatalogCommitCoordinator(
|
|
fileStore: makeTestCatalogStore(fileURL: root.appendingPathComponent("catalog.json")),
|
|
runtime: FakeCatalogRuntime()), plugins: snapshot)
|
|
let form = workflow.newFormPluginSection(selection: .named(program: "tool"))
|
|
#expect(form.programs.first?.source == .user)
|
|
#expect(form.programs.first?.availability == availability)
|
|
#expect(!form.mappingsUnreadable)
|
|
}
|
|
|
|
@Test @MainActor func missingUnsavedChoiceHasUnresolvedPresentation() {
|
|
let plugin = PluginSectionState(
|
|
selection: .named(program: "saved"),
|
|
programs: [.init(program: "saved", source: .managed, availability: .available)],
|
|
mappingsUnreadable: false, optionsPresent: true, options: "flag")
|
|
let form = ServerEditForm(
|
|
address: "127.0.0.1", port: 8388,
|
|
encryptionMethod: "aes-256-gcm", password: "pw", remark: "Original",
|
|
plugin: plugin, isEditable: true)
|
|
let fields = ServerFormFields()
|
|
fields.showServer(.fresh()) { _ in form }
|
|
fields.pluginChoice = .named(program: "removed")
|
|
fields.pluginOptions.load("host=unsaved")
|
|
fields.updatePresentation(ServerFormPresentation(isEditable: true, plugin: plugin))
|
|
#expect(fields.pluginChoice == .named(program: "removed"))
|
|
#expect(fields.presentation?.plugin.unresolvedProgram(for: fields.pluginChoice) == "removed")
|
|
#expect(fields.pluginOptions.composedString == "host=unsaved")
|
|
#expect(plugin.unresolvedProgram(for: .none) == nil)
|
|
#expect(plugin.unresolvedProgram(for: .named(program: "saved")) == nil)
|
|
}
|
|
|
|
@Test @MainActor func mappingRemovalAndOverrideChangesPreserveServerDraft() async throws {
|
|
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
|
defer { try? FileManager.default.removeItem(at: root) }
|
|
let fixture = try makePluginFormFixture(root: root)
|
|
let id = try await fixture.workflow.createServer(
|
|
ServerEditDraft(
|
|
address: "127.0.0.1", port: 8388, encryptionMethod: "aes-256-gcm",
|
|
password: "pw", remark: "Original", plugin: .named(program: "tool"), pluginOptions: "flag"),
|
|
into: nil)
|
|
let fields = ServerFormFields()
|
|
fields.showServer(id, load: fixture.workflow.serverEditForm)
|
|
fields.remark = "Unsaved"
|
|
fields.pluginOptions.load("host=unsaved")
|
|
try fixture.plugins.removePlugin(program: "tool")
|
|
fields.updatePresentation(
|
|
fixture.workflow.serverFormPresentation(for: id), load: fixture.workflow.serverEditForm)
|
|
#expect(fields.pluginChoice == .unknown(program: "tool"))
|
|
#expect(fields.pluginOptions.composedString == "host=unsaved")
|
|
try fixture.plugins.addPlugin(program: "tool", path: fixture.managedBinary.path)
|
|
fields.updatePresentation(
|
|
fixture.workflow.serverFormPresentation(for: id), load: fixture.workflow.serverEditForm)
|
|
#expect(fields.pluginChoice == .named(program: "tool"))
|
|
#expect(fields.pluginOptions.composedString == "host=unsaved")
|
|
#expect(try fixture.workflow.serverEditForm(for: id)?.plugin.options == "flag")
|
|
#expect(fields.remark == "Unsaved")
|
|
|
|
// An unsaved choice disappearing must retain a selectable unresolved reference.
|
|
fields.pluginChoice = .named(program: "tool")
|
|
try fixture.plugins.removePlugin(program: "tool")
|
|
fields.updatePresentation(
|
|
fixture.workflow.serverFormPresentation(for: id), load: fixture.workflow.serverEditForm)
|
|
#expect(fields.presentation?.plugin.unresolvedProgram(for: .named(program: "tool")) == "tool")
|
|
#expect(
|
|
fixture.workflow.newFormPluginSection(selection: .named(program: "tool"))
|
|
.unresolvedProgram(for: .named(program: "tool")) == "tool")
|
|
#expect(fields.pluginOptions.composedString == "host=unsaved")
|
|
|
|
fields.pluginChoice = .named(program: "v2ray-plugin")
|
|
try fixture.plugins.addPlugin(program: "v2ray-plugin", path: fixture.managedBinary.path)
|
|
fields.updatePresentation(
|
|
fixture.workflow.serverFormPresentation(for: id), load: fixture.workflow.serverEditForm)
|
|
#expect(
|
|
fields.presentation?.plugin.programs.first { $0.program == "v2ray-plugin" }?.source == .user)
|
|
try fixture.plugins.removePlugin(program: "v2ray-plugin")
|
|
fields.updatePresentation(
|
|
fixture.workflow.serverFormPresentation(for: id), load: fixture.workflow.serverEditForm)
|
|
#expect(
|
|
fields.presentation?.plugin.programs.first { $0.program == "v2ray-plugin" }?.source
|
|
== .managed)
|
|
#expect(fields.pluginChoice == .named(program: "v2ray-plugin"))
|
|
#expect(fields.pluginOptions.composedString == "host=unsaved")
|
|
#expect(fields.remark == "Unsaved")
|
|
}
|
|
|
|
}
|
|
|
|
private struct PluginFormFixture {
|
|
let managedBinary: URL
|
|
let plugins: PluginCatalog
|
|
let workflow: CatalogWorkflow
|
|
}
|
|
|
|
@MainActor
|
|
private func makePluginFormFixture(root: URL) throws -> PluginFormFixture {
|
|
let managedBinary = root.appendingPathComponent("Contents/Helpers/Plugins/v2ray-plugin")
|
|
try FileManager.default.createDirectory(
|
|
at: managedBinary.deletingLastPathComponent(), withIntermediateDirectories: true)
|
|
try Data("#!/bin/sh\n".utf8).write(to: managedBinary)
|
|
try FileManager.default.setAttributes(
|
|
[.posixPermissions: 0o755], ofItemAtPath: managedBinary.path)
|
|
let plugins = PluginCatalog(
|
|
store: PluginMappingFileStore(fileURL: root.appendingPathComponent("plugins.json")),
|
|
managed: BundleManagedPluginProvider(bundleURL: root), inspector: QuietPluginInspector())
|
|
try plugins.addPlugin(program: "tool", path: managedBinary.path)
|
|
let workflow = makeCatalogWorkflow(
|
|
coordinator: CatalogCommitCoordinator(
|
|
fileStore: makeTestCatalogStore(fileURL: root.appendingPathComponent("catalog.json")),
|
|
runtime: FakeCatalogRuntime()), plugins: plugins)
|
|
return PluginFormFixture(managedBinary: managedBinary, plugins: plugins, workflow: workflow)
|
|
}
|