Files
ShadowsocksX-NG/ShadowsocksX-NG2/Tests/Activation/ActivationStateFileStoreTests.swift
Qiu YuzhouandDeepSeek Harness e7116a5ebd test: merge duplicated cases into the stronger one
Every case here restates a rule another test already pins with the same or a
wider input class:

- the activation group case is byte-identical to its neighbour, minus an extra
  assertion; the rule snapshot version guard is re-driven with a second value
  of the same schemaVersion check; the global-mode deployment-failure case is
  a mode-literal copy of the strictly stronger direct-mode one; the clear-
  failure case duplicates the deferred-retry sibling
- two RuntimeDocument projection cases only re-map locals, already asserted as
  full array equality; the IPv6-LAN endpoint case repeats the direct-mode case
  (the derivation reads only ports)
- the three HTTP export cases varied only listenerMode while the derivation
  ignores it and one name contradicted its own expectation; one loop over all
  listener modes now states the real rule
- the 0700/0600 permission baseline is one AtomicFileWriter behavior and stays
  pinned through RuntimeFileStoreTests, which also covers self-heal and atomic
  replace

The command-boundary case keeps its mixed-scenario isolation check (four
commands, four independent adapter counters) and drops only its unfalsifiable
queriedTargetIDs claim.

Co-Authored-By: DeepSeek Harness <noreply@deepseek.com>
2026-10-21 01:46:29 +08:00

94 lines
3.5 KiB
Swift

import XCTest
@testable import ShadowsocksX_NG2
/// 活动目标持久化缝:往返无损、缺失/损坏按安全侧恢复、权限基线。
final class ActivationStateFileStoreTests: XCTestCase {
private var workDir: URL!
private var store: ActivationStateFileStore!
override func setUpWithError() throws {
try super.setUpWithError()
workDir = FileManager.default.temporaryDirectory
.appendingPathComponent("activation-file-store-tests-\(UUID().uuidString)", isDirectory: true)
try FileManager.default.createDirectory(at: workDir, withIntermediateDirectories: true)
store = ActivationStateFileStore(fileURL: workDir.appendingPathComponent("activation.json"))
}
override func tearDownWithError() throws {
try FileManager.default.removeItem(at: workDir)
try super.tearDownWithError()
}
// MARK: 缺失与损坏
func testMissingFileLoadsAsNoActiveTarget() throws {
XCTAssertNil(try store.loadActiveTargetID(), "文件缺失按从未激活处理")
}
func testBrokenJSONLoadsAsNoActiveTargetOnTheSafeSide() throws {
try "{ not json {{{".write(to: store.fileURL, atomically: true, encoding: .utf8)
XCTAssertNil(
try store.loadActiveTargetID(), "损坏按安全侧恢复:无活动目标,代理保持停止")
}
func testUnknownSchemaVersionLoadsAsNoActiveTarget() throws {
let payload = """
{"version": 99, "activeTargetID": "s1"}
"""
try payload.write(to: store.fileURL, atomically: true, encoding: .utf8)
XCTAssertNil(try store.loadActiveTargetID())
}
// MARK: 往返无损
func testRoundTripPreservesTargetIdentity() throws {
let target = NodeID(rawValue: "manual:group-1")
try store.save(activeTargetID: target)
XCTAssertEqual(try store.loadActiveTargetID(), target, "目标身份跨持久化无损")
}
func testSavingNilClearsPersistedTarget() throws {
try store.save(activeTargetID: NodeID(rawValue: "s1"))
try store.save(activeTargetID: nil)
XCTAssertNil(try store.loadActiveTargetID(), "清除即整文件替换,不再残留旧目标")
}
func testDefaultFileURLMatchesRuntimeDirectoryContract() {
XCTAssertTrue(
ActivationStateFileStore.defaultFileURL().path.hasSuffix(
"ShadowsocksX-NG2/configuration/activation.json")
)
}
// MARK: 与状态机的生命周期往返
func testActivationLifecycleRoundTripThroughFileStore() throws {
var catalog = ConfigurationCatalog()
let leaf = try ActivationFixture.addPlainServer("a", in: &catalog)
var machine = ActivationStateMachine()
_ = try ActivationFixture.activate(&machine, leaf, in: catalog)
try store.save(activeTargetID: machine.activeTargetID)
// 下次启动:从文件恢复目标,载入目录后立即重校验(「重新校验同步」)。
var restored = ActivationStateMachine(activeTargetID: try store.loadActiveTargetID())
let configuration = try ActivationFixture.requireDeployed(
ActivationFixture.commit(&restored, catalog))
XCTAssertEqual(configuration.targetID, leaf, "恢复的目标重放派生")
// 目标随后失效:清除并停止,持久化同步清空。
try catalog.remove(leaf)
let failure = try ActivationFixture.requireCleared(
ActivationFixture.commit(&restored, catalog))
XCTAssertEqual(failure, .targetNotFound(leaf))
try store.save(activeTargetID: restored.activeTargetID)
XCTAssertNil(try store.loadActiveTargetID(), "清除随持久化落盘,重启不再复活旧目标")
}
}