Implement incremental sync and offline recovery

This commit is contained in:
diyaa
2026-06-15 22:31:23 +02:00
parent fa7727d572
commit 295c6c1d9b
25 changed files with 2293 additions and 494 deletions
@@ -4,6 +4,7 @@ import VelodyDomain
import VelodyNetworking
import VelodyPlayback
import VelodyPersistence
import VelodyUtilities
@testable import VelodyiPhone
@MainActor
@@ -449,9 +450,35 @@ final class iPhoneLibraryViewModelPolishTests: XCTestCase {
let remoteTrack = try XCTUnwrap(remoteRow(in: viewModel, trackID: track.trackId))
XCTAssertEqual(remoteTrack.status, .missing)
XCTAssertEqual(remoteTrack.statusBadgeTitle, "Missing")
XCTAssertTrue(remoteTrack.canDownload)
XCTAssertEqual(remoteTrack.downloadButtonTitle, "Re-download")
XCTAssertFalse(remoteTrack.canPlay)
XCTAssertTrue(viewModel.availableOfflineTracks.isEmpty)
XCTAssertEqual(viewModel.availableOfflineSectionTitle, "Available Offline (0)")
}
func testDownloadedTrackAppearsInAvailableOfflineState() async throws {
let track = makeRemoteTrack(
trackId: "remote-recovered-download",
assetId: "asset-recovered-download",
title: "Recovered Download"
)
let viewModel = makeViewModel(
remoteTracks: [track],
downloadStates: [makeDownloadedState(for: track)],
audioFiles: [localFilePath(for: track): Data([0x1, 0x2, 0x3])]
)
await viewModel.loadIfNeeded()
let remoteTrack = try XCTUnwrap(remoteRow(in: viewModel, trackID: track.trackId))
let offlineTrack = try XCTUnwrap(offlineRow(in: viewModel, trackID: track.trackId))
XCTAssertEqual(remoteTrack.status, .downloaded)
XCTAssertEqual(remoteTrack.statusBadgeTitle, "Downloaded")
XCTAssertEqual(offlineTrack.statusBadgeTitle, "Downloaded")
XCTAssertEqual(viewModel.availableOfflineSectionTitle, "Available Offline (1)")
}
}
@MainActor
@@ -479,7 +506,8 @@ final class iPhoneLibraryViewModelDeviceAuthTests: XCTestCase {
forKey: "velody.iphone.device-access-token"
)
XCTAssertEqual(await counter.count, 1)
let registerCount = await counter.currentCount()
XCTAssertEqual(registerCount, 1)
XCTAssertFalse((storedDeviceID ?? "").isEmpty)
XCTAssertFalse((storedDeviceAccessToken ?? "").isEmpty)
}
@@ -513,7 +541,8 @@ final class iPhoneLibraryViewModelDeviceAuthTests: XCTestCase {
forKey: "velody.iphone.device-access-token"
)
XCTAssertEqual(await counter.count, 1)
let registerCount = await counter.currentCount()
XCTAssertEqual(registerCount, 1)
XCTAssertNotEqual(storedDeviceID, legacyDeviceID)
XCTAssertFalse((storedDeviceAccessToken ?? "").isEmpty)
}
@@ -525,6 +554,10 @@ private actor RegisterCallCounter {
func increment() {
count += 1
}
func currentCount() -> Int {
count
}
}
private struct TestRegisterAPIClient: VelodyAPIClient {
@@ -563,6 +596,12 @@ private struct TestRegisterAPIClient: VelodyAPIClient {
try await stubClient.fetchSyncBootstrap()
}
func fetchSyncChanges(
cursor: SyncCursor
) async throws -> SyncChangesResponse {
try await stubClient.fetchSyncChanges(cursor: cursor)
}
func fetchRemoteLibrary(
deviceId: String
) async throws -> RemoteLibraryResponseDTO {