按账号与相册隔离相机下载目录,并同步更新传输管道与测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-30 15:55:54 +08:00
parent 660852ffad
commit 5f7ef24683
14 changed files with 382 additions and 163 deletions

View File

@ -16,7 +16,9 @@ final class CameraTransferPipelineTests: XCTestCase {
let camera = MockCameraService()
let sink = MockUploadSink()
let pipeline = CameraTransferPipeline(cameraService: camera)
pipeline.configure(uploadSink: sink, uploadEnabled: true)
let scope = makeScope("new_asset")
defer { CameraDownloadStorage.removeScopeDirectory(for: scope) }
pipeline.configure(uploadSink: sink, uploadEnabled: true, downloadScope: scope)
var latestTasks: [CameraTransferTask] = []
pipeline.onTasksUpdated = { latestTasks = $0 }
@ -30,7 +32,10 @@ final class CameraTransferPipelineTests: XCTestCase {
XCTAssertEqual(sink.uploadCount, 1)
let savedTask = latestTasks.first { $0.assetID == asset.id }
XCTAssertEqual(savedTask?.localPath?.hasPrefix("CameraDownloads/"), true)
XCTAssertEqual(
savedTask?.localPath?.hasPrefix("CameraDownloads/test_account/\(scope.albumID)/originals/"),
true
)
XCTAssertEqual(savedTask?.localPath?.hasPrefix("/"), false)
if let localURL = savedTask?.localURL {
try? FileManager.default.removeItem(at: localURL)
@ -43,9 +48,11 @@ final class CameraTransferPipelineTests: XCTestCase {
let camera = MockCameraService()
let sink = MockUploadSink()
let pipeline = CameraTransferPipeline(cameraService: camera)
pipeline.configure(uploadSink: sink, uploadEnabled: false)
let scope = makeScope("retry")
defer { CameraDownloadStorage.removeScopeDirectory(for: scope) }
pipeline.configure(uploadSink: sink, uploadEnabled: false, downloadScope: scope)
let fileURL = CameraDownloadStorage.uniqueLocalURL(for: "retry_pipeline.JPG")
let fileURL = CameraDownloadStorage.uniqueLocalURL(for: "retry_pipeline.JPG", scope: scope)
try Data(repeating: 0xAB, count: 16).write(to: fileURL)
defer { try? FileManager.default.removeItem(at: fileURL) }
@ -65,7 +72,9 @@ final class CameraTransferPipelineTests: XCTestCase {
let camera = MockCameraService()
let sink = MockSlowUploadSink()
let pipeline = CameraTransferPipeline(cameraService: camera)
pipeline.configure(uploadSink: sink, uploadEnabled: true)
let scope = makeScope("manual")
defer { CameraDownloadStorage.removeScopeDirectory(for: scope) }
pipeline.configure(uploadSink: sink, uploadEnabled: true, downloadScope: scope)
camera.listedAssets = (1 ... 6).map { index in
CameraAsset(id: "manual_\(index)", filename: "MANUAL_\(index).JPG", fileSize: 1024)
}
@ -83,7 +92,9 @@ final class CameraTransferPipelineTests: XCTestCase {
let camera = MockCameraService()
let sink = MockUploadSink()
let pipeline = CameraTransferPipeline(cameraService: camera)
pipeline.configure(uploadSink: sink, uploadEnabled: false)
let scope = makeScope("mode_switch")
defer { CameraDownloadStorage.removeScopeDirectory(for: scope) }
pipeline.configure(uploadSink: sink, uploadEnabled: false, downloadScope: scope)
camera.onNewAsset?(CameraAsset(id: "post_001", filename: "POST_001.JPG", fileSize: 1024))
try await Task.sleep(nanoseconds: 400_000_000)
@ -91,7 +102,7 @@ final class CameraTransferPipelineTests: XCTestCase {
XCTAssertEqual(sink.uploadedFileNames, [])
XCTAssertEqual(pipeline.task(forAssetID: "post_001")?.status, .downloaded)
pipeline.configure(uploadSink: sink, uploadEnabled: true)
pipeline.configure(uploadSink: sink, uploadEnabled: true, downloadScope: scope)
camera.onNewAsset?(CameraAsset(id: "live_001", filename: "LIVE_001.JPG", fileSize: 1024))
try await Task.sleep(nanoseconds: 500_000_000)
@ -105,7 +116,9 @@ final class CameraTransferPipelineTests: XCTestCase {
let camera = MockCameraService()
let sink = MockUploadSink()
let pipeline = CameraTransferPipeline(cameraService: camera)
pipeline.configure(uploadSink: sink, uploadEnabled: true)
let scope = makeScope("sync_existing")
defer { CameraDownloadStorage.removeScopeDirectory(for: scope) }
pipeline.configure(uploadSink: sink, uploadEnabled: true, downloadScope: scope)
camera.listedAssets = [
CameraAsset(id: "history_001", filename: "HISTORY_001.JPG", fileSize: 1024)
]
@ -122,7 +135,9 @@ final class CameraTransferPipelineTests: XCTestCase {
let camera = MockBurstCameraService()
let sink = MockSlowUploadSink()
let pipeline = CameraTransferPipeline(cameraService: camera)
pipeline.configure(uploadSink: sink, uploadEnabled: true)
let scope = makeScope("burst")
defer { CameraDownloadStorage.removeScopeDirectory(for: scope) }
pipeline.configure(uploadSink: sink, uploadEnabled: true, downloadScope: scope)
for index in 1 ... 10 {
let filename = "DSC_\(index).JPG"
@ -149,7 +164,9 @@ final class CameraTransferPipelineTests: XCTestCase {
let camera = MockCameraService()
let sink = MockChattyUploadSink()
let pipeline = CameraTransferPipeline(cameraService: camera)
pipeline.configure(uploadSink: sink, uploadEnabled: true)
let scope = makeScope("chatty")
defer { CameraDownloadStorage.removeScopeDirectory(for: scope) }
pipeline.configure(uploadSink: sink, uploadEnabled: true, downloadScope: scope)
var updateCount = 0
pipeline.onTasksUpdated = { _ in updateCount += 1 }
@ -160,6 +177,13 @@ final class CameraTransferPipelineTests: XCTestCase {
XCTAssertEqual(pipeline.task(forAssetID: "ptp_chatty")?.status, .uploaded)
XCTAssertLessThan(updateCount, 18)
}
private func makeScope(_ albumName: String) -> CameraDownloadStorage.Scope {
let albumID = albumName.unicodeScalars.reduce(0) { partial, scalar in
(partial + Int(scalar.value)) % 10_000
}
return CameraDownloadStorage.Scope(accountKey: "test_account", albumID: albumID)
}
}
@MainActor