mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-08 11:14:51 +00:00
125 lines
5.3 KiB
Swift
125 lines
5.3 KiB
Swift
import XCTest
|
|
|
|
@testable import ShadowsocksX_NG2
|
|
|
|
/// 表单新建手动服务器的点名校验用例(标签 + 草稿 + 预期 typed error)。
|
|
private struct InvalidCreateDraftCase {
|
|
let label: String
|
|
let draft: ServerEditDraft
|
|
let expected: ServerFormError
|
|
|
|
init(_ label: String, _ draft: ServerEditDraft, _ expected: ServerFormError) {
|
|
self.label = label
|
|
self.draft = draft
|
|
self.expected = expected
|
|
}
|
|
}
|
|
|
|
/// 表单新建手动服务器命令(`createServer`)的 UI-facing interface 测试:
|
|
/// 落点与身份、凭据入存储、点名校验拒绝与插件选择落盘,语义与编辑提交对称。
|
|
@MainActor
|
|
final class CatalogWorkflowCreateServerTests: XCTestCase {
|
|
private var workDir: URL!
|
|
private var fileURL: URL!
|
|
private var runtime: FakeCatalogRuntime!
|
|
var workflow: CatalogWorkflow!
|
|
|
|
override func setUp() async throws {
|
|
try await super.setUp()
|
|
// 专用工作目录:写入器会把父目录强制 0700(不得指向临时根)。
|
|
workDir = FileManager.default.temporaryDirectory
|
|
.appendingPathComponent("catalog-create-server-tests-\(UUID().uuidString)", isDirectory: true)
|
|
try FileManager.default.createDirectory(at: workDir, withIntermediateDirectories: true)
|
|
fileURL = workDir.appendingPathComponent("catalog.json")
|
|
|
|
runtime = FakeCatalogRuntime()
|
|
runtime.hasActiveTarget = true
|
|
workflow = makeWorkflow()
|
|
}
|
|
|
|
override func tearDown() async throws {
|
|
try? FileManager.default.removeItem(at: workDir)
|
|
try await super.tearDown()
|
|
}
|
|
|
|
private func makeWorkflow() -> CatalogWorkflow {
|
|
let coordinator = CatalogCommitCoordinator(
|
|
fileStore: makeTestCatalogStore(fileURL: fileURL), runtime: runtime)
|
|
return makeCatalogWorkflow(
|
|
coordinator: coordinator,
|
|
plugins: NoManagedPluginProvider())
|
|
}
|
|
|
|
func testCreateServerAddsManualLeafWithCredentialAndReturnsIdentity() async throws {
|
|
let groupID = try await workflow.createGroup(named: "手动组", into: nil)
|
|
let id = try await workflow.createServer(
|
|
ServerEditDraft(
|
|
address: "203.0.113.10", port: 8389, encryptionMethod: "aes-256-gcm",
|
|
password: "表单密码", remark: "新来的", plugin: .none, pluginOptions: nil),
|
|
into: groupID)
|
|
let node = try XCTUnwrap(workflow.tree.node(withID: id))
|
|
XCTAssertFalse(node.isGroup)
|
|
XCTAssertTrue(node.isManual)
|
|
XCTAssertEqual(node.parentID, groupID)
|
|
let form = try XCTUnwrap(try workflow.serverEditForm(for: id))
|
|
XCTAssertEqual(form.address, "203.0.113.10")
|
|
XCTAssertEqual(form.port, 8389)
|
|
XCTAssertEqual(form.remark, "新来的")
|
|
XCTAssertEqual(form.encryptionMethod, "aes-256-gcm")
|
|
XCTAssertEqual(form.password, "表单密码", "密码作为凭据写入,目录持引用")
|
|
XCTAssertTrue(node.invalidReasons.isEmpty)
|
|
}
|
|
|
|
func testCreateServerRejectsInvalidDraftsLeavingCatalogAndCredentialsUntouched() async throws {
|
|
let groupID = try await workflow.createGroup(named: "组", into: nil)
|
|
for caseItem in Self.invalidDraftCases {
|
|
await expectThrowsAsync(
|
|
{ _ = try await workflow.createServer(caseItem.draft, into: groupID) },
|
|
onThrow: { error in
|
|
XCTAssertEqual(error as? ServerFormError, caseItem.expected, caseItem.label)
|
|
})
|
|
}
|
|
|
|
XCTAssertEqual(workflow.tree.node(withID: groupID)?.childCount, 0, "校验失败不进目录")
|
|
}
|
|
|
|
func testCreateServerPluginSelectionStoresProgramAndOptions() async throws {
|
|
let id = try await workflow.createServer(
|
|
ServerEditDraft(
|
|
address: "203.0.113.10", port: 8388, encryptionMethod: "aes-256-gcm",
|
|
password: "密码", remark: "插件服务器", plugin: .named(program: "v2ray-plugin"),
|
|
pluginOptions: "mode=websocket;host=example.com"),
|
|
into: nil)
|
|
let plugin = try XCTUnwrap(try workflow.serverEditForm(for: id)?.plugin)
|
|
XCTAssertEqual(plugin.selection, .named(program: "v2ray-plugin"))
|
|
XCTAssertTrue(plugin.optionsPresent)
|
|
XCTAssertEqual(plugin.options, "mode=websocket;host=example.com")
|
|
}
|
|
|
|
/// 表单校验用例表:与 `updateServer` 同规则的点名拒绝(含受管集外插件)。
|
|
private static var invalidDraftCases: [InvalidCreateDraftCase] {
|
|
func draft(
|
|
address: String = "203.0.113.10", port: Int = 8388,
|
|
encryptionMethod: String = "aes-256-gcm", password: String = "密码",
|
|
plugin: PluginSelection = .none
|
|
) -> ServerEditDraft {
|
|
ServerEditDraft(
|
|
address: address, port: port, encryptionMethod: encryptionMethod, password: password,
|
|
remark: "", plugin: plugin, pluginOptions: nil)
|
|
}
|
|
return [
|
|
InvalidCreateDraftCase("空地址", draft(address: " "), .invalidAddress),
|
|
InvalidCreateDraftCase("端口下界", draft(port: 0), .invalidPort),
|
|
InvalidCreateDraftCase("端口上界", draft(port: 65_536), .invalidPort),
|
|
InvalidCreateDraftCase("缺加密方式", draft(encryptionMethod: " "), .missingEncryptionMethod),
|
|
InvalidCreateDraftCase(
|
|
"不支持加密方式", draft(encryptionMethod: "future-cipher"),
|
|
.unsupportedEncryptionMethod("future-cipher")),
|
|
InvalidCreateDraftCase("空密码", draft(password: ""), .invalidPassword),
|
|
InvalidCreateDraftCase(
|
|
"受管集外插件", draft(plugin: .named(program: "future-plugin")),
|
|
.pluginUnknown("future-plugin")),
|
|
]
|
|
}
|
|
}
|