diff --git a/PadelClubDataTests/DataAccessSyncTests.swift b/PadelClubDataTests/DataAccessSyncTests.swift new file mode 100644 index 0000000..5d9fc02 --- /dev/null +++ b/PadelClubDataTests/DataAccessSyncTests.swift @@ -0,0 +1,109 @@ +// +// DataAccessSyncTests.swift +// PadelClubDataTests +// +// Created by Laurent Morvillier on 02/05/2025. +// + +import Testing +@testable import PadelClubData +@testable import LeStorage + +struct DataAccessSyncTests { + + let username1: String = "UserDataTests" + let password1: String = "MyPass1234--" + + let username2: String = "seconduser" + let password2: String = "MyPass1234--" + + var secondStoreCenter: StoreCenter + + init() async throws { + + self.secondStoreCenter = StoreCenter(directoryName: "storage-2") + self.secondStoreCenter.configureURLs(secureScheme: false, domain: "127.0.0.1:8000", webSockets: false) + self.secondStoreCenter.tokenKeychain = MockKeychainStore(fileName: "storage-2/token.json") + self.secondStoreCenter.deviceKeychain = MockKeychainStore(fileName: "storage-2/device.json") + try self.secondStoreCenter.deviceKeychain.add(value: UUID().uuidString) + + self.secondStoreCenter.classProject = "PadelClubData" + + let token2 = try? self.secondStoreCenter.rawTokenShouldNotBeUsed() + try await self.login(storeCenter: self.secondStoreCenter, username: self.username2, password: self.password2) + + StoreCenter.main.configureURLs(secureScheme: false, domain: "127.0.0.1:8000", webSockets: false) + StoreCenter.main.tokenKeychain = MockKeychainStore(fileName: "storage/token.json") + StoreCenter.main.deviceKeychain = MockKeychainStore(fileName: "storage/device.json") + try StoreCenter.main.deviceKeychain.add(value: UUID().uuidString) + StoreCenter.main.classProject = "PadelClubData" + + let token = try? StoreCenter.main.rawTokenShouldNotBeUsed() + try await self.login(storeCenter: StoreCenter.main, username: self.username1, password: self.password1) + } + + mutating func login(storeCenter: StoreCenter, username: String, password: String) async throws { + let _: CustomUser = try await storeCenter.service().login(username: username, password: password) + } + + @Test func testSetup() async throws { + #expect(StoreCenter.main.isAuthenticated) + #expect(self.secondStoreCenter.isAuthenticated) + + guard let userId1 = StoreCenter.main.userId else { + throw TestError.notAuthenticated + } + guard let userId2 = self.secondStoreCenter.userId else { + throw TestError.notAuthenticated + } + #expect(userId1 != userId2) + } + + @Test func testDataAccess() async throws { + + guard let userId1 = StoreCenter.main.userId else { + throw TestError.notAuthenticated + } + guard let userId2 = self.secondStoreCenter.userId else { + throw TestError.notAuthenticated + } + + // Setup + let eventColA: SyncedCollection = await StoreCenter.main.mainStore.synchronizedCollectionWithFileLoading() + let tournamentColA: SyncedCollection = await StoreCenter.main.mainStore.synchronizedCollectionWithFileLoading() + let eventColB: SyncedCollection = await self.secondStoreCenter.mainStore.synchronizedCollectionWithFileLoading() + let tournamentColB: SyncedCollection = await self.secondStoreCenter.mainStore.synchronizedCollectionWithFileLoading() + + await eventColA.deleteAsync(contentOfs: Array(eventColA)) + await tournamentColA.deleteAsync(contentOfs: Array(tournamentColA)) + let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync() + + #expect(eventColB.count == 0) + #expect(tournamentColB.count == 0) + + // Create + let eventA = Event(creator: userId1) + await eventColA.addOrUpdateAsync(instance: eventA) + let tournamentA = Tournament(event: eventA.id, name: "P100") + await tournamentColA.addOrUpdateAsync(instance: tournamentA) + + // Share with user2 + try StoreCenter.main.setAuthorizedUsers(for: tournamentA, users: [userId2]) + + let dataB = try await self.secondStoreCenter.testSynchronizeOnceAsync() + let syncDataB = try SyncData(data: dataB, storeCenter: self.secondStoreCenter) + #expect(syncDataB.grants.count == 2) + + #expect(eventColB.count == 1) + #expect(tournamentColB.count == 1) + + // Remove sharing from user2 + try StoreCenter.main.setAuthorizedUsers(for: tournamentA, users: []) + + let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync() + #expect(eventColB.count == 0) + #expect(tournamentColB.count == 0) + + } + +} diff --git a/PadelClubDataTests/SynchronizationTests.swift b/PadelClubDataTests/SynchronizationTests.swift index 6ca7137..b8d6034 100644 --- a/PadelClubDataTests/SynchronizationTests.swift +++ b/PadelClubDataTests/SynchronizationTests.swift @@ -9,6 +9,11 @@ import Testing @testable import PadelClubData @testable import LeStorage +enum SyncTestError: Error { + case instanceNotFound(id: String) + case missingSyncData +} + struct SynchronizationTests { let username: String = "UserDataTests" @@ -73,6 +78,9 @@ struct SynchronizationTests { #expect(eventCollection2.hasLoaded == true) eventCollection2.clear() + // cleanup sync residues + let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync() + // Create let event: Event = Event(creator: userId, club: nil, name: "test") await eventCollection1.addOrUpdateAsync(instance: event) @@ -83,12 +91,157 @@ struct SynchronizationTests { try await eventCollection1.loadDataFromServerIfAllowed(clear: true) #expect(eventCollection1.count == 1) - try await self.secondStoreCenter.testSynchronizeOnceAsync() + let data = try await self.secondStoreCenter.testSynchronizeOnceAsync() + let syncData = try SyncData(data: data, storeCenter: self.secondStoreCenter) + #expect(syncData.updates.count == 1) + #expect(syncData.deletions.count == 0) + #expect(eventCollection2.count == 1) - try await self.secondStoreCenter.testSynchronizeOnceAsync() + let data2 = try await self.secondStoreCenter.testSynchronizeOnceAsync() + let syncData2 = try SyncData(data: data2, storeCenter: self.secondStoreCenter) + #expect(syncData2.updates.count == 0) + #expect(syncData.deletions.count == 0) + #expect(eventCollection2.count == 1) + + } + + @Test func testSynchronizationBothWays() async throws { + + guard let userId = StoreCenter.main.userId else { + throw TestError.notAuthenticated + } + + // Setup events collections + let eventCollectionA: SyncedCollection = await StoreCenter.main.mainStore.synchronizedCollectionWithFileLoading() + await eventCollectionA.deleteAsync(contentOfs: Array(eventCollectionA)) + let eventCollectionB: SyncedCollection = await self.secondStoreCenter.mainStore.synchronizedCollectionWithFileLoading() + eventCollectionB.clear() + + // Setup clubs collections + let clubCollectionA: SyncedCollection = await StoreCenter.main.mainStore.synchronizedCollectionWithFileLoading() + await clubCollectionA.deleteAsync(contentOfs: Array(clubCollectionA)) + let clubCollectionB: SyncedCollection = await self.secondStoreCenter.mainStore.synchronizedCollectionWithFileLoading() + clubCollectionB.clear() + + // cleanup sync residues + let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync() + + // Create + let eventA: Event = Event(creator: userId, club: nil, name: "test-b") + await eventCollectionA.addOrUpdateAsync(instance: eventA) + + // Retrieve Event + let dataB = try await self.secondStoreCenter.testSynchronizeOnceAsync() + let syncDataB = try SyncData(data: dataB, storeCenter: self.secondStoreCenter) + #expect(syncDataB.updates.count == 1) + #expect(eventCollectionB.count == 1) + + // Create club on 2nd StoreCenter + let club = Club(creator: userId, name: "Padel Club", acronym: "PC") + await clubCollectionB.addOrUpdateAsync(instance: club) + + guard let eventB = eventCollectionB.findById(eventA.id) else { + throw SyncTestError.instanceNotFound(id: eventA.id) + } + eventB.club = club.id + await eventCollectionB.addOrUpdateAsync(instance: eventB) + + // Synchronize 1st StoreCenter + let dataA = try await StoreCenter.main.testSynchronizeOnceAsync() + let syncDataA = try SyncData(data: dataA, storeCenter: StoreCenter.main) + + #expect(eventCollectionA.count == 1) + #expect(clubCollectionB.count == 1) + #expect(syncDataA.updates.count == 2) + + guard let clubArray = syncDataA.updates.first(where: { $0.type == Club.self }) else { + throw SyncTestError.missingSyncData + } + #expect(clubArray.items.count == 1) + guard let eventArray = syncDataA.updates.first(where: { $0.type == Event.self }) else { + throw SyncTestError.missingSyncData + } + #expect(eventArray.items.count == 1) + + guard let eventACopy = eventCollectionA.findById(eventA.id) else { + throw SyncTestError.instanceNotFound(id: eventA.id) + } + #expect(eventACopy.club == club.id) } + @Test func testSynchronizationDelete() async throws { + + // Setup tournament + let tournament = Tournament() + + // Setup TeamReg + let teamRegColA: SyncedCollection = await StoreCenter.main.mainStore.synchronizedCollectionWithFileLoading() + await teamRegColA.deleteAsync(contentOfs: Array(teamRegColA)) + let teamRegColB: SyncedCollection = await self.secondStoreCenter.mainStore.synchronizedCollectionWithFileLoading() + teamRegColB.clear() + + // cleanup sync residues + let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync() + + // Create + let trA = TeamRegistration(tournament: tournament.id) + await teamRegColA.addOrUpdateAsync(instance: trA) + await teamRegColA.deleteAsync(instance: trA) + #expect(teamRegColA.count == 0) + + let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync() + #expect(teamRegColB.count == 0) + + } + + @Test func testSyncConflictResolution() async throws { + + guard let userId = StoreCenter.main.userId else { + throw TestError.notAuthenticated + } + + // Setup events collections + let eventCollectionA: SyncedCollection = await StoreCenter.main.mainStore.synchronizedCollectionWithFileLoading() + await eventCollectionA.deleteAsync(contentOfs: Array(eventCollectionA)) + let eventCollectionB: SyncedCollection = await self.secondStoreCenter.mainStore.synchronizedCollectionWithFileLoading() + eventCollectionB.clear() + + // cleanup sync residues + let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync() + + // Create + let eventA: Event = Event(creator: userId, club: nil, name: "test-b") + await eventCollectionA.addOrUpdateAsync(instance: eventA) + + // Retrieve Event + let dataB = try await self.secondStoreCenter.testSynchronizeOnceAsync() + let syncDataB = try SyncData(data: dataB, storeCenter: self.secondStoreCenter) + #expect(syncDataB.updates.count == 1) + #expect(eventCollectionB.count == 1) + + guard let eventB = eventCollectionB.findById(eventA.id) else { + throw SyncTestError.instanceNotFound(id: eventA.id) + } + eventA.name = "my event is nice" + await eventCollectionA.addOrUpdateAsync(instance: eventA) + + eventB.name = "my event is better" + await eventCollectionB.addOrUpdateAsync(instance: eventB) + + await eventCollectionA.addOrUpdateAsync(instance: eventA) + + let _ = try await StoreCenter.main.testSynchronizeOnceAsync() + let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync() + + #expect(eventCollectionA.count == 1) + #expect(eventCollectionB.count == 1) + + #expect(eventCollectionA.first?.name == "my event is nice") + #expect(eventCollectionB.first?.name == "my event is nice") + + } + }