为旅拍 OTG 上传增加 client_photo_id,并按服务端已登记 ID 同步本地上传状态。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-16 19:13:19 +08:00
parent 3d5ad8a614
commit b019e1e494
11 changed files with 323 additions and 115 deletions

View File

@ -483,6 +483,37 @@ final class WiredCameraTransferViewModelTests: XCTestCase {
XCTAssertEqual(Set(result.importedPhotoIds), Set(records.map(\.id)))
XCTAssertEqual(Set(records.map(\.status)), [.uploaded])
XCTAssertEqual(Set(records.map(\.fileName)).count, 2)
XCTAssertEqual(Set(records.map(\.clientPhotoId)).count, 2)
XCTAssertTrue(records.allSatisfy { UUID(uuidString: $0.clientPhotoId) != nil })
}
func testStartSynchronizesPersistedUploadStatusesFromServerClientPhotoIds() async {
let context = makeOTGTestContext()
let api = TravelAlbumMockAPI()
api.materialClientPhotoIdsResponse = TravelAlbumMaterialClientPhotoIDsResponse(
clientPhotoIds: ["server-id"]
)
let records = [
makeOTGRecord(id: "matched", capturedAt: "2026-07-16 12:00:00", status: .failed)
.withClientPhotoId("server-id"),
makeOTGRecord(id: "missing", capturedAt: "2026-07-16 12:01:00", status: .uploaded)
.withClientPhotoId("missing-id"),
]
context.store.save(records, albumId: 9)
let viewModel = makeWiredViewModel(
context: context,
manager: MockWiredCameraConnectionManager(driver: MockCameraDriver(objects: [])),
api: api
)
viewModel.start()
await waitUntil {
let byId = Dictionary(uniqueKeysWithValues: context.store.load(albumId: 9).map { ($0.id, $0) })
return byId["matched"]?.status == .uploaded && byId["missing"]?.status == .pending
}
viewModel.stop()
XCTAssertEqual(api.materialClientPhotoIdsCallCount, 1)
}
}
@ -683,6 +714,7 @@ private func makeWiredViewModel(
context: OTGTestContext,
manager: MockWiredCameraConnectionManager,
uploader: MockTravelAlbumOTGUploader? = nil,
api: TravelAlbumMockAPI? = nil,
userDefaults: UserDefaults = makeOTGTestDefaults()
) -> WiredCameraTransferViewModel {
prepareAppStoreForOTGTests()
@ -693,10 +725,19 @@ private func makeWiredViewModel(
connectionManager: manager,
storage: context.store,
uploader: uploader ?? MockTravelAlbumOTGUploader(),
api: api ?? TravelAlbumMockAPI(),
userDefaults: userDefaults
)
}
private extension TravelAlbumOTGPhotoRecord {
func withClientPhotoId(_ value: String) -> TravelAlbumOTGPhotoRecord {
var copy = self
copy.clientPhotoId = value
return copy
}
}
private func makeOTGPhotoItem(
id: String,
capturedAt: String,
@ -914,6 +955,7 @@ final class TravelAlbumMockAPI: TravelAlbumServing {
var infoResponse = TravelAlbum()
var materialListResponses: [TravelAlbumListResponse<TravelAlbumMaterial>] = []
var uploadMaterialResponse = TravelAlbumMaterial()
var materialClientPhotoIdsResponse = TravelAlbumMaterialClientPhotoIDsResponse(clientPhotoIds: [])
var mpCodeResponse = TravelAlbumMpCodeResponse(mpCodeOssUrl: "")
var createError: Error?
@ -921,6 +963,7 @@ final class TravelAlbumMockAPI: TravelAlbumServing {
private(set) var createRequests: [TravelAlbumCreateRequest] = []
private(set) var materialRequests: [MaterialRequest] = []
private(set) var uploadMaterialRequests: [TravelAlbumUploadMaterialRequest] = []
private(set) var materialClientPhotoIdsCallCount = 0
private(set) var deletedAlbumIds: [Int] = []
private(set) var deletedMaterialIds: [Int] = []
@ -968,6 +1011,11 @@ final class TravelAlbumMockAPI: TravelAlbumServing {
return uploadMaterialResponse
}
func materialClientPhotoIds(userEquityTravelId: Int) async throws -> TravelAlbumMaterialClientPhotoIDsResponse {
materialClientPhotoIdsCallCount += 1
return materialClientPhotoIdsResponse
}
func deleteAlbum(id: Int) async throws {
deletedAlbumIds.append(id)
}