localhost1978's picture
download
raw
21 kB
#if os(macOS)
import Foundation
import SSK_Abstractions
import XCTest
@testable import SSK_Foundation
final class VaultLifecycleControllerTests: XCTestCase {
func testFullHostLifecycleUsesRegistryIdentityAndStopsServiceBeforeLock() async throws {
let fixture = try VaultLifecycleFixture()
defer { fixture.removeFiles() }
let createResponse = await fixture.controller.response(
for: .create(
imageURL: fixture.imageURL,
maximumSizeGB: 20,
volumeName: "Vault"
)
)
let record = try XCTUnwrap(createResponse.record)
XCTAssertNil(createResponse.failure)
let persistedRecords = await fixture.persistence.savedRecords()
XCTAssertEqual(persistedRecords, [record])
let keyRequests = await fixture.persistence.createdKeyIDs()
XCTAssertEqual(keyRequests, [record.id])
let lookupResponse = await fixture.controller.response(
for: .lookup(imageURL: fixture.imageURL)
)
XCTAssertEqual(lookupResponse.record, record)
let mountResponse = await fixture.controller.response(
for: .mount(vaultID: record.id)
)
let mounted = try XCTUnwrap(mountResponse.status)
XCTAssertEqual(mounted.state, .mounted)
XCTAssertEqual(
mounted.mountURL,
fixture.mountRoot.appendingPathComponent(record.id.description)
)
XCTAssertFalse(try XCTUnwrap(mounted.mountURL).path.hasPrefix("/Volumes/"))
let diskMount = try XCTUnwrap(fixture.disk.lastMount())
XCTAssertEqual(diskMount.imageURL, record.imageURL)
XCTAssertEqual(diskMount.expectedUUID, record.apfsVolumeUUID)
XCTAssertEqual(diskMount.passphrase, "passphrase-\(record.id.description)")
let leaseResponse = await fixture.controller.response(
for: .acquireLease(vaultID: record.id, durationSeconds: 600)
)
let lease = try XCTUnwrap(leaseResponse.lease)
XCTAssertEqual(lease.vaultID, record.id)
let serviceEventsAfterStart = await fixture.service.events()
XCTAssertEqual(serviceEventsAfterStart, [.start(record.id, try XCTUnwrap(mounted.mountURL))])
let blockedUnmount = await fixture.controller.response(
for: .unmount(vaultID: record.id)
)
XCTAssertEqual(blockedUnmount.failure?.code, .vaultInUse)
XCTAssertTrue(fixture.disk.unmountedSessions().isEmpty)
let releaseResponse = await fixture.controller.response(
for: .releaseLease(vaultID: record.id, leaseID: lease.id)
)
XCTAssertNil(releaseResponse.failure)
let unmountResponse = await fixture.controller.response(
for: .unmount(vaultID: record.id)
)
XCTAssertNil(unmountResponse.failure)
XCTAssertEqual(unmountResponse.status?.state, .unmounted)
XCTAssertEqual(fixture.disk.unmountedSessions().count, 1)
let serviceEvents = await fixture.service.events()
XCTAssertEqual(
serviceEvents,
[
.start(record.id, try XCTUnwrap(mounted.mountURL)),
.stop(record.id),
]
)
}
func testExistingUnregisteredSparsebundleIsRejectedAsLegacy() async throws {
let fixture = try VaultLifecycleFixture()
defer { fixture.removeFiles() }
try FileManager.default.createDirectory(
at: fixture.imageURL,
withIntermediateDirectories: false
)
let response = await fixture.controller.response(
for: .lookup(imageURL: fixture.imageURL)
)
XCTAssertEqual(response.failure?.code, .vaultNotRegistered)
XCTAssertNil(response.record)
XCTAssertTrue(fixture.disk.createdImages().isEmpty)
}
func testTwoVaultsReceiveSeparateUUIDsAndKeys() async throws {
let fixture = try VaultLifecycleFixture()
defer { fixture.removeFiles() }
let secondURL = fixture.root.appendingPathComponent("Second.sparsebundle")
let first = await fixture.controller.response(
for: .create(
imageURL: fixture.imageURL,
maximumSizeGB: 20,
volumeName: "First"
)
)
let second = await fixture.controller.response(
for: .create(
imageURL: secondURL,
maximumSizeGB: 40,
volumeName: "Second"
)
)
let firstRecord = try XCTUnwrap(first.record)
let secondRecord = try XCTUnwrap(second.record)
XCTAssertNotEqual(firstRecord.id, secondRecord.id)
XCTAssertNotEqual(firstRecord.apfsVolumeUUID, secondRecord.apfsVolumeUUID)
let keyIDs = await fixture.persistence.createdKeyIDs()
XCTAssertEqual(Set(keyIDs), Set([firstRecord.id, secondRecord.id]))
let savedRecords = await fixture.persistence.savedRecords()
XCTAssertEqual(Set(savedRecords.map(\.id)), Set([firstRecord.id, secondRecord.id]))
}
func testDiskUnmountFailureKeepsSessionMountedForRetry() async throws {
let fixture = try VaultLifecycleFixture()
defer { fixture.removeFiles() }
let created = await fixture.controller.response(
for: .create(
imageURL: fixture.imageURL,
maximumSizeGB: 20,
volumeName: "Vault"
)
)
let record = try XCTUnwrap(created.record)
_ = await fixture.controller.response(for: .mount(vaultID: record.id))
fixture.disk.setUnmountFailure(true)
let failed = await fixture.controller.response(for: .unmount(vaultID: record.id))
XCTAssertEqual(failed.failure?.code, .diskOperationFailed)
let status = await fixture.controller.response(for: .status(vaultID: record.id))
XCTAssertEqual(status.status?.state, .mounted)
fixture.disk.setUnmountFailure(false)
let retried = await fixture.controller.response(for: .unmount(vaultID: record.id))
XCTAssertNil(retried.failure)
XCTAssertEqual(retried.status?.state, .unmounted)
}
func testMissingRegisteredKeyFailsWithoutAttachingDisk() async throws {
let fixture = try VaultLifecycleFixture()
defer { fixture.removeFiles() }
let created = await fixture.controller.response(
for: .create(
imageURL: fixture.imageURL,
maximumSizeGB: 20,
volumeName: "Vault"
)
)
let record = try XCTUnwrap(created.record)
await fixture.persistence.deletePassphrase(for: record.id)
let response = await fixture.controller.response(for: .mount(vaultID: record.id))
XCTAssertEqual(response.failure?.code, .keyUnavailable)
XCTAssertNil(fixture.disk.lastMount())
}
func testLookupRejectsSymlinkSparsebundle() async throws {
let fixture = try VaultLifecycleFixture()
defer { fixture.removeFiles() }
let realDirectory = fixture.root.appendingPathComponent("real", isDirectory: true)
try FileManager.default.createDirectory(at: realDirectory, withIntermediateDirectories: false)
try FileManager.default.createSymbolicLink(
at: fixture.imageURL,
withDestinationURL: realDirectory
)
let response = await fixture.controller.response(
for: .lookup(imageURL: fixture.imageURL)
)
XCTAssertEqual(response.failure?.code, .invalidPath)
}
func testInvalidCreateSettingsFailBeforeKeyOrDiskWork() async throws {
let fixture = try VaultLifecycleFixture()
defer { fixture.removeFiles() }
let response = await fixture.controller.response(
for: .create(
imageURL: fixture.imageURL,
maximumSizeGB: 0,
volumeName: "Bad/Name"
)
)
XCTAssertEqual(response.failure?.code, .invalidConfiguration)
let createdKeyIDs = await fixture.persistence.createdKeyIDs()
XCTAssertTrue(createdKeyIDs.isEmpty)
XCTAssertTrue(fixture.disk.createdImages().isEmpty)
}
func testBackgroundServiceStartFailureRollsBackLease() async throws {
let fixture = try VaultLifecycleFixture()
defer { fixture.removeFiles() }
await fixture.service.setStartFailure(true)
let created = await fixture.controller.response(
for: .create(
imageURL: fixture.imageURL,
maximumSizeGB: 20,
volumeName: "Vault"
)
)
let record = try XCTUnwrap(created.record)
_ = await fixture.controller.response(for: .mount(vaultID: record.id))
let lease = await fixture.controller.response(
for: .acquireLease(vaultID: record.id, durationSeconds: 600)
)
XCTAssertEqual(lease.failure?.code, .backgroundServiceFailed)
let status = await fixture.controller.response(for: .status(vaultID: record.id))
XCTAssertEqual(status.status?.activeLeaseCount, 0)
XCTAssertEqual(status.status?.state, .mounted)
let unmount = await fixture.controller.response(for: .unmount(vaultID: record.id))
XCTAssertNil(unmount.failure)
}
func testBackgroundServiceStopFailureKeepsLeaseAndBlocksLock() async throws {
let fixture = try VaultLifecycleFixture()
defer { fixture.removeFiles() }
let created = await fixture.controller.response(
for: .create(
imageURL: fixture.imageURL,
maximumSizeGB: 20,
volumeName: "Vault"
)
)
let record = try XCTUnwrap(created.record)
_ = await fixture.controller.response(for: .mount(vaultID: record.id))
let leaseResponse = await fixture.controller.response(
for: .acquireLease(vaultID: record.id, durationSeconds: 600)
)
let lease = try XCTUnwrap(leaseResponse.lease)
await fixture.service.setStopFailure(true)
let release = await fixture.controller.response(
for: .releaseLease(vaultID: record.id, leaseID: lease.id)
)
XCTAssertEqual(release.failure?.code, .backgroundServiceFailed)
let status = await fixture.controller.response(for: .status(vaultID: record.id))
XCTAssertEqual(status.status?.activeLeaseCount, 1)
let unmount = await fixture.controller.response(for: .unmount(vaultID: record.id))
XCTAssertEqual(unmount.failure?.code, .vaultInUse)
XCTAssertTrue(fixture.disk.unmountedSessions().isEmpty)
}
func testSecondVaultCannotTakeBackgroundServiceWhileFirstIsActive() async throws {
let fixture = try VaultLifecycleFixture()
defer { fixture.removeFiles() }
let secondURL = fixture.root.appendingPathComponent("Second.sparsebundle")
let firstResponse = await fixture.controller.response(
for: .create(
imageURL: fixture.imageURL,
maximumSizeGB: 20,
volumeName: "First"
)
)
let first = try XCTUnwrap(firstResponse.record)
let secondResponse = await fixture.controller.response(
for: .create(
imageURL: secondURL,
maximumSizeGB: 20,
volumeName: "Second"
)
)
let second = try XCTUnwrap(secondResponse.record)
_ = await fixture.controller.response(for: .mount(vaultID: first.id))
_ = await fixture.controller.response(for: .mount(vaultID: second.id))
_ = await fixture.controller.response(
for: .acquireLease(vaultID: first.id, durationSeconds: 600)
)
let secondLease = await fixture.controller.response(
for: .acquireLease(vaultID: second.id, durationSeconds: 600)
)
XCTAssertEqual(secondLease.failure?.code, .vaultInUse)
let secondStatus = await fixture.controller.response(for: .status(vaultID: second.id))
XCTAssertEqual(secondStatus.status?.activeLeaseCount, 0)
}
func testShutdownStopsBackgroundServiceBeforeDiskUnmount() async throws {
let root = FileManager.default.temporaryDirectory
.appendingPathComponent("ssk-shutdown-order-\(UUID().uuidString)")
try FileManager.default.createDirectory(
at: root,
withIntermediateDirectories: false,
attributes: [.posixPermissions: 0o700]
)
defer { try? FileManager.default.removeItem(at: root) }
let persistence = TestVaultPersistence()
let events = ShutdownOrderEvents()
let disk = TestVaultDiskOperator(events: events)
let credentials = VaultCredentialStore(
getOrCreatePassphrase: { id in
await persistence.createPassphrase(for: id)
},
existingPassphrase: { id in
await persistence.passphrase(for: id)
},
deleteKey: { id in
await persistence.deletePassphrase(for: id)
},
loadRecords: { await persistence.savedRecords() },
saveRecords: { records in await persistence.save(records: records) }
)
let controller = VaultLifecycleController(
credentials: credentials,
diskOperator: disk,
mountRootURL: root.appendingPathComponent("mounts"),
backgroundHooks: VaultBackgroundServiceHooks(
start: { _, _ in },
stop: { _ in events.append(.serviceStop) }
)
)
let imageURL = root.appendingPathComponent("Vault.sparsebundle")
let createResponse = await controller.response(
for: .create(
imageURL: imageURL,
maximumSizeGB: 20,
volumeName: "Vault"
)
)
let record = try XCTUnwrap(createResponse.record)
_ = await controller.response(for: .mount(vaultID: record.id))
_ = await controller.response(
for: .acquireLease(vaultID: record.id, durationSeconds: 600)
)
try await controller.shutdown()
XCTAssertEqual(events.values(), [.serviceStop, .diskUnmount])
}
}
private struct VaultLifecycleFixture {
let root: URL
let imageURL: URL
let mountRoot: URL
let persistence: TestVaultPersistence
let disk: TestVaultDiskOperator
let service: TestVaultBackgroundService
let controller: VaultLifecycleController
init() throws {
root = FileManager.default.temporaryDirectory
.appendingPathComponent("ssk-vault-tests-\(UUID().uuidString)", isDirectory: true)
try FileManager.default.createDirectory(
at: root,
withIntermediateDirectories: false,
attributes: [.posixPermissions: 0o700]
)
imageURL = root.appendingPathComponent("Vault.sparsebundle", isDirectory: true)
mountRoot = root.appendingPathComponent("mounts", isDirectory: true)
persistence = TestVaultPersistence()
disk = TestVaultDiskOperator()
service = TestVaultBackgroundService()
let persistence = self.persistence
let credentials = VaultCredentialStore(
getOrCreatePassphrase: { id in
await persistence.createPassphrase(for: id)
},
existingPassphrase: { id in
await persistence.passphrase(for: id)
},
deleteKey: { id in
await persistence.deletePassphrase(for: id)
},
loadRecords: {
await persistence.savedRecords()
},
saveRecords: { records in
await persistence.save(records: records)
}
)
let service = self.service
let hooks = VaultBackgroundServiceHooks(
start: { id, mountURL in
try await service.start(id: id, mountURL: mountURL)
},
stop: { id in
try await service.stop(id: id)
}
)
controller = VaultLifecycleController(
credentials: credentials,
diskOperator: disk,
mountRootURL: mountRoot,
backgroundHooks: hooks
)
}
func removeFiles() {
try? FileManager.default.removeItem(at: root)
}
}
private actor TestVaultPersistence {
private var records: [VaultRecord] = []
private var passphrases: [VaultID: String] = [:]
private var keyCreationOrder: [VaultID] = []
func createPassphrase(for id: VaultID) -> String {
if let existing = passphrases[id] { return existing }
let value = "passphrase-\(id.description)"
passphrases[id] = value
keyCreationOrder.append(id)
return value
}
func passphrase(for id: VaultID) -> String? {
passphrases[id]
}
func deletePassphrase(for id: VaultID) {
passphrases.removeValue(forKey: id)
}
func save(records: [VaultRecord]) {
self.records = records
}
func savedRecords() -> [VaultRecord] {
records
}
func createdKeyIDs() -> [VaultID] {
keyCreationOrder
}
}
private final class TestVaultDiskOperator: VaultDiskOperating, @unchecked Sendable {
struct MountCall: Equatable {
let imageURL: URL
let passphrase: String
let mountPoint: URL
let expectedUUID: UUID
}
private let lock = NSLock()
private var creations: [URL] = []
private var mountCall: MountCall?
private var unmounts: [VaultDiskSession] = []
private var shouldFailUnmount = false
private let events: ShutdownOrderEvents?
init(events: ShutdownOrderEvents? = nil) {
self.events = events
}
func createVault(
at imageURL: URL,
passphrase: String,
configuration: SparsebundleConfig
) throws -> VaultDiskCreation {
try FileManager.default.createDirectory(
at: imageURL,
withIntermediateDirectories: false,
attributes: [.posixPermissions: 0o700]
)
lock.withLock { creations.append(imageURL) }
return VaultDiskCreation(apfsVolumeUUID: UUID())
}
func mountVault(
at imageURL: URL,
passphrase: String,
mountPoint: URL,
expectedVolumeUUID: UUID
) throws -> VaultDiskSession {
lock.withLock {
mountCall = MountCall(
imageURL: imageURL,
passphrase: passphrase,
mountPoint: mountPoint,
expectedUUID: expectedVolumeUUID
)
}
return VaultDiskSession(
mountURL: mountPoint,
apfsVolume: URL(fileURLWithPath: "/dev/disk99s1"),
wholeDisk: URL(fileURLWithPath: "/dev/disk99"),
apfsVolumeUUID: expectedVolumeUUID
)
}
func unmountVault(_ session: VaultDiskSession) throws {
let fail = lock.withLock { shouldFailUnmount }
if fail {
throw SparsebundleError.unmountFailed(1)
}
lock.withLock { unmounts.append(session) }
events?.append(.diskUnmount)
}
func createdImages() -> [URL] {
lock.withLock { creations }
}
func lastMount() -> MountCall? {
lock.withLock { mountCall }
}
func unmountedSessions() -> [VaultDiskSession] {
lock.withLock { unmounts }
}
func setUnmountFailure(_ value: Bool) {
lock.withLock { shouldFailUnmount = value }
}
}
private final class ShutdownOrderEvents: @unchecked Sendable {
enum Event: Equatable {
case serviceStop
case diskUnmount
}
private let lock = NSLock()
private var storage: [Event] = []
func append(_ event: Event) {
lock.withLock { storage.append(event) }
}
func values() -> [Event] {
lock.withLock { storage }
}
}
private actor TestVaultBackgroundService {
private enum TestFailure: Error {
case start
case stop
}
enum Event: Equatable {
case start(VaultID, URL)
case stop(VaultID)
}
private var recordedEvents: [Event] = []
private var shouldFailStart = false
private var shouldFailStop = false
func start(id: VaultID, mountURL: URL) throws {
if shouldFailStart { throw TestFailure.start }
recordedEvents.append(.start(id, mountURL))
}
func stop(id: VaultID) throws {
if shouldFailStop { throw TestFailure.stop }
recordedEvents.append(.stop(id))
}
func setStartFailure(_ value: Bool) {
shouldFailStart = value
}
func setStopFailure(_ value: Bool) {
shouldFailStop = value
}
func events() -> [Event] {
recordedEvents
}
}
#endif

Xet Storage Details

Size:
21 kB
·
Xet hash:
687640c1f6b4f0b1f593e52af25a2efcd8a5a22f0c7c43574b66b61c3e1cb5cd

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.