还原顶栏、浮动卡片、时间侧栏与底部交互;照片路径改为 CameraDownloads 相对路径并兼容旧数据;格式 Chip 固定 JPG 且不可点击。 Co-authored-by: Cursor <cursoragent@cursor.com>
80 lines
2.6 KiB
Swift
80 lines
2.6 KiB
Swift
//
|
||
// 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)
|
||
|
||
var latestTasks: [CameraTransferTask] = []
|
||
pipeline.onTasksUpdated = { latestTasks = $0 }
|
||
|
||
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)
|
||
let savedTask = latestTasks.first { $0.assetID == asset.id }
|
||
XCTAssertEqual(savedTask?.localPath?.hasPrefix("CameraDownloads/"), true)
|
||
XCTAssertEqual(savedTask?.localPath?.hasPrefix("/"), false)
|
||
if let localURL = savedTask?.localURL {
|
||
try? FileManager.default.removeItem(at: localURL)
|
||
}
|
||
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)"
|
||
}
|
||
}
|