// // CameraTransferPipelineTests.swift // suixinkanTests // // Created by Codex on 2026/6/29. // import XCTest @testable import suixinkan @MainActor /// 相机传输管道测试,使用 Mock 相机与上传 Sink。 final class CameraTransferPipelineTests: XCTestCase { /// 测试新照片下载后触发上传 Sink。 func testNewAssetTriggersUploadSink() async { let camera = MockCameraService() let sink = MockUploadSink() let pipeline = CameraTransferPipeline(cameraService: camera) pipeline.configure(uploadSink: sink, uploadEnabled: true) let asset = CameraAsset(id: "ptp_1", filename: "DSC_001.JPG", fileSize: 1024) let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("DSC_001.JPG") try? Data(repeating: 0xFF, count: 8).write(to: tempURL) camera.onNewAsset?(asset) try? await Task.sleep(nanoseconds: 300_000_000) XCTAssertEqual(sink.uploadCount, 1) try? FileManager.default.removeItem(at: tempURL) } } @MainActor private final class MockCameraService: CameraServiceProtocol { let brand: CameraBrand = .sony var connectionState: CameraConnectionState = .connected(deviceName: "Mock") var onConnectionStateChange: ((CameraConnectionState) -> Void)? var onNewAsset: ((CameraAsset) -> Void)? func connect() async {} func disconnect() async {} func listAssets() async throws -> [CameraAsset] { [] } func downloadAsset(_ asset: CameraAsset) async throws -> URL { let url = FileManager.default.temporaryDirectory.appendingPathComponent(asset.filename) if !FileManager.default.fileExists(atPath: url.path) { try Data(repeating: 0xFF, count: 8).write(to: url) } return url } } @MainActor private final class MockUploadSink: CameraAssetUploadSink { var uploadCount = 0 func upload( localURL: URL, fileName: String, fileType: Int, progress: @escaping @Sendable (Int) -> Void ) async throws -> String { uploadCount += 1 progress(100) return "https://cdn/mock/\(fileName)" } }