Files
ShadowsocksX-NG/ShadowsocksX-NG2/Tests/Subscription/SubscriptionDocumentParserTests+SchemaValidation.swift
Qiu Yuzhou 20a62117f2 refactor(tests): 按功能领域重组 Tests/ 目录
Tests/ 根层原有 138 个平铺测试文件,无法从目录读出测试覆盖的功能领域。

按功能领域重组为 17 个目录(Catalog/Workflow 与 Rules/Custom 两处嵌套,分别
对应 App/Application/CatalogWorkflow 与 Domain/Rules/Custom):Architecture /
Support / Agent / Activation / Catalog / Subscription / Rules / ProxyRuntime /
ProxyControl / Runtime / Settings / SystemProxy / Plugin / Diagnostics /
Credentials / Workspace / Servers;Smoke/ 保持独立 target 不动。138 个移动中
132 个是 100% 相似度的纯重命名,其余 6 个在重命名之上带路径反推改写(见下)。

本次不是纯搬移:测试代码比生产代码多一层位置耦合,有 7 处用固定层数的
deletingLastPathComponent() 从 #filePath 反推仓库路径(都假定文件就在 Tests/
根层)。这 7 处改走新增的 Tests/Support/TestSourceTree.swift——从调用点沿父
目录上溯找到含 project.yml 的目录,因此不再依赖父目录层数,以后移动测试文件
也不会静默指错。

配套改动:

- 新增 Tests 布局守卫 TestsLayoutTests,断言 Tests/ 根层无平铺 .swift。比
  Domain 那条弱一档——根层的 Info.plist(info.path 生成物)与
  Fixtures/RuleSnapshots 下的 JSON(folder resource)都必须保留,不能用
  「树下只有 .swift」覆盖。
- project.yml:Smoke target 按文件引用的 Tests/AppArtifact.swift 改指
  Tests/Support/;两处注释里写死的 Tests/SkeletonTests.swift 随移动更新。
  excludes 的裸名 "Smoke" / "Fixtures" 仍匹配 Tests/ 直属子目录,无需改动。
- README 的 Tests 条目展开为目录说明,并修掉 Domain 条目里写死的
  Tests/SkeletonTests.swift。
- 顺带删去 DomainLayoutTests 文档注释里「整理前 62 个文件」这类易漂移的数字。

验证:xcodegen generate 后单测 target 为 139 个源文件(全部非 Smoke 文件,0 个
Smoke 文件混入)且仍以 Fixtures/RuleSnapshots 为资源,Smoke target 为 8 个
Smoke 文件加 AppArtifact;依赖路径反推的 6 个测试类全绿(路径指错即失败);
task format 未改写任何被移动文件;task lint 与整理前同为 21 条内容型 warning;
build 0 error;task test 1025 用例全通过(整理前 1024,+1 为新守卫);
TestsLayoutTests 经变异验证(根层投放探针即失败);packaging-gate ALL CHECKS
PASSED。
2026-10-05 20:37:35 +08:00

97 lines
3.7 KiB
Swift

import XCTest
@testable import ShadowsocksX_NG2
extension SubscriptionDocumentParserTests {
// MARK: schema 与记录校验失败(整份拒绝)
func testUnsupportedVersionThrows() {
let data = Data(#"{"version": 2, "servers": []}"#.utf8)
XCTAssertThrowsError(try parse(data)) { error in
XCTAssertEqual(error as? SubscriptionParseError, .unsupportedSchemaVersion)
}
}
func testMissingVersionThrows() {
XCTAssertThrowsError(try parse(Data(#"{"servers": []}"#.utf8))) { error in
XCTAssertEqual(error as? SubscriptionParseError, .unsupportedSchemaVersion)
}
}
func testMissingServersThrows() {
XCTAssertThrowsError(try parse(Data(#"{"version": 1}"#.utf8))) { error in
XCTAssertEqual(error as? SubscriptionParseError, .missingServers)
}
}
func testMalformedJSONThrowsDecodingFailure() {
XCTAssertThrowsError(try parse(Data(#"{"version": 1, "servers": ["#.utf8))) { error in
XCTAssertEqual(error as? SubscriptionParseError, .decodingFailure)
}
}
func testServersNotAnArrayThrowsDecodingFailure() {
XCTAssertThrowsError(try parse(Data(#"{"version": 1, "servers": "nope"}"#.utf8))) { error in
XCTAssertEqual(error as? SubscriptionParseError, .decodingFailure)
}
}
func testRecordValidationFailuresThrowWithIndex() {
let cases: [(String, String)] = [
(#"{"server": "h", "server_port": 8388, "password": "p"}"#, "method"),
(#"{"server": "h", "server_port": 8388, "method": "m"}"#, "password"),
(#"{"server_port": 8388, "password": "p", "method": "m"}"#, "server"),
(#"{"server": "", "server_port": 8388, "password": "p", "method": "m"}"#, "server"),
(#"{"server": "h", "server_port": 0, "password": "p", "method": "m"}"#, "server_port"),
(#"{"server": "h", "server_port": 65536, "password": "p", "method": "m"}"#, "server_port"),
(#"{"server": "h", "server_port": 8388, "password": "", "method": "m"}"#, "password"),
(
#"{"id": "not-a-uuid", "server": "h", "server_port": 8388, "password": "p", "method": "m"}"#,
"id",
),
]
for (record, expectedField) in cases {
let data = Data("{\"version\": 1, \"servers\": [\(record)]}".utf8)
XCTAssertThrowsError(try parse(data), expectedField) { error in
guard case .recordValidation(let index, let reason) = error as? SubscriptionParseError
else {
return XCTFail("\(expectedField): 应报 recordValidation,实际 \(error)")
}
XCTAssertEqual(index, 0)
XCTAssertTrue(reason.contains(expectedField), "\(expectedField): \(reason)")
}
}
}
func testDuplicateServerIDThrows() {
let data = Data(
"""
{"version": 1, "servers": [
{"id": "aaaaaaaa-0000-4000-8000-00000000000a", "server": "203.0.113.1", "server_port": 8388,
"password": "p", "method": "m"},
{"id": "aaaaaaaa-0000-4000-8000-00000000000a", "server": "203.0.113.9", "server_port": 9999,
"password": "q", "method": "n"}]}
""".utf8)
XCTAssertThrowsError(try parse(data)) { error in
XCTAssertEqual(
error as? SubscriptionParseError,
.duplicateServerID(id: "id:aaaaaaaa-0000-4000-8000-00000000000a"))
}
}
func testIdenticalIdLessRecordsAreDuplicateIdentity() {
let record =
#"{"server": "203.0.113.1", "server_port": 8388, "password": "p", "method": "m"}"#
let data = Data("{\"version\": 1, \"servers\": [\(record), \(record)]}".utf8)
XCTAssertThrowsError(try parse(data)) { error in
guard case .duplicateServerID(let id) = error as? SubscriptionParseError else {
return XCTFail("应报 duplicateServerID,实际 \(error)")
}
XCTAssertTrue(id.hasPrefix("content:"), "无稳定 ID 用内容指纹判重")
}
}
}