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