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/DeletionTests.swift

302 lines
14 KiB

//
// DeletionTests.swift
// PadelClubDataTests
//
// Created by Laurent Morvillier on 08/05/2025.
//
import Testing
@testable import PadelClubData
@testable import LeStorage
struct DeletionTests {
let username1: String = "UserDataTests"
let password1: String = "MyPass1234--"
var secondStoreCenter: StoreCenter
init() async throws {
let conf = Config.server
FileManager.default.deleteDirectoryInDocuments(directoryName: "storage")
FileManager.default.deleteDirectoryInDocuments(directoryName: "storage-2")
self.secondStoreCenter = StoreCenter(directoryName: "storage-2")
self.secondStoreCenter.configureURLs(secureScheme: conf.secure, domain: conf.domain, webSockets: false, useSynchronization: true)
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, username: self.username1, password: self.password1)
}
StoreCenter.main.configureURLs(secureScheme: conf.secure, domain: conf.domain, webSockets: false, useSynchronization: true)
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, 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 testDeleteDependencies() async throws {
let teamRegCol: SyncedCollection<TeamRegistration> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
let playerRegCol: SyncedCollection<PlayerRegistration> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
let tr1 = TeamRegistration(tournament: "1")
let pr11 = PlayerRegistration(teamRegistration: tr1.id, firstName: "a1", lastName: "b1")
let pr12 = PlayerRegistration(teamRegistration: tr1.id, firstName: "a1", lastName: "b2")
let tr2 = TeamRegistration(tournament: "1")
let pr21 = PlayerRegistration(teamRegistration: tr2.id, firstName: "a2", lastName: "b1")
let pr22 = PlayerRegistration(teamRegistration: tr2.id, firstName: "a2", lastName: "b2")
try await teamRegCol.addOrUpdateAsync(contentOfs: [tr1, tr2])
try await playerRegCol.addOrUpdateAsync(contentOfs: [pr11, pr12, pr21, pr22])
try await teamRegCol.deleteAsync(instance: tr1)
#expect(teamRegCol.count == 1)
#expect(playerRegCol.count == 2)
}
/// This test creates a hierarchy of objects and deletes to see if everything has been properly deleted
@Test func testDeleteEventWithDependencies() async throws {
guard let user = StoreCenter.main.userId else {
throw TestError.notAuthenticated
}
let clubColA: SyncedCollection<Club> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
let eventColA: SyncedCollection<Event> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
let tournamentColA: SyncedCollection<Tournament> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
let club = Club(creator: user, name: "Club", acronym: "LC")
try await clubColA.addOrUpdateAsync(instance: club)
let event = Event(creator: user, club: club.id, name: "the event")
try await eventColA.addOrUpdateAsync(instance: event)
let tournament = Tournament(event: event.id, name: "P1000")
try await tournamentColA.addOrUpdateAsync(instance: tournament)
let tournamentStore = StoreCenter.main.requestStore(identifier: tournament.id)
let groupStageColA: SyncedCollection<GroupStage> = await tournamentStore.asyncLoadingSynchronizedCollection()
let roundColA: SyncedCollection<Round> = await tournamentStore.asyncLoadingSynchronizedCollection()
let teamRegistrationColA: SyncedCollection<TeamRegistration> = await tournamentStore.asyncLoadingSynchronizedCollection()
let playerRegistrationColA: SyncedCollection<PlayerRegistration> = await tournamentStore.asyncLoadingSynchronizedCollection()
let matchColA: SyncedCollection<Match> = await tournamentStore.asyncLoadingSynchronizedCollection()
let teamScoreColA: SyncedCollection<TeamScore> = await tournamentStore.asyncLoadingSynchronizedCollection()
let gs1 = GroupStage(tournament: tournament.id)
try await groupStageColA.addOrUpdateAsync(instance: gs1)
let round1 = Round(tournament: tournament.id, index: 0)
try await roundColA.addOrUpdateAsync(instance: round1)
let tr1 = TeamRegistration(tournament: tournament.id)
try await teamRegistrationColA.addOrUpdateAsync(instance: tr1)
let pr1 = PlayerRegistration(teamRegistration: tr1.id, firstName: "A", lastName: "B")
try await playerRegistrationColA.addOrUpdateAsync(instance: pr1)
let pr2 = PlayerRegistration(teamRegistration: tr1.id, firstName: "B", lastName: "C")
try await playerRegistrationColA.addOrUpdateAsync(instance: pr2)
let match = Match(groupStage: gs1.id, index: 0)
try await matchColA.addOrUpdateAsync(instance: match)
let ts1 = TeamScore(match: match.id, teamRegistration: tr1.id)
try await teamScoreColA.addOrUpdateAsync(instance: ts1)
#expect(clubColA.count == 1)
#expect(eventColA.count == 1)
#expect(tournamentColA.count == 1)
#expect(groupStageColA.count == 1)
#expect(roundColA.count == 1)
#expect(matchColA.count == 1)
#expect(teamRegistrationColA.count == 1)
#expect(teamScoreColA.count == 1)
#expect(playerRegistrationColA.count == 2)
try await eventColA.deleteAsync(instance: event)
#expect(clubColA.count == 1)
#expect(eventColA.count == 0)
#expect(tournamentColA.count == 0)
#expect(groupStageColA.count == 0)
#expect(roundColA.count == 0)
#expect(matchColA.count == 0)
#expect(teamRegistrationColA.count == 0)
#expect(teamScoreColA.count == 0)
#expect(playerRegistrationColA.count == 0)
}
/// This test creates a hierarchy of objects and see if the delete is properly propagated on a second StoreCenter
@Test func testDeleteEventWithDependenciesOnOtherStorage() async throws {
guard let user = StoreCenter.main.userId else {
throw TestError.notAuthenticated
}
// Creates collection on StoreCenter.main
let clubColA: SyncedCollection<Club> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
let eventColA: SyncedCollection<Event> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
let tournamentColA: SyncedCollection<Tournament> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
let clubColB: SyncedCollection<Club> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection()
let eventColB: SyncedCollection<Event> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection()
let tournamentColB: SyncedCollection<Tournament> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection()
// cleanup sync residues
let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync()
let club = Club(creator: user, name: "Club", acronym: "LC")
try await clubColA.addOrUpdateAsync(instance: club)
let event = Event(creator: user, club: club.id, name: "the event")
try await eventColA.addOrUpdateAsync(instance: event)
let tournament = Tournament(event: event.id, name: "P1000")
try await tournamentColA.addOrUpdateAsync(instance: tournament)
let tournamentStoreA = StoreCenter.main.requestStore(identifier: tournament.id)
let groupStageColA: SyncedCollection<GroupStage> = await tournamentStoreA.asyncLoadingSynchronizedCollection()
let roundColA: SyncedCollection<Round> = await tournamentStoreA.asyncLoadingSynchronizedCollection()
let teamRegistrationColA: SyncedCollection<TeamRegistration> = await tournamentStoreA.asyncLoadingSynchronizedCollection()
let playerRegistrationColA: SyncedCollection<PlayerRegistration> = await tournamentStoreA.asyncLoadingSynchronizedCollection()
let matchColA: SyncedCollection<Match> = await tournamentStoreA.asyncLoadingSynchronizedCollection()
let teamScoreColA: SyncedCollection<TeamScore> = await tournamentStoreA.asyncLoadingSynchronizedCollection()
let gs1 = GroupStage(tournament: tournament.id)
try await groupStageColA.addOrUpdateAsync(instance: gs1)
let round1 = Round(tournament: tournament.id, index: 0)
try await roundColA.addOrUpdateAsync(instance: round1)
let tr1 = TeamRegistration(tournament: tournament.id)
try await teamRegistrationColA.addOrUpdateAsync(instance: tr1)
let pr1 = PlayerRegistration(teamRegistration: tr1.id, firstName: "A", lastName: "B")
try await playerRegistrationColA.addOrUpdateAsync(instance: pr1)
let pr2 = PlayerRegistration(teamRegistration: tr1.id, firstName: "B", lastName: "C")
try await playerRegistrationColA.addOrUpdateAsync(instance: pr2)
let match = Match(groupStage: gs1.id, index: 0)
try await matchColA.addOrUpdateAsync(instance: match)
let ts1 = TeamScore(match: match.id, teamRegistration: tr1.id)
try await teamScoreColA.addOrUpdateAsync(instance: ts1)
#expect(clubColA.count == 1)
#expect(eventColA.count == 1)
#expect(tournamentColA.count == 1)
#expect(groupStageColA.count == 1)
#expect(roundColA.count == 1)
#expect(matchColA.count == 1)
#expect(teamRegistrationColA.count == 1)
#expect(teamScoreColA.count == 1)
#expect(playerRegistrationColA.count == 2)
let tournamentStoreB = self.secondStoreCenter.requestStore(identifier: tournament.id)
let groupStageColB: SyncedCollection<GroupStage> = await tournamentStoreB.asyncLoadingSynchronizedCollection()
let roundColB: SyncedCollection<Round> = await tournamentStoreB.asyncLoadingSynchronizedCollection()
let teamRegistrationColB: SyncedCollection<TeamRegistration> = await tournamentStoreB.asyncLoadingSynchronizedCollection()
let playerRegistrationColB: SyncedCollection<PlayerRegistration> = await tournamentStoreB.asyncLoadingSynchronizedCollection()
let matchColB: SyncedCollection<Match> = await tournamentStoreB.asyncLoadingSynchronizedCollection()
let teamScoreColB: SyncedCollection<TeamScore> = await tournamentStoreB.asyncLoadingSynchronizedCollection()
// cleanup sync residues
let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync()
// let syncData = try SyncData(data: data, storeCenter: self.secondStoreCenter)
#expect(clubColB.count == 1)
#expect(eventColB.count == 1)
#expect(tournamentColB.count == 1)
#expect(groupStageColB.count == 1)
#expect(roundColB.count == 1)
#expect(matchColB.count == 1)
#expect(teamRegistrationColB.count == 1)
#expect(teamScoreColB.count == 1)
#expect(playerRegistrationColB.count == 2)
try await eventColA.deleteAsync(instance: event)
let boolChecker = BoolChecker()
try await boolChecker.waitForCondition {
await !StoreCenter.main.hasPendingAPICalls()
}
// cleanup sync residues
let data = try await self.secondStoreCenter.testSynchronizeOnceAsync()
let syncData = try SyncData(data: data, storeCenter: self.secondStoreCenter)
#expect(syncData.deletions.count == 8) // 8 different deleted types
#expect(clubColB.count == 1)
#expect(eventColB.count == 0)
#expect(tournamentColB.count == 0)
do {
let _ = try self.secondStoreCenter.store(identifier: tournament.id)
Issue.record("should go in the catch because the store has been destroyed")
} catch {
#expect(1 == 1)
}
// #expect(groupStageColB.count == 0)
// #expect(roundColB.count == 0)
// #expect(matchColB.count == 0)
// #expect(teamRegistrationColB.count == 0)
// #expect(teamScoreColB.count == 0)
// #expect(playerRegistrationColB.count == 0)
}
}
enum BoolCheckerError: Error {
case conditionNotMet(timeout: TimeInterval)
}
actor BoolChecker {
/// Continuously checks a condition every 100ms
/// - Parameters:
/// - checkCondition: A closure that returns a Bool
/// - Throws: BoolCheckerError if condition is not met within 2500ms
/// - Returns: True when the condition becomes true
func waitForCondition(
_ checkCondition: @escaping () async -> Bool
) async throws {
let timeout: TimeInterval = 30
let startTime = Date()
while Date().timeIntervalSince(startTime) < timeout {
// Check if the condition is true
if await checkCondition() {
return
}
// print("sleep...")
// Wait for 100ms before next check
try? await Task.sleep(for: .milliseconds(1000))
}
// Throw error if timeout is reached
throw BoolCheckerError.conditionNotMet(timeout: timeout)
}
}