同步相册云盘界面并完善相关交互
This commit is contained in:
@ -77,6 +77,104 @@ final class CloudTransferManagerTests: XCTestCase {
|
||||
XCTAssertTrue(manager.downloadTasks.isEmpty)
|
||||
}
|
||||
|
||||
func testUploadPauseThenResumeCompletes() async throws {
|
||||
let uploader = PausingThenSucceedCloudUploader(url: "https://cdn/resumed.jpg")
|
||||
let api = TransferMockCloudDriveAPI()
|
||||
let manager = CloudTransferManager(
|
||||
store: MemoryTransferStore(),
|
||||
uploader: uploader,
|
||||
api: api,
|
||||
downloader: FakeDownloader(),
|
||||
photoSaver: FakePhotoSaver(),
|
||||
scenicIdProvider: { 10 }
|
||||
)
|
||||
let fileURL = try makeTempFile(name: "resume.jpg")
|
||||
|
||||
manager.addUploadTask(localFileURL: fileURL, fileName: "resume.jpg", fileType: 2, fileSize: 3, parentFolderId: 7)
|
||||
let id = try XCTUnwrap(manager.uploadTasks.first?.id)
|
||||
try await waitUntil { uploader.attempts == 1 }
|
||||
manager.pauseUpload(id: id)
|
||||
try await waitUntil { manager.uploadTasks.first?.status == .paused }
|
||||
|
||||
manager.resumeUpload(id: id)
|
||||
try await waitUntil { manager.uploadTasks.isEmpty }
|
||||
|
||||
XCTAssertEqual(uploader.attempts, 2)
|
||||
XCTAssertEqual(api.uploadCalls.first?.parentFolderId, 7)
|
||||
XCTAssertEqual(api.uploadCalls.first?.fileUrl, "https://cdn/resumed.jpg")
|
||||
}
|
||||
|
||||
func testFailedUploadCanRetry() async throws {
|
||||
let uploader = FailOnceCloudUploader(url: "https://cdn/retried.jpg")
|
||||
let api = TransferMockCloudDriveAPI()
|
||||
let manager = CloudTransferManager(
|
||||
store: MemoryTransferStore(),
|
||||
uploader: uploader,
|
||||
api: api,
|
||||
downloader: FakeDownloader(),
|
||||
photoSaver: FakePhotoSaver(),
|
||||
scenicIdProvider: { 10 }
|
||||
)
|
||||
let fileURL = try makeTempFile(name: "retry.jpg")
|
||||
|
||||
manager.addUploadTask(localFileURL: fileURL, fileName: "retry.jpg", fileType: 2, fileSize: 3, parentFolderId: 0)
|
||||
try await waitUntil { manager.uploadTasks.first?.status == .failed }
|
||||
let id = try XCTUnwrap(manager.uploadTasks.first?.id)
|
||||
|
||||
manager.resumeUpload(id: id)
|
||||
try await waitUntil { manager.uploadTasks.isEmpty }
|
||||
|
||||
XCTAssertEqual(uploader.attempts, 2)
|
||||
XCTAssertEqual(api.uploadCalls.first?.fileUrl, "https://cdn/retried.jpg")
|
||||
}
|
||||
|
||||
func testUploadCancelRemovesTaskAndStagedFile() async throws {
|
||||
let uploader = PausingThenSucceedCloudUploader(url: "unused")
|
||||
let manager = CloudTransferManager(
|
||||
store: MemoryTransferStore(),
|
||||
uploader: uploader,
|
||||
api: TransferMockCloudDriveAPI(),
|
||||
downloader: FakeDownloader(),
|
||||
photoSaver: FakePhotoSaver(),
|
||||
scenicIdProvider: { 10 }
|
||||
)
|
||||
let fileURL = try makeTempFile(name: "cancel.jpg")
|
||||
|
||||
manager.addUploadTask(localFileURL: fileURL, fileName: "cancel.jpg", fileType: 2, fileSize: 3, parentFolderId: 0)
|
||||
let id = try XCTUnwrap(manager.uploadTasks.first?.id)
|
||||
manager.cancelUpload(id: id)
|
||||
|
||||
XCTAssertTrue(manager.uploadTasks.isEmpty)
|
||||
XCTAssertFalse(FileManager.default.fileExists(atPath: fileURL.path))
|
||||
}
|
||||
|
||||
func testDownloadPauseThenResumeCompletes() async throws {
|
||||
let downloader = PausingThenSucceedDownloader()
|
||||
let saver = FakePhotoSaver()
|
||||
let manager = CloudTransferManager(
|
||||
store: MemoryTransferStore(),
|
||||
uploader: FakeCloudUploader(url: "unused"),
|
||||
api: TransferMockCloudDriveAPI(),
|
||||
downloader: downloader,
|
||||
photoSaver: saver,
|
||||
scenicIdProvider: { 10 }
|
||||
)
|
||||
|
||||
manager.addDownloadTask(fileURL: "https://cdn/resume.jpg", fileName: "resume.jpg", fileType: 2)
|
||||
let id = try XCTUnwrap(manager.downloadTasks.first?.id)
|
||||
try await waitUntil { await downloader.attemptCount() == 1 }
|
||||
manager.pauseDownload(id: id)
|
||||
try await waitUntil { manager.downloadTasks.first?.status == .paused }
|
||||
|
||||
manager.resumeDownload(id: id)
|
||||
try await waitUntil { manager.downloadTasks.first?.status == .completed }
|
||||
|
||||
let attemptCount = await downloader.attemptCount()
|
||||
XCTAssertEqual(attemptCount, 2)
|
||||
XCTAssertEqual(saver.saved.count, 1)
|
||||
XCTAssertEqual(manager.downloadTasks.first?.progress, 100)
|
||||
}
|
||||
|
||||
private func makeTempFile(name: String) throws -> URL {
|
||||
let url = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + "_" + name)
|
||||
try Data([1, 2, 3]).write(to: url)
|
||||
@ -96,6 +194,20 @@ final class CloudTransferManagerTests: XCTestCase {
|
||||
try await Task.sleep(nanoseconds: 20_000_000)
|
||||
}
|
||||
}
|
||||
|
||||
private func waitUntil(
|
||||
timeout: TimeInterval = 2,
|
||||
condition: @escaping () async -> Bool
|
||||
) async throws {
|
||||
let deadline = Date().addingTimeInterval(timeout)
|
||||
while !(await condition()) {
|
||||
if Date() > deadline {
|
||||
XCTFail("Timed out waiting for condition")
|
||||
return
|
||||
}
|
||||
try await Task.sleep(nanoseconds: 20_000_000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class MemoryTransferStore: CloudTransferTaskStoring {
|
||||
@ -130,6 +242,44 @@ private final class FakeCloudUploader: CloudDriveUploading {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class PausingThenSucceedCloudUploader: CloudDriveUploading {
|
||||
let url: String
|
||||
private(set) var attempts = 0
|
||||
|
||||
init(url: String) {
|
||||
self.url = url
|
||||
}
|
||||
|
||||
func uploadCloudDriveFile(data: Data, fileName: String, fileType: Int, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
|
||||
attempts += 1
|
||||
if attempts == 1 {
|
||||
try await Task.sleep(nanoseconds: 5_000_000_000)
|
||||
}
|
||||
onProgress(100)
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class FailOnceCloudUploader: CloudDriveUploading {
|
||||
let url: String
|
||||
private(set) var attempts = 0
|
||||
|
||||
init(url: String) {
|
||||
self.url = url
|
||||
}
|
||||
|
||||
func uploadCloudDriveFile(data: Data, fileName: String, fileType: Int, scenicId: Int, onProgress: @escaping (Int) -> Void) async throws -> String {
|
||||
attempts += 1
|
||||
if attempts == 1 {
|
||||
throw TestError.failed
|
||||
}
|
||||
onProgress(100)
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
private final class FakeDownloader: CloudFileDownloading {
|
||||
var downloadCalls: [(remoteURL: String, fileName: String)] = []
|
||||
|
||||
@ -143,6 +293,38 @@ private final class FakeDownloader: CloudFileDownloading {
|
||||
}
|
||||
}
|
||||
|
||||
private final class PausingThenSucceedDownloader: CloudFileDownloading {
|
||||
private let counter = DownloadAttemptCounter()
|
||||
|
||||
func downloadFile(remoteURL: String, fileName: String, onProgress: @escaping @Sendable (Int) -> Void) async throws -> URL {
|
||||
let attempt = await counter.next()
|
||||
if attempt == 1 {
|
||||
try await Task.sleep(nanoseconds: 5_000_000_000)
|
||||
}
|
||||
onProgress(100)
|
||||
let url = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + "_" + fileName)
|
||||
try Data([1]).write(to: url)
|
||||
return url
|
||||
}
|
||||
|
||||
func attemptCount() async -> Int {
|
||||
await counter.current()
|
||||
}
|
||||
}
|
||||
|
||||
private actor DownloadAttemptCounter {
|
||||
private var value = 0
|
||||
|
||||
func next() -> Int {
|
||||
value += 1
|
||||
return value
|
||||
}
|
||||
|
||||
func current() -> Int {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
private final class FakePhotoSaver: CloudPhotoSaving {
|
||||
var saved: [(fileURL: URL, fileType: Int)] = []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user