ActionItem now carries optional targetPaths so plan.execute can use plan-carried targets instead of reconstructing execution intent from findings. This improves execution reliability and enables proper restore mappings for recovery items. - Add targetPaths field to ActionItem domain model - Update plan execution to prefer plan-carried targets with finding fallback - Expand safe cache path fragments for direct-trash execution - Add gate review documentation for ATL-211/212/215 - Bump protocol version to 0.3.0
764 lines
35 KiB
Swift
764 lines
35 KiB
Swift
import XCTest
|
|
@testable import AtlasInfrastructure
|
|
import AtlasApplication
|
|
import AtlasDomain
|
|
import AtlasProtocol
|
|
|
|
final class AtlasInfrastructureTests: XCTestCase {
|
|
func testRepositoryPersistsWorkspaceState() {
|
|
let fileURL = temporaryStateFileURL()
|
|
let repository = AtlasWorkspaceRepository(stateFileURL: fileURL)
|
|
var state = AtlasScaffoldWorkspace.state()
|
|
state.settings.recoveryRetentionDays = 21
|
|
|
|
XCTAssertNoThrow(try repository.saveState(state))
|
|
let loaded = repository.loadState()
|
|
|
|
XCTAssertEqual(loaded.settings.recoveryRetentionDays, 21)
|
|
XCTAssertEqual(loaded.snapshot.apps.count, state.snapshot.apps.count)
|
|
}
|
|
|
|
|
|
func testRepositorySaveStateThrowsForInvalidParentURL() {
|
|
let repository = AtlasWorkspaceRepository(
|
|
stateFileURL: URL(fileURLWithPath: "/dev/null/workspace-state.json")
|
|
)
|
|
|
|
XCTAssertThrowsError(try repository.saveState(AtlasScaffoldWorkspace.state()))
|
|
}
|
|
|
|
func testExecutePlanMovesSupportedFindingsIntoRecoveryWhileKeepingInspectionOnlyItems() async throws {
|
|
let repository = AtlasWorkspaceRepository(stateFileURL: temporaryStateFileURL())
|
|
let home = FileManager.default.homeDirectoryForCurrentUser
|
|
let targetDirectory = home.appendingPathComponent("Library/Caches/AtlasExecutionTests/" + UUID().uuidString, isDirectory: true)
|
|
try FileManager.default.createDirectory(at: targetDirectory, withIntermediateDirectories: true)
|
|
let targetFile = targetDirectory.appendingPathComponent("sample.cache")
|
|
try Data("cache".utf8).write(to: targetFile)
|
|
|
|
let supportedFinding = Finding(
|
|
id: UUID(),
|
|
title: "Sample cache",
|
|
detail: targetFile.path,
|
|
bytes: 5,
|
|
risk: .safe,
|
|
category: "Developer tools",
|
|
targetPaths: [targetFile.path]
|
|
)
|
|
let unsupportedPath = home.appendingPathComponent("Documents/AtlasUnsupported/" + UUID().uuidString).path
|
|
let unsupportedFinding = Finding(
|
|
id: UUID(),
|
|
title: "Unsupported cache",
|
|
detail: unsupportedPath,
|
|
bytes: 7,
|
|
risk: .safe,
|
|
category: "Developer tools",
|
|
targetPaths: [unsupportedPath]
|
|
)
|
|
let state = AtlasWorkspaceState(
|
|
snapshot: AtlasWorkspaceSnapshot(
|
|
reclaimableSpaceBytes: 12,
|
|
findings: [supportedFinding, unsupportedFinding],
|
|
apps: [],
|
|
taskRuns: [],
|
|
recoveryItems: [],
|
|
permissions: [],
|
|
healthSnapshot: nil
|
|
),
|
|
currentPlan: ActionPlan(
|
|
title: "Review 2 selected findings",
|
|
items: [
|
|
ActionItem(id: supportedFinding.id, title: "Move Sample cache to Trash", detail: supportedFinding.detail, kind: .removeCache, recoverable: true),
|
|
ActionItem(id: unsupportedFinding.id, title: "Inspect Unsupported cache", detail: unsupportedFinding.detail, kind: .inspectPermission, recoverable: false),
|
|
],
|
|
estimatedBytes: 12
|
|
),
|
|
settings: AtlasScaffoldWorkspace.state().settings
|
|
)
|
|
_ = try repository.saveState(state)
|
|
|
|
let worker = AtlasScaffoldWorkerService(repository: repository, allowStateOnlyCleanExecution: false)
|
|
let executeResult = try await worker.submit(
|
|
AtlasRequestEnvelope(command: .executePlan(planID: state.currentPlan.id))
|
|
)
|
|
|
|
if case let .accepted(task) = executeResult.response.response {
|
|
XCTAssertEqual(task.kind, .executePlan)
|
|
} else {
|
|
XCTFail("Expected accepted execute-plan response")
|
|
}
|
|
XCTAssertFalse(FileManager.default.fileExists(atPath: targetFile.path))
|
|
XCTAssertEqual(executeResult.snapshot.findings.count, 1)
|
|
XCTAssertEqual(executeResult.snapshot.findings.first?.id, unsupportedFinding.id)
|
|
XCTAssertEqual(executeResult.snapshot.recoveryItems.count, 1)
|
|
|
|
let restoredItemID = try XCTUnwrap(executeResult.snapshot.recoveryItems.first?.id)
|
|
let restoreTaskID = UUID()
|
|
let restoreResult = try await worker.submit(
|
|
AtlasRequestEnvelope(command: .restoreItems(taskID: restoreTaskID, itemIDs: [restoredItemID]))
|
|
)
|
|
|
|
XCTAssertTrue(FileManager.default.fileExists(atPath: targetFile.path))
|
|
XCTAssertEqual(Set(restoreResult.snapshot.findings.map(\.id)), Set([supportedFinding.id, unsupportedFinding.id]))
|
|
XCTAssertEqual(restoreResult.snapshot.recoveryItems.count, 0)
|
|
}
|
|
|
|
func testExecutePlanPreservesCompletedRecoveryEntriesWhenLaterFindingFailsInStateOnlyMode() async throws {
|
|
let repository = AtlasWorkspaceRepository(stateFileURL: temporaryStateFileURL())
|
|
let fileManager = FileManager.default
|
|
let home = fileManager.homeDirectoryForCurrentUser
|
|
|
|
let cacheDirectory = home.appendingPathComponent("Library/Caches/AtlasExecutionTests/" + UUID().uuidString, isDirectory: true)
|
|
try fileManager.createDirectory(at: cacheDirectory, withIntermediateDirectories: true)
|
|
let cacheFile = cacheDirectory.appendingPathComponent("sample.cache")
|
|
try Data("cache".utf8).write(to: cacheFile)
|
|
|
|
let helperDirectory = home.appendingPathComponent("Applications/AtlasExecutionTests/" + UUID().uuidString, isDirectory: true)
|
|
try fileManager.createDirectory(at: helperDirectory, withIntermediateDirectories: true)
|
|
let helperFile = helperDirectory.appendingPathComponent("HelperRequired.app")
|
|
try Data("helper".utf8).write(to: helperFile)
|
|
|
|
addTeardownBlock {
|
|
try? FileManager.default.removeItem(at: helperDirectory)
|
|
}
|
|
|
|
let supportedFinding = Finding(
|
|
id: UUID(),
|
|
title: "Sample cache",
|
|
detail: cacheFile.path,
|
|
bytes: 5,
|
|
risk: .safe,
|
|
category: "Developer tools",
|
|
targetPaths: [cacheFile.path]
|
|
)
|
|
let helperRequiredFinding = Finding(
|
|
id: UUID(),
|
|
title: "Helper required cleanup",
|
|
detail: helperFile.path,
|
|
bytes: 7,
|
|
risk: .safe,
|
|
category: "Developer tools",
|
|
targetPaths: [helperFile.path]
|
|
)
|
|
let state = AtlasWorkspaceState(
|
|
snapshot: AtlasWorkspaceSnapshot(
|
|
reclaimableSpaceBytes: 12,
|
|
findings: [supportedFinding, helperRequiredFinding],
|
|
apps: [],
|
|
taskRuns: [],
|
|
recoveryItems: [],
|
|
permissions: [],
|
|
healthSnapshot: nil
|
|
),
|
|
currentPlan: ActionPlan(
|
|
title: "Review 2 selected findings",
|
|
items: [
|
|
ActionItem(id: supportedFinding.id, title: "Move Sample cache to Trash", detail: supportedFinding.detail, kind: .removeCache, recoverable: true),
|
|
ActionItem(id: helperRequiredFinding.id, title: "Move Helper required cleanup to Trash", detail: helperRequiredFinding.detail, kind: .removeCache, recoverable: true),
|
|
],
|
|
estimatedBytes: 12
|
|
),
|
|
settings: AtlasScaffoldWorkspace.state().settings
|
|
)
|
|
_ = try repository.saveState(state)
|
|
|
|
let worker = AtlasScaffoldWorkerService(repository: repository, allowStateOnlyCleanExecution: true)
|
|
let result = try await worker.submit(AtlasRequestEnvelope(command: .executePlan(planID: state.currentPlan.id)))
|
|
|
|
if case let .accepted(task) = result.response.response {
|
|
XCTAssertEqual(task.kind, .executePlan)
|
|
} else {
|
|
XCTFail("Expected accepted execute-plan response")
|
|
}
|
|
XCTAssertFalse(fileManager.fileExists(atPath: cacheFile.path))
|
|
XCTAssertTrue(fileManager.fileExists(atPath: helperFile.path))
|
|
XCTAssertEqual(result.snapshot.recoveryItems.count, 1)
|
|
XCTAssertEqual(result.snapshot.recoveryItems.first?.title, supportedFinding.title)
|
|
XCTAssertEqual(result.snapshot.findings.map(\.id), [helperRequiredFinding.id])
|
|
XCTAssertEqual(result.snapshot.taskRuns.first?.status, .failed)
|
|
XCTAssertEqual(
|
|
result.snapshot.taskRuns.first?.summary,
|
|
[
|
|
AtlasL10n.string("infrastructure.execute.summary.clean.one", language: state.settings.language, 1),
|
|
AtlasL10n.string("infrastructure.execute.summary.clean.failed.one", language: state.settings.language, 1),
|
|
].joined(separator: " ")
|
|
)
|
|
}
|
|
|
|
func testStartScanRejectsWhenProviderFailsWithoutFallback() async throws {
|
|
let repository = AtlasWorkspaceRepository(stateFileURL: temporaryStateFileURL())
|
|
let worker = AtlasScaffoldWorkerService(
|
|
repository: repository,
|
|
smartCleanScanProvider: FailingSmartCleanProvider(),
|
|
allowProviderFailureFallback: false
|
|
)
|
|
|
|
let result = try await worker.submit(
|
|
AtlasRequestEnvelope(command: .startScan(taskID: UUID()))
|
|
)
|
|
|
|
guard case let .rejected(code, reason) = result.response.response else {
|
|
return XCTFail("Expected rejected scan response")
|
|
}
|
|
XCTAssertEqual(code, .executionUnavailable)
|
|
XCTAssertTrue(reason.contains("Smart Clean scan is unavailable"))
|
|
}
|
|
|
|
func testPermissionInspectorMarksFullDiskAccessGrantedWhenAnyProbeIsReadable() async {
|
|
let probeURLs = [
|
|
URL(fileURLWithPath: "/tmp/unreadable"),
|
|
URL(fileURLWithPath: "/tmp/readable"),
|
|
]
|
|
let inspector = AtlasPermissionInspector(
|
|
homeDirectoryURL: URL(fileURLWithPath: "/tmp"),
|
|
fullDiskAccessProbeURLs: probeURLs,
|
|
protectedLocationReader: { url in url.path == "/tmp/readable" },
|
|
accessibilityStatusProvider: { false },
|
|
notificationsAuthorizationProvider: { false }
|
|
)
|
|
|
|
let permissions = await inspector.snapshot()
|
|
let fullDiskAccess = permissions.first(where: { $0.kind == .fullDiskAccess })
|
|
|
|
XCTAssertEqual(fullDiskAccess?.isGranted, true)
|
|
}
|
|
|
|
func testPermissionInspectorMarksFullDiskAccessMissingWhenAllProbesFail() async {
|
|
let probeURLs = [
|
|
URL(fileURLWithPath: "/tmp/probe-a"),
|
|
URL(fileURLWithPath: "/tmp/probe-b"),
|
|
]
|
|
let inspector = AtlasPermissionInspector(
|
|
homeDirectoryURL: URL(fileURLWithPath: "/tmp"),
|
|
fullDiskAccessProbeURLs: probeURLs,
|
|
protectedLocationReader: { _ in false },
|
|
accessibilityStatusProvider: { false },
|
|
notificationsAuthorizationProvider: { false }
|
|
)
|
|
|
|
let permissions = await inspector.snapshot()
|
|
let fullDiskAccess = permissions.first(where: { $0.kind == .fullDiskAccess })
|
|
|
|
XCTAssertEqual(fullDiskAccess?.isGranted, false)
|
|
XCTAssertTrue(fullDiskAccess?.rationale.contains("重新打开") == true || fullDiskAccess?.rationale.contains("reopen Atlas") == true)
|
|
}
|
|
|
|
func testUnsupportedTargetIsDowngradedToInspectionAndDoesNotFailExecution() async throws {
|
|
let repository = AtlasWorkspaceRepository(stateFileURL: temporaryStateFileURL())
|
|
let unsupportedPath = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Documents/AtlasUnsupported/" + UUID().uuidString).path
|
|
let finding = Finding(
|
|
id: UUID(),
|
|
title: "Unsupported cache",
|
|
detail: unsupportedPath,
|
|
bytes: 5,
|
|
risk: .safe,
|
|
category: "Developer tools",
|
|
targetPaths: [unsupportedPath]
|
|
)
|
|
XCTAssertFalse(AtlasSmartCleanExecutionSupport.isFindingExecutionSupported(finding))
|
|
let state = AtlasWorkspaceState(
|
|
snapshot: AtlasWorkspaceSnapshot(
|
|
reclaimableSpaceBytes: 5,
|
|
findings: [finding],
|
|
apps: [],
|
|
taskRuns: [],
|
|
recoveryItems: [],
|
|
permissions: [],
|
|
healthSnapshot: nil
|
|
),
|
|
currentPlan: ActionPlan(
|
|
title: "Review 1 selected finding",
|
|
items: [ActionItem(id: finding.id, title: "Inspect Unsupported cache", detail: finding.detail, kind: .inspectPermission, recoverable: false)],
|
|
estimatedBytes: 5
|
|
),
|
|
settings: AtlasScaffoldWorkspace.state().settings
|
|
)
|
|
_ = try repository.saveState(state)
|
|
|
|
let worker = AtlasScaffoldWorkerService(repository: repository, allowStateOnlyCleanExecution: false)
|
|
let result = try await worker.submit(AtlasRequestEnvelope(command: .executePlan(planID: state.currentPlan.id)))
|
|
|
|
if case let .accepted(task) = result.response.response {
|
|
XCTAssertEqual(task.kind, .executePlan)
|
|
} else {
|
|
XCTFail("Expected accepted execute-plan response")
|
|
}
|
|
XCTAssertEqual(result.snapshot.findings.count, 1)
|
|
XCTAssertEqual(result.snapshot.recoveryItems.count, 0)
|
|
}
|
|
|
|
func testInspectionOnlyPlanIsAcceptedWithoutMutatingState() async throws {
|
|
let repository = AtlasWorkspaceRepository(stateFileURL: temporaryStateFileURL())
|
|
let worker = AtlasScaffoldWorkerService(repository: repository, allowStateOnlyCleanExecution: false)
|
|
let initialState = repository.loadState()
|
|
|
|
let result = try await worker.submit(
|
|
AtlasRequestEnvelope(command: .executePlan(planID: initialState.currentPlan.id))
|
|
)
|
|
|
|
if case let .accepted(task) = result.response.response {
|
|
XCTAssertEqual(task.kind, .executePlan)
|
|
} else {
|
|
XCTFail("Expected accepted execute-plan response")
|
|
}
|
|
XCTAssertEqual(result.snapshot.findings.count, initialState.snapshot.findings.count)
|
|
XCTAssertEqual(result.snapshot.recoveryItems.count, initialState.snapshot.recoveryItems.count)
|
|
}
|
|
|
|
func testExecutePlanRejectsWhenFailClosedExecutionFailsBeforeAnySideEffect() async throws {
|
|
let repository = AtlasWorkspaceRepository(stateFileURL: temporaryStateFileURL())
|
|
let fileManager = FileManager.default
|
|
let helperDirectory = fileManager.homeDirectoryForCurrentUser.appendingPathComponent("Applications/AtlasExecutionTests/" + UUID().uuidString, isDirectory: true)
|
|
try fileManager.createDirectory(at: helperDirectory, withIntermediateDirectories: true)
|
|
let helperFile = helperDirectory.appendingPathComponent("HelperRequired.app")
|
|
try Data("helper".utf8).write(to: helperFile)
|
|
|
|
addTeardownBlock {
|
|
try? FileManager.default.removeItem(at: helperDirectory)
|
|
}
|
|
|
|
let finding = Finding(
|
|
id: UUID(),
|
|
title: "Helper required cleanup",
|
|
detail: helperFile.path,
|
|
bytes: 7,
|
|
risk: .safe,
|
|
category: "Developer tools",
|
|
targetPaths: [helperFile.path]
|
|
)
|
|
let state = AtlasWorkspaceState(
|
|
snapshot: AtlasWorkspaceSnapshot(
|
|
reclaimableSpaceBytes: 7,
|
|
findings: [finding],
|
|
apps: [],
|
|
taskRuns: [],
|
|
recoveryItems: [],
|
|
permissions: [],
|
|
healthSnapshot: nil
|
|
),
|
|
currentPlan: ActionPlan(
|
|
title: "Review 1 selected finding",
|
|
items: [ActionItem(id: finding.id, title: "Move Helper required cleanup to Trash", detail: finding.detail, kind: .removeCache, recoverable: true)],
|
|
estimatedBytes: 7
|
|
),
|
|
settings: AtlasScaffoldWorkspace.state().settings
|
|
)
|
|
_ = try repository.saveState(state)
|
|
|
|
let worker = AtlasScaffoldWorkerService(repository: repository, allowStateOnlyCleanExecution: false)
|
|
let result = try await worker.submit(AtlasRequestEnvelope(command: .executePlan(planID: state.currentPlan.id)))
|
|
|
|
guard case let .rejected(code, reason) = result.response.response else {
|
|
return XCTFail("Expected rejected execute-plan response")
|
|
}
|
|
XCTAssertEqual(code, .executionUnavailable)
|
|
XCTAssertTrue(reason.contains("Bundled helper unavailable"))
|
|
XCTAssertTrue(fileManager.fileExists(atPath: helperFile.path))
|
|
XCTAssertEqual(result.snapshot.findings.map(\.id), [finding.id])
|
|
XCTAssertEqual(result.snapshot.recoveryItems.count, 0)
|
|
}
|
|
|
|
func testZcompdumpTargetIsSupportedExecutionTarget() {
|
|
let targetURL = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".zcompdump")
|
|
let finding = Finding(
|
|
id: UUID(),
|
|
title: "Zsh completion cache",
|
|
detail: targetURL.path,
|
|
bytes: 1,
|
|
risk: .safe,
|
|
category: "Developer tools",
|
|
targetPaths: [targetURL.path]
|
|
)
|
|
|
|
XCTAssertTrue(AtlasSmartCleanExecutionSupport.isSupportedExecutionTarget(targetURL))
|
|
XCTAssertTrue(AtlasSmartCleanExecutionSupport.isFindingExecutionSupported(finding))
|
|
}
|
|
|
|
func testPnpmStoreTargetIsSupportedExecutionTarget() {
|
|
let targetURL = FileManager.default.homeDirectoryForCurrentUser
|
|
.appendingPathComponent("Library/pnpm/store/v3/files/atlas-fixture/package.tgz")
|
|
let finding = Finding(
|
|
id: UUID(),
|
|
title: "pnpm store",
|
|
detail: targetURL.path,
|
|
bytes: 1,
|
|
risk: .safe,
|
|
category: "Developer tools",
|
|
targetPaths: [targetURL.path]
|
|
)
|
|
|
|
XCTAssertTrue(AtlasSmartCleanExecutionSupport.isSupportedExecutionTarget(targetURL))
|
|
XCTAssertTrue(AtlasSmartCleanExecutionSupport.isFindingExecutionSupported(finding))
|
|
}
|
|
|
|
func testExecutePlanTrashesRealTargetsWhenAvailable() async throws {
|
|
let repository = AtlasWorkspaceRepository(stateFileURL: temporaryStateFileURL())
|
|
let home = FileManager.default.homeDirectoryForCurrentUser
|
|
let targetDirectory = home.appendingPathComponent("Library/Caches/AtlasExecutionTests/" + UUID().uuidString, isDirectory: true)
|
|
try FileManager.default.createDirectory(at: targetDirectory, withIntermediateDirectories: true)
|
|
let targetFile = targetDirectory.appendingPathComponent("sample.cache")
|
|
try Data("cache".utf8).write(to: targetFile)
|
|
|
|
let finding = Finding(
|
|
id: UUID(),
|
|
title: "Sample cache",
|
|
detail: targetFile.path,
|
|
bytes: 5,
|
|
risk: .safe,
|
|
category: "Developer tools",
|
|
targetPaths: [targetFile.path]
|
|
)
|
|
let state = AtlasWorkspaceState(
|
|
snapshot: AtlasWorkspaceSnapshot(
|
|
reclaimableSpaceBytes: 5,
|
|
findings: [finding],
|
|
apps: [],
|
|
taskRuns: [],
|
|
recoveryItems: [],
|
|
permissions: [],
|
|
healthSnapshot: nil
|
|
),
|
|
currentPlan: ActionPlan(title: "Review 1 selected finding", items: [ActionItem(id: finding.id, title: "Move Sample cache to Trash", detail: finding.detail, kind: .removeCache, recoverable: true)], estimatedBytes: 5),
|
|
settings: AtlasScaffoldWorkspace.state().settings
|
|
)
|
|
_ = try repository.saveState(state)
|
|
|
|
let worker = AtlasScaffoldWorkerService(repository: repository, allowStateOnlyCleanExecution: false)
|
|
let result = try await worker.submit(AtlasRequestEnvelope(command: .executePlan(planID: state.currentPlan.id)))
|
|
|
|
if case let .accepted(task) = result.response.response {
|
|
XCTAssertEqual(task.kind, .executePlan)
|
|
} else {
|
|
XCTFail("Expected accepted execute-plan response")
|
|
}
|
|
XCTAssertFalse(FileManager.default.fileExists(atPath: targetFile.path))
|
|
XCTAssertEqual(result.snapshot.findings.count, 0)
|
|
}
|
|
|
|
func testExecutePlanUsesStructuredTargetPathsCarriedByCurrentPlan() async throws {
|
|
let repository = AtlasWorkspaceRepository(stateFileURL: temporaryStateFileURL())
|
|
let home = FileManager.default.homeDirectoryForCurrentUser
|
|
let targetDirectory = home.appendingPathComponent("Library/Caches/AtlasExecutionTests/" + UUID().uuidString, isDirectory: true)
|
|
try FileManager.default.createDirectory(at: targetDirectory, withIntermediateDirectories: true)
|
|
let targetFile = targetDirectory.appendingPathComponent("plan-target.cache")
|
|
try Data("cache".utf8).write(to: targetFile)
|
|
|
|
let finding = Finding(
|
|
id: UUID(),
|
|
title: "Plan-backed cache",
|
|
detail: targetFile.path,
|
|
bytes: 5,
|
|
risk: .safe,
|
|
category: "Developer tools",
|
|
targetPaths: nil
|
|
)
|
|
let state = AtlasWorkspaceState(
|
|
snapshot: AtlasWorkspaceSnapshot(
|
|
reclaimableSpaceBytes: 5,
|
|
findings: [finding],
|
|
apps: [],
|
|
taskRuns: [],
|
|
recoveryItems: [],
|
|
permissions: [],
|
|
healthSnapshot: nil
|
|
),
|
|
currentPlan: ActionPlan(
|
|
title: "Review 1 selected finding",
|
|
items: [
|
|
ActionItem(
|
|
id: finding.id,
|
|
title: "Move Plan-backed cache to Trash",
|
|
detail: finding.detail,
|
|
kind: .removeCache,
|
|
recoverable: true,
|
|
targetPaths: [targetFile.path]
|
|
)
|
|
],
|
|
estimatedBytes: 5
|
|
),
|
|
settings: AtlasScaffoldWorkspace.state().settings
|
|
)
|
|
_ = try repository.saveState(state)
|
|
|
|
let worker = AtlasScaffoldWorkerService(repository: repository, allowStateOnlyCleanExecution: false)
|
|
let result = try await worker.submit(AtlasRequestEnvelope(command: .executePlan(planID: state.currentPlan.id)))
|
|
|
|
if case let .accepted(task) = result.response.response {
|
|
XCTAssertEqual(task.kind, .executePlan)
|
|
} else {
|
|
XCTFail("Expected accepted execute-plan response")
|
|
}
|
|
XCTAssertFalse(FileManager.default.fileExists(atPath: targetFile.path))
|
|
XCTAssertEqual(result.snapshot.findings.count, 0)
|
|
XCTAssertEqual(result.snapshot.recoveryItems.first?.originalPath, targetFile.path)
|
|
}
|
|
|
|
func testScanExecuteRescanRemovesExecutedTargetFromRealResults() async throws {
|
|
let repository = AtlasWorkspaceRepository(stateFileURL: temporaryStateFileURL())
|
|
let home = FileManager.default.homeDirectoryForCurrentUser
|
|
let targetDirectory = home.appendingPathComponent("Library/Caches/AtlasExecutionTests/" + UUID().uuidString, isDirectory: true)
|
|
try FileManager.default.createDirectory(at: targetDirectory, withIntermediateDirectories: true)
|
|
let targetFile = targetDirectory.appendingPathComponent("sample.cache")
|
|
try Data("cache".utf8).write(to: targetFile)
|
|
|
|
let provider = FileBackedSmartCleanProvider(targetFileURL: targetFile)
|
|
let worker = AtlasScaffoldWorkerService(
|
|
repository: repository,
|
|
smartCleanScanProvider: provider,
|
|
allowProviderFailureFallback: false,
|
|
allowStateOnlyCleanExecution: false
|
|
)
|
|
|
|
let firstScan = try await worker.submit(AtlasRequestEnvelope(command: .startScan(taskID: UUID())))
|
|
XCTAssertEqual(firstScan.snapshot.findings.count, 1)
|
|
let planID = try XCTUnwrap(firstScan.previewPlan?.id)
|
|
|
|
let execute = try await worker.submit(AtlasRequestEnvelope(command: .executePlan(planID: planID)))
|
|
if case let .accepted(task) = execute.response.response {
|
|
XCTAssertEqual(task.kind, .executePlan)
|
|
} else {
|
|
XCTFail("Expected accepted execute-plan response")
|
|
}
|
|
XCTAssertFalse(FileManager.default.fileExists(atPath: targetFile.path))
|
|
|
|
let secondScan = try await worker.submit(AtlasRequestEnvelope(command: .startScan(taskID: UUID())))
|
|
XCTAssertEqual(secondScan.snapshot.findings.count, 0)
|
|
XCTAssertEqual(secondScan.snapshot.reclaimableSpaceBytes, 0)
|
|
}
|
|
|
|
func testScanExecuteRescanRemovesExecutedPnpmStoreTargetFromRealResults() async throws {
|
|
let repository = AtlasWorkspaceRepository(stateFileURL: temporaryStateFileURL())
|
|
let home = FileManager.default.homeDirectoryForCurrentUser
|
|
let targetDirectory = home.appendingPathComponent("Library/pnpm/store/v3/files/AtlasExecutionTests/" + UUID().uuidString, isDirectory: true)
|
|
try FileManager.default.createDirectory(at: targetDirectory, withIntermediateDirectories: true)
|
|
let targetFile = targetDirectory.appendingPathComponent("package.tgz")
|
|
try Data("pnpm-cache".utf8).write(to: targetFile)
|
|
|
|
let provider = FileBackedSmartCleanProvider(targetFileURL: targetFile, title: "pnpm store")
|
|
let worker = AtlasScaffoldWorkerService(
|
|
repository: repository,
|
|
smartCleanScanProvider: provider,
|
|
allowProviderFailureFallback: false,
|
|
allowStateOnlyCleanExecution: false
|
|
)
|
|
|
|
let firstScan = try await worker.submit(AtlasRequestEnvelope(command: .startScan(taskID: UUID())))
|
|
XCTAssertEqual(firstScan.snapshot.findings.count, 1)
|
|
let planID = try XCTUnwrap(firstScan.previewPlan?.id)
|
|
let initialRecoveryCount = firstScan.snapshot.recoveryItems.count
|
|
|
|
let execute = try await worker.submit(AtlasRequestEnvelope(command: .executePlan(planID: planID)))
|
|
if case let .accepted(task) = execute.response.response {
|
|
XCTAssertEqual(task.kind, .executePlan)
|
|
} else {
|
|
XCTFail("Expected accepted execute-plan response")
|
|
}
|
|
XCTAssertFalse(FileManager.default.fileExists(atPath: targetFile.path))
|
|
XCTAssertEqual(execute.snapshot.recoveryItems.count, initialRecoveryCount + 1)
|
|
XCTAssertTrue(execute.snapshot.recoveryItems.contains(where: { $0.title == "pnpm store" }))
|
|
|
|
let secondScan = try await worker.submit(AtlasRequestEnvelope(command: .startScan(taskID: UUID())))
|
|
XCTAssertEqual(secondScan.snapshot.findings.count, 0)
|
|
XCTAssertEqual(secondScan.snapshot.reclaimableSpaceBytes, 0)
|
|
}
|
|
|
|
func testExecutePlanDoesNotCreateRecoveryEntryWhenTargetIsAlreadyGone() async throws {
|
|
let repository = AtlasWorkspaceRepository(stateFileURL: temporaryStateFileURL())
|
|
let home = FileManager.default.homeDirectoryForCurrentUser
|
|
let targetDirectory = home.appendingPathComponent("Library/Caches/AtlasExecutionTests/" + UUID().uuidString, isDirectory: true)
|
|
try FileManager.default.createDirectory(at: targetDirectory, withIntermediateDirectories: true)
|
|
let targetFile = targetDirectory.appendingPathComponent("sample.cache")
|
|
|
|
let finding = Finding(
|
|
id: UUID(),
|
|
title: "Stale cache",
|
|
detail: targetFile.path,
|
|
bytes: 5,
|
|
risk: .safe,
|
|
category: "Developer tools",
|
|
targetPaths: [targetFile.path]
|
|
)
|
|
let state = AtlasWorkspaceState(
|
|
snapshot: AtlasWorkspaceSnapshot(
|
|
reclaimableSpaceBytes: 5,
|
|
findings: [finding],
|
|
apps: [],
|
|
taskRuns: [],
|
|
recoveryItems: [],
|
|
permissions: [],
|
|
healthSnapshot: nil
|
|
),
|
|
currentPlan: ActionPlan(title: "Review 1 selected finding", items: [ActionItem(id: finding.id, title: "Move Stale cache to Trash", detail: finding.detail, kind: .removeCache, recoverable: true)], estimatedBytes: 5),
|
|
settings: AtlasScaffoldWorkspace.state().settings
|
|
)
|
|
_ = try repository.saveState(state)
|
|
|
|
let worker = AtlasScaffoldWorkerService(repository: repository, allowStateOnlyCleanExecution: false)
|
|
let result = try await worker.submit(AtlasRequestEnvelope(command: .executePlan(planID: state.currentPlan.id)))
|
|
|
|
if case let .accepted(task) = result.response.response {
|
|
XCTAssertEqual(task.kind, .executePlan)
|
|
} else {
|
|
XCTFail("Expected accepted execute-plan response")
|
|
}
|
|
XCTAssertEqual(result.snapshot.recoveryItems.count, 0)
|
|
XCTAssertEqual(result.snapshot.findings.count, 0)
|
|
XCTAssertEqual(
|
|
result.snapshot.taskRuns.first?.summary,
|
|
AtlasL10n.string("infrastructure.execute.summary.clean.stale.one", language: state.settings.language)
|
|
)
|
|
}
|
|
|
|
func testRestoreRecoveryItemPhysicallyRestoresRealTargets() async throws {
|
|
let repository = AtlasWorkspaceRepository(stateFileURL: temporaryStateFileURL())
|
|
let home = FileManager.default.homeDirectoryForCurrentUser
|
|
let targetDirectory = home.appendingPathComponent("Library/Caches/AtlasExecutionTests/" + UUID().uuidString, isDirectory: true)
|
|
try FileManager.default.createDirectory(at: targetDirectory, withIntermediateDirectories: true)
|
|
let targetFile = targetDirectory.appendingPathComponent("sample.cache")
|
|
try Data("cache".utf8).write(to: targetFile)
|
|
|
|
let finding = Finding(
|
|
id: UUID(),
|
|
title: "Sample cache",
|
|
detail: targetFile.path,
|
|
bytes: 5,
|
|
risk: .safe,
|
|
category: "Developer tools",
|
|
targetPaths: [targetFile.path]
|
|
)
|
|
let state = AtlasWorkspaceState(
|
|
snapshot: AtlasWorkspaceSnapshot(
|
|
reclaimableSpaceBytes: 5,
|
|
findings: [finding],
|
|
apps: [],
|
|
taskRuns: [],
|
|
recoveryItems: [],
|
|
permissions: [],
|
|
healthSnapshot: nil
|
|
),
|
|
currentPlan: ActionPlan(title: "Review 1 selected finding", items: [ActionItem(id: finding.id, title: "Move Sample cache to Trash", detail: finding.detail, kind: .removeCache, recoverable: true)], estimatedBytes: 5),
|
|
settings: AtlasScaffoldWorkspace.state().settings
|
|
)
|
|
_ = try repository.saveState(state)
|
|
|
|
let worker = AtlasScaffoldWorkerService(repository: repository, allowStateOnlyCleanExecution: false)
|
|
let execute = try await worker.submit(AtlasRequestEnvelope(command: .executePlan(planID: state.currentPlan.id)))
|
|
let recoveryItemID = try XCTUnwrap(execute.snapshot.recoveryItems.first?.id)
|
|
XCTAssertFalse(FileManager.default.fileExists(atPath: targetFile.path))
|
|
|
|
let restore = try await worker.submit(AtlasRequestEnvelope(command: .restoreItems(taskID: UUID(), itemIDs: [recoveryItemID])))
|
|
|
|
if case let .accepted(task) = restore.response.response {
|
|
XCTAssertEqual(task.kind, .restore)
|
|
} else {
|
|
XCTFail("Expected accepted restore response")
|
|
}
|
|
XCTAssertTrue(FileManager.default.fileExists(atPath: targetFile.path))
|
|
XCTAssertEqual(
|
|
restore.snapshot.taskRuns.first?.summary,
|
|
AtlasL10n.string("infrastructure.restore.summary.disk.one", language: state.settings.language)
|
|
)
|
|
}
|
|
|
|
func testRestoreItemsStateOnlySummaryDoesNotClaimOnDiskRestore() async throws {
|
|
let repository = AtlasWorkspaceRepository(stateFileURL: temporaryStateFileURL())
|
|
let finding = Finding(
|
|
id: UUID(),
|
|
title: "Atlas-only fixture",
|
|
detail: "State-only recovery item",
|
|
bytes: 5,
|
|
risk: .safe,
|
|
category: "Developer tools"
|
|
)
|
|
let recoveryItem = RecoveryItem(
|
|
id: UUID(),
|
|
title: finding.title,
|
|
detail: finding.detail,
|
|
originalPath: "~/Library/Caches/AtlasOnly",
|
|
bytes: 5,
|
|
deletedAt: Date(),
|
|
expiresAt: Date().addingTimeInterval(3600),
|
|
payload: .finding(finding),
|
|
restoreMappings: nil
|
|
)
|
|
let state = AtlasWorkspaceState(
|
|
snapshot: AtlasWorkspaceSnapshot(
|
|
reclaimableSpaceBytes: 0,
|
|
findings: [],
|
|
apps: [],
|
|
taskRuns: [],
|
|
recoveryItems: [recoveryItem],
|
|
permissions: [],
|
|
healthSnapshot: nil
|
|
),
|
|
currentPlan: ActionPlan(title: "Review 0 selected findings", items: [], estimatedBytes: 0),
|
|
settings: AtlasScaffoldWorkspace.state().settings
|
|
)
|
|
_ = try repository.saveState(state)
|
|
|
|
let worker = AtlasScaffoldWorkerService(repository: repository, allowStateOnlyCleanExecution: false)
|
|
let restore = try await worker.submit(AtlasRequestEnvelope(command: .restoreItems(taskID: UUID(), itemIDs: [recoveryItem.id])))
|
|
|
|
if case let .accepted(task) = restore.response.response {
|
|
XCTAssertEqual(task.kind, .restore)
|
|
} else {
|
|
XCTFail("Expected accepted restore response")
|
|
}
|
|
XCTAssertEqual(
|
|
restore.snapshot.taskRuns.first?.summary,
|
|
AtlasL10n.string("infrastructure.restore.summary.state.one", language: state.settings.language)
|
|
)
|
|
}
|
|
|
|
func testExecuteAppUninstallRemovesAppAndCreatesRecoveryEntry() async throws {
|
|
let repository = AtlasWorkspaceRepository(stateFileURL: temporaryStateFileURL())
|
|
let worker = AtlasScaffoldWorkerService(repository: repository, allowStateOnlyCleanExecution: true)
|
|
let initialState = repository.loadState()
|
|
let app = try XCTUnwrap(initialState.snapshot.apps.first)
|
|
|
|
let result = try await worker.submit(
|
|
AtlasRequestEnvelope(command: .executeAppUninstall(appID: app.id))
|
|
)
|
|
|
|
XCTAssertFalse(result.snapshot.apps.contains(where: { $0.id == app.id }))
|
|
XCTAssertTrue(result.snapshot.recoveryItems.contains(where: { $0.title == app.name }))
|
|
XCTAssertEqual(result.snapshot.taskRuns.first?.kind, .uninstallApp)
|
|
}
|
|
|
|
private func temporaryStateFileURL() -> URL {
|
|
FileManager.default.temporaryDirectory
|
|
.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
|
.appendingPathComponent("workspace-state.json")
|
|
}
|
|
|
|
}
|
|
|
|
private struct FailingSmartCleanProvider: AtlasSmartCleanScanProviding {
|
|
func collectSmartCleanScan() async throws -> AtlasSmartCleanScanResult {
|
|
struct SampleError: LocalizedError { var errorDescription: String? { "simulated scan failure" } }
|
|
throw SampleError()
|
|
}
|
|
}
|
|
|
|
private struct FileBackedSmartCleanProvider: AtlasSmartCleanScanProviding {
|
|
let targetFileURL: URL
|
|
var title: String = "Sample cache"
|
|
var category: String = "Developer tools"
|
|
|
|
func collectSmartCleanScan() async throws -> AtlasSmartCleanScanResult {
|
|
guard FileManager.default.fileExists(atPath: targetFileURL.path) else {
|
|
return AtlasSmartCleanScanResult(findings: [], summary: "No reclaimable items remain.")
|
|
}
|
|
let size = Int64((try? FileManager.default.attributesOfItem(atPath: targetFileURL.path)[.size] as? NSNumber)?.int64Value ?? 0)
|
|
let finding = Finding(
|
|
id: UUID(uuidString: "30000000-0000-0000-0000-000000000001") ?? UUID(),
|
|
title: title,
|
|
detail: targetFileURL.path,
|
|
bytes: size,
|
|
risk: .safe,
|
|
category: category,
|
|
targetPaths: [targetFileURL.path]
|
|
)
|
|
return AtlasSmartCleanScanResult(findings: [finding], summary: "Found 1 reclaimable item.")
|
|
}
|
|
}
|