You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
PadelClubData/PadelClubDataTests/SynchronizationTests.swift

257 lines
10 KiB

//
// SynchronizationTests.swift
// PadelClubDataTests
//
// Created by Laurent Morvillier on 17/04/2025.
//
import Testing
@testable import PadelClubData
@testable import LeStorage
enum SyncTestError: Error {
case instanceNotFound(id: String)
case missingSyncData
}
struct SynchronizationTests {
let username: String = "UserDataTests"
let password: String = "MyPass1234--"
var secondStoreCenter: StoreCenter
init() async throws {
FileManager.default.deleteDirectoryInDocuments(directoryName: "storage")
FileManager.default.deleteDirectoryInDocuments(directoryName: "storage-2")
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()
if token2 == nil {
try await self.login(storeCenter: self.secondStoreCenter)
}
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()
if token == nil {
try await self.login(storeCenter: StoreCenter.main)
}
}
mutating func login(storeCenter: StoreCenter) async throws {
let _: CustomUser = try await storeCenter.service().login(username: self.username, password: self.password)
}
@Test
func testDeviceIds() async throws {
#expect(StoreCenter.main.deviceId() != self.secondStoreCenter.deviceId())
}
@Test func testAuthentication() {
#expect(StoreCenter.main.isAuthenticated)
#expect(self.secondStoreCenter.isAuthenticated)
}
@Test func testSynchronization() async throws {
guard let userId = StoreCenter.main.userId else {
throw TestError.notAuthenticated
}
// Cleanup
let eventCollection1: SyncedCollection<Event> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
#expect(eventCollection1.hasLoaded == true)
await eventCollection1.deleteAsync(contentOfs: Array(eventCollection1))
let eventCollection2: SyncedCollection<Event> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection()
#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)
let serverEvents: [Event] = try await StoreCenter.main.service().get()
#expect(serverEvents.count == 1)
try await eventCollection1.loadDataFromServerIfAllowed(clear: true)
#expect(eventCollection1.count == 1)
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)
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<Event> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
await eventCollectionA.deleteAsync(contentOfs: Array(eventCollectionA))
let eventCollectionB: SyncedCollection<Event> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection()
eventCollectionB.clear()
// Setup clubs collections
let clubCollectionA: SyncedCollection<Club> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
await clubCollectionA.deleteAsync(contentOfs: Array(clubCollectionA))
let clubCollectionB: SyncedCollection<Club> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection()
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<TeamRegistration> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
await teamRegColA.deleteAsync(contentOfs: Array(teamRegColA))
let teamRegColB: SyncedCollection<TeamRegistration> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection()
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<Event> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
await eventCollectionA.deleteAsync(contentOfs: Array(eventCollectionA))
let eventCollectionB: SyncedCollection<Event> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection()
eventCollectionB.clear()
// cleanup sync residues
let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync()
#expect(eventCollectionA.count == 0)
#expect(eventCollectionB.count == 0)
// Create
let eventA: Event = Event(creator: userId, club: nil, name: "test-b")
await eventCollectionA.addOrUpdateAsync(instance: eventA)
#expect(eventCollectionA.count == 1)
let serverEvents: [Event] = try await StoreCenter.main.service().get()
#expect(serverEvents.count == 1)
// 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")
}
}