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.
302 lines
14 KiB
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)
|
|
}
|
|
}
|
|
|