Add offline resilience coverage
This commit is contained in:
@@ -290,6 +290,86 @@ final class RemoteLibrarySyncServiceTests: XCTestCase {
|
||||
XCTAssertEqual(storedCursor, SyncCursor(value: "7"))
|
||||
}
|
||||
|
||||
func testPersistedCursorSurvivesRelaunchFailureAndLaterRecovery() async throws {
|
||||
let fileManager = FileManager.default
|
||||
let tempDirectory = fileManager.temporaryDirectory.appendingPathComponent(
|
||||
UUID().uuidString,
|
||||
isDirectory: true
|
||||
)
|
||||
let remoteLibraryFileURL = tempDirectory.appendingPathComponent("remote-library.json")
|
||||
let syncCursorFileURL = tempDirectory.appendingPathComponent("remote-library-sync-cursor.json")
|
||||
let bootstrapTrack = makeRemoteTrack(trackId: "track-cursor-persist")
|
||||
let updatedTrack = makeRemoteTrack(
|
||||
trackId: bootstrapTrack.trackId,
|
||||
title: "Track Cursor Updated"
|
||||
)
|
||||
|
||||
defer {
|
||||
try? fileManager.removeItem(at: tempDirectory)
|
||||
}
|
||||
|
||||
let firstService = makeSyncService(
|
||||
apiClient: MockVelodyAPIClient(
|
||||
bootstrapResponse: SyncBootstrapResponse(
|
||||
nextCursor: SyncCursor(value: "1"),
|
||||
tracks: [bootstrapTrack],
|
||||
serverTime: "2026-06-15T12:00:00.000Z"
|
||||
)
|
||||
),
|
||||
store: try FileRemoteLibraryStore(fileURL: remoteLibraryFileURL),
|
||||
cursorStore: try FileRemoteLibrarySyncCursorStore(fileURL: syncCursorFileURL)
|
||||
)
|
||||
|
||||
_ = try await firstService.syncRemoteLibrary(deviceId: "device-123")
|
||||
|
||||
let offlineService = makeSyncService(
|
||||
apiClient: MockVelodyAPIClient(
|
||||
changeError: VelodyAPIError.requestFailed("Offline")
|
||||
),
|
||||
store: try FileRemoteLibraryStore(fileURL: remoteLibraryFileURL),
|
||||
cursorStore: try FileRemoteLibrarySyncCursorStore(fileURL: syncCursorFileURL)
|
||||
)
|
||||
|
||||
await XCTAssertThrowsErrorAsync {
|
||||
_ = try await offlineService.syncRemoteLibrary(deviceId: "device-123")
|
||||
}
|
||||
|
||||
let recoveryClient = MockVelodyAPIClient(
|
||||
changeResponses: [
|
||||
SyncChangesResponse(
|
||||
nextCursor: SyncCursor(value: "2"),
|
||||
hasMore: false,
|
||||
requiresBootstrap: false,
|
||||
events: [
|
||||
SyncEvent(
|
||||
cursor: SyncCursor(value: "2"),
|
||||
entityType: "TRACK",
|
||||
entityId: updatedTrack.trackId,
|
||||
action: "UPDATED",
|
||||
track: updatedTrack,
|
||||
createdAt: "2026-06-15T12:05:00.000Z"
|
||||
),
|
||||
],
|
||||
serverTime: "2026-06-15T12:05:00.000Z"
|
||||
),
|
||||
]
|
||||
)
|
||||
let recoveredService = makeSyncService(
|
||||
apiClient: recoveryClient,
|
||||
store: try FileRemoteLibraryStore(fileURL: remoteLibraryFileURL),
|
||||
cursorStore: try FileRemoteLibrarySyncCursorStore(fileURL: syncCursorFileURL)
|
||||
)
|
||||
|
||||
let recoveredTracks = try await recoveredService.syncRemoteLibrary(deviceId: "device-123")
|
||||
let recoveredCursor = try await FileRemoteLibrarySyncCursorStore(fileURL: syncCursorFileURL)
|
||||
.loadCursor()
|
||||
|
||||
XCTAssertEqual(recoveredTracks, [updatedTrack])
|
||||
XCTAssertEqual(recoveredCursor, SyncCursor(value: "2"))
|
||||
let recordedChangeCursors = await recoveryClient.recordedChangeCursors()
|
||||
XCTAssertEqual(recordedChangeCursors, ["1"])
|
||||
}
|
||||
|
||||
func testSyncFailurePreservesDownloadedStateAndLocalFile() async throws {
|
||||
let track = makeRemoteTrack(trackId: "track-offline", assetId: "asset-offline")
|
||||
let localFilePath = "/in-memory/\(track.assetId).mp3"
|
||||
|
||||
Reference in New Issue
Block a user