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
@@ -77,4 +77,46 @@ final class RemoteLibraryDTOTests: XCTestCase {
XCTAssertEqual(decoded.tracks.first?.durationSeconds, 245)
XCTAssertEqual(decoded.tracks.first?.artwork?.artworkId, "artwork-789")
}
func testSyncChangesResponseDecodesTrackPayloadAndCursor() throws {
let data = Data(
"""
{
"nextCursor": "12",
"hasMore": false,
"requiresBootstrap": false,
"events": [
{
"cursor": "12",
"entityType": "TRACK",
"entityId": "track-123",
"action": "UPDATED",
"track": {
"trackId": "track-123",
"title": "Remote Title",
"artist": "Remote Artist",
"durationSeconds": 245,
"sha256": "\(String(repeating: "a", count: 64))",
"assetId": "asset-456",
"createdAt": "2026-06-15T12:00:00.000Z",
"updatedAt": "2026-06-15T12:05:00.000Z",
"artwork": null
},
"deletedTrackId": null,
"createdAt": "2026-06-15T12:05:00.000Z"
}
],
"serverTime": "2026-06-15T12:05:00.000Z"
}
""".utf8
)
let decoded = try JSONDecoder().decode(SyncChangesResponse.self, from: data)
XCTAssertEqual(decoded.nextCursor.value, "12")
XCTAssertFalse(decoded.hasMore)
XCTAssertFalse(decoded.requiresBootstrap)
XCTAssertEqual(decoded.events.first?.cursor.value, "12")
XCTAssertEqual(decoded.events.first?.track?.trackId, "track-123")
}
}
@@ -44,6 +44,52 @@ final class URLSessionVelodyAPIClientAuthorizationTests: XCTestCase {
XCTAssertEqual(response.tracks.count, 0)
}
func testFetchSyncChangesSendsAuthorizationHeaderAndCursorQuery() async throws {
RecordingURLProtocol.handler = { request in
XCTAssertEqual(
request.value(forHTTPHeaderField: "Authorization"),
"Bearer test-device-access-token"
)
XCTAssertEqual(request.url?.path, "/api/v1/sync/changes")
XCTAssertEqual(request.url?.query, "cursor=12")
return (
HTTPURLResponse(
url: try XCTUnwrap(request.url),
statusCode: 200,
httpVersion: nil,
headerFields: ["Content-Type": "application/json"]
)!,
Data(
"""
{
"nextCursor": "12",
"hasMore": false,
"requiresBootstrap": false,
"events": [],
"serverTime": "2026-06-15T12:00:00.000Z"
}
""".utf8
)
)
}
let client = URLSessionVelodyAPIClient(
environment: ServerEnvironment(
baseURL: URL(string: "http://127.0.0.1:3007")!,
appVersion: "Tests"
),
session: makeSession(),
deviceAccessTokenProvider: {
"test-device-access-token"
}
)
let response = try await client.fetchSyncChanges(cursor: SyncCursor(value: "12"))
XCTAssertEqual(response.nextCursor.value, "12")
XCTAssertEqual(response.events.count, 0)
}
func testRegisterDeviceDoesNotSendAuthorizationHeader() async throws {
RecordingURLProtocol.handler = { request in
XCTAssertNil(request.value(forHTTPHeaderField: "Authorization"))