parent
2cb5e0e41e
commit
3139a694ad
@ -0,0 +1,269 @@ |
||||
// |
||||
// 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 { |
||||
|
||||
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, 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: false, domain: "127.0.0.1:8000", 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) |
||||
} |
||||
|
||||
/// 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) |
||||
#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(100)) |
||||
} |
||||
|
||||
// Throw error if timeout is reached |
||||
throw BoolCheckerError.conditionNotMet(timeout: timeout) |
||||
} |
||||
} |
||||
Loading…
Reference in new issue