Refactor deleteDependencies + tests

sync3
Laurent 6 months ago
parent 2cb5e0e41e
commit 3139a694ad
  1. 15
      PadelClubData/Data/Club.swift
  2. 2
      PadelClubData/Data/Court.swift
  3. 2
      PadelClubData/Data/DateInterval.swift
  4. 2
      PadelClubData/Data/DrawLog.swift
  5. 27
      PadelClubData/Data/Event.swift
  6. 46
      PadelClubData/Data/GroupStage.swift
  7. 15
      PadelClubData/Data/Match.swift
  8. 31
      PadelClubData/Data/Round.swift
  9. 57
      PadelClubData/Data/TeamRegistration.swift
  10. 67
      PadelClubData/Data/Tournament.swift
  11. 4
      PadelClubData/Data/TournamentLibrary.swift
  12. 269
      PadelClubDataTests/DeletionTests.swift
  13. 1
      PadelClubDataTests/PadelClubDataTests.swift
  14. 67
      PadelClubDataTests/SyncDataAccessTests.swift

@ -29,12 +29,15 @@ final public class Club: BaseClub {
DataStore.shared.courts.filter { $0.club == self.id }.sorted(by: \.index)
}
public override func deleteDependencies(shouldBeSynchronized: Bool) {
let customizedCourts = self.customizedCourts
for customizedCourt in customizedCourts {
customizedCourt.deleteDependencies(shouldBeSynchronized: shouldBeSynchronized)
}
DataStore.shared.courts.deleteDependencies(customizedCourts, shouldBeSynchronized: shouldBeSynchronized)
public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
store.deleteDependencies(type: Court.self, shouldBeSynchronized: shouldBeSynchronized) { $0.club == self.id }
// let customizedCourts = self.customizedCourts
// for customizedCourt in customizedCourts {
// customizedCourt.deleteDependencies(store: store, shouldBeSynchronized: shouldBeSynchronized)
// }
// DataStore.shared.courts.deleteDependencies(customizedCourts, shouldBeSynchronized: shouldBeSynchronized)
}
}

@ -52,7 +52,7 @@ final public class Court: BaseCourt {
Store.main.findById(club)
}
public override func deleteDependencies(shouldBeSynchronized: Bool) {
public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
}
}

@ -57,7 +57,7 @@ final public class DateInterval: BaseDateInterval {
date <= startDate && date <= endDate && date >= startDate && date >= endDate
}
public override func deleteDependencies(shouldBeSynchronized: Bool) {
public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
}
// enum CodingKeys: String, CodingKey {

@ -74,7 +74,7 @@ final public class DrawLog: BaseDrawLog, SideStorable {
return TournamentLibrary.shared.store(tournamentId: self.tournament)
}
public override func deleteDependencies(shouldBeSynchronized: Bool) {
public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
}
}

@ -25,19 +25,24 @@ final public class Event: BaseEvent {
super.init()
}
public override func deleteDependencies(shouldBeSynchronized: Bool) {
let tournaments = self.tournaments
for tournament in tournaments {
tournament.deleteDependencies(shouldBeSynchronized: shouldBeSynchronized)
}
public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
DataStore.shared.tournaments.deleteDependencies(tournaments, shouldBeSynchronized: shouldBeSynchronized)
store.deleteDependencies(type: Tournament.self, shouldBeSynchronized: shouldBeSynchronized) { $0.event == self.id && $0.isDeleted == false }
store.deleteDependencies(type: DateInterval.self, shouldBeSynchronized: shouldBeSynchronized) { $0.event == self.id }
let courtsUnavailabilities = self.courtsUnavailability
for courtsUnavailability in courtsUnavailabilities {
courtsUnavailability.deleteDependencies(shouldBeSynchronized: shouldBeSynchronized)
}
DataStore.shared.dateIntervals.deleteDependencies(courtsUnavailabilities, shouldBeSynchronized: shouldBeSynchronized)
// let tournaments = self.tournaments
// for tournament in tournaments {
// tournament.deleteDependencies(store: store, shouldBeSynchronized: shouldBeSynchronized)
// }
//
// DataStore.shared.tournaments.deleteDependencies(tournaments, shouldBeSynchronized: shouldBeSynchronized)
//
// let courtsUnavailabilities = self.courtsUnavailability
// for courtsUnavailability in courtsUnavailabilities {
// courtsUnavailability.deleteDependencies(store: store, shouldBeSynchronized: shouldBeSynchronized)
// }
// DataStore.shared.dateIntervals.deleteDependencies(courtsUnavailabilities, shouldBeSynchronized: shouldBeSynchronized)
}
// MARK: - Computed dependencies

@ -599,43 +599,17 @@ final public class GroupStage: BaseGroupStage, SideStorable {
return teams(true).firstIndex(of: team)
}
public override func deleteDependencies(shouldBeSynchronized: Bool) {
let matches = self._matches()
for match in matches {
match.deleteDependencies(shouldBeSynchronized: shouldBeSynchronized)
}
self.tournamentStore?.matches.deleteDependencies(matches, shouldBeSynchronized: shouldBeSynchronized)
}
// init(from decoder: Decoder) throws {
// let container = try decoder.container(keyedBy: CodingKeys.self)
//
// id = try container.decode(String.self, forKey: ._id)
// storeId = try container.decode(String.self, forKey: ._storeId)
// lastUpdate = try container.decode(Date.self, forKey: ._lastUpdate)
// tournament = try container.decode(String.self, forKey: ._tournament)
// index = try container.decode(Int.self, forKey: ._index)
// size = try container.decode(Int.self, forKey: ._size)
// format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format)
// startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate)
// name = try container.decodeIfPresent(String.self, forKey: ._name)
// step = try container.decodeIfPresent(Int.self, forKey: ._step) ?? 0
// }
//
// public func encode(to encoder: Encoder) throws {
// public var container = encoder.container(keyedBy: CodingKeys.self)
public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
store.deleteDependencies(type: Match.self, shouldBeSynchronized: shouldBeSynchronized) { $0.groupStage == self.id }
//
// try container.encode(id, forKey: ._id)
// try container.encode(storeId, forKey: ._storeId)
// try container.encode(lastUpdate, forKey: ._lastUpdate)
// try container.encode(tournament, forKey: ._tournament)
// try container.encode(index, forKey: ._index)
// try container.encode(size, forKey: ._size)
// try container.encode(format, forKey: ._format)
// try container.encode(startDate, forKey: ._startDate)
// try container.encode(name, forKey: ._name)
// try container.encode(step, forKey: ._step)
// }
// let matches = self._matches()
// for match in matches {
// match.deleteDependencies(store: store, shouldBeSynchronized: shouldBeSynchronized)
// }
// self.tournamentStore?.matches.deleteDependencies(matches, shouldBeSynchronized: shouldBeSynchronized)
}
func insertOnServer() {
self.tournamentStore?.groupStages.writeChangeAndInsertOnServer(instance: self)

@ -70,12 +70,15 @@ final public class Match: BaseMatch, SideStorable {
// MARK: -
public override func deleteDependencies(shouldBeSynchronized: Bool) {
let teamScores = self.teamScores
for teamScore in teamScores {
teamScore.deleteDependencies(shouldBeSynchronized: shouldBeSynchronized)
}
self.tournamentStore?.teamScores.deleteDependencies(teamScores, shouldBeSynchronized: shouldBeSynchronized)
public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
store.deleteDependencies(type: TeamScore.self, shouldBeSynchronized: shouldBeSynchronized) { $0.match == self.id }
// let teamScores = self.teamScores
// for teamScore in teamScores {
// teamScore.deleteDependencies(store: store, shouldBeSynchronized: shouldBeSynchronized)
// }
// self.tournamentStore?.teamScores.deleteDependencies(teamScores, shouldBeSynchronized: shouldBeSynchronized)
}
public func indexInRound(in matches: [Match]? = nil) -> Int {

@ -890,20 +890,25 @@ defer {
_cachedLoserRoundsAndChildren = nil
}
public override func deleteDependencies(shouldBeSynchronized: Bool) {
let matches = self._matches()
for match in matches {
match.deleteDependencies(shouldBeSynchronized: shouldBeSynchronized)
}
self.tournamentStore?.matches.deleteDependencies(matches, shouldBeSynchronized: shouldBeSynchronized)
let loserRounds = self.loserRounds()
for round in loserRounds {
round.deleteDependencies(shouldBeSynchronized: shouldBeSynchronized)
}
public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
store.deleteDependencies(type: Match.self, shouldBeSynchronized: shouldBeSynchronized) { $0.round == self.id }
store.deleteDependencies(type: Round.self, shouldBeSynchronized: shouldBeSynchronized) { $0.parent == self.id }
self.tournamentStore?.rounds.deleteDependencies(loserRounds, shouldBeSynchronized: shouldBeSynchronized)
// let matches = self._matches()
// for match in matches {
// match.deleteDependencies(store: store, shouldBeSynchronized: shouldBeSynchronized)
// }
//
// self.tournamentStore?.matches.deleteDependencies(matches, shouldBeSynchronized: shouldBeSynchronized)
//
// let loserRounds = self.loserRounds()
// for round in loserRounds {
// round.deleteDependencies(store: store, shouldBeSynchronized: shouldBeSynchronized)
// }
//
// self.tournamentStore?.rounds.deleteDependencies(loserRounds, shouldBeSynchronized: shouldBeSynchronized)
}

@ -12,37 +12,6 @@ import SwiftUI
@Observable
final public class TeamRegistration: BaseTeamRegistration, SideStorable {
// static func resourceName() -> String { "team-registrations" }
// static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
// static func filterByStoreIdentifier() -> Bool { return true }
// static var relationshipNames: [String] = []
//
// var id: String = Store.randomId()
// var lastUpdate: Date
// var tournament: String
// var groupStage: String?
// var registrationDate: Date?
// var callDate: Date?
// var bracketPosition: Int?
// var groupStagePosition: Int?
// var comment: String?
// var source: String?
// var sourceValue: String?
// var logo: String?
// var name: String?
//
// var walkOut: Bool = false
// var wildCardBracket: Bool = false
// var wildCardGroupStage: Bool = false
// var weight: Int = 0
// var lockedWeight: Int?
// var confirmationDate: Date?
// var qualified: Bool = false
// var finalRanking: Int?
// var pointsEarned: Int?
//
// var storeId: String? = nil
public init(
tournament: String, groupStage: String? = nil, registrationDate: Date? = nil,
callDate: Date? = nil, bracketPosition: Int? = nil, groupStagePosition: Int? = nil,
@ -130,18 +99,22 @@ final public class TeamRegistration: BaseTeamRegistration, SideStorable {
tournamentStore.teamScores.delete(contentOfs: ts)
}
public override func deleteDependencies(shouldBeSynchronized: Bool) {
let unsortedPlayers = unsortedPlayers()
for player in unsortedPlayers {
player.deleteDependencies(shouldBeSynchronized: shouldBeSynchronized)
}
self.tournamentStore?.playerRegistrations.deleteDependencies(unsortedPlayers, shouldBeSynchronized: shouldBeSynchronized)
public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
store.deleteDependencies(type: TeamScore.self, shouldBeSynchronized: shouldBeSynchronized) { $0.teamRegistration == self.id }
store.deleteDependencies(type: PlayerRegistration.self, shouldBeSynchronized: shouldBeSynchronized) { $0.teamRegistration == self.id }
let teamScores = teamScores()
for teamScore in teamScores {
teamScore.deleteDependencies(shouldBeSynchronized: shouldBeSynchronized)
}
self.tournamentStore?.teamScores.deleteDependencies(teamScores, shouldBeSynchronized: shouldBeSynchronized)
// let unsortedPlayers = unsortedPlayers()
// for player in unsortedPlayers {
// player.deleteDependencies(store: store, shouldBeSynchronized: shouldBeSynchronized)
// }
// self.tournamentStore?.playerRegistrations.deleteDependencies(unsortedPlayers, shouldBeSynchronized: shouldBeSynchronized)
//
// let teamScores = teamScores()
// for teamScore in teamScores {
// teamScore.deleteDependencies(store: store, shouldBeSynchronized: shouldBeSynchronized)
// }
// self.tournamentStore?.teamScores.deleteDependencies(teamScores, shouldBeSynchronized: shouldBeSynchronized)
}
public func hasArrived(isHere: Bool = false) {

@ -32,34 +32,47 @@ final public class Tournament: BaseTournament {
return TournamentLibrary.shared.store(tournamentId: self.id)
}
public override func deleteDependencies(shouldBeSynchronized: Bool) {
guard let store = self.tournamentStore else { return }
let drawLogs = Array(store.drawLogs)
for drawLog in drawLogs {
drawLog.deleteDependencies(shouldBeSynchronized: shouldBeSynchronized)
}
store.drawLogs.deleteDependencies(drawLogs, shouldBeSynchronized: shouldBeSynchronized)
let teams = Array(store.teamRegistrations)
for team in teams {
team.deleteDependencies(shouldBeSynchronized: shouldBeSynchronized)
}
store.teamRegistrations.deleteDependencies(teams, shouldBeSynchronized: shouldBeSynchronized)
let groups = Array(store.groupStages)
for group in groups {
group.deleteDependencies(shouldBeSynchronized: shouldBeSynchronized)
}
store.groupStages.deleteDependencies(groups, shouldBeSynchronized: shouldBeSynchronized)
let rounds = Array(store.rounds)
for round in rounds {
round.deleteDependencies(shouldBeSynchronized: shouldBeSynchronized)
public override func deleteDependencies(store: Store, shouldBeSynchronized: Bool) {
do {
let tournamentStore = try store.alternateStore(identifier: self.id)
tournamentStore.deleteAllDependencies(type: DrawLog.self, shouldBeSynchronized: shouldBeSynchronized)
tournamentStore.deleteAllDependencies(type: TeamRegistration.self, shouldBeSynchronized: shouldBeSynchronized)
tournamentStore.deleteAllDependencies(type: GroupStage.self, shouldBeSynchronized: shouldBeSynchronized)
tournamentStore.deleteAllDependencies(type: Round.self, shouldBeSynchronized: shouldBeSynchronized)
tournamentStore.deleteAllDependencies(type: MatchScheduler.self, shouldBeSynchronized: shouldBeSynchronized)
} catch {
Logger.error(error)
}
store.rounds.deleteDependencies(rounds, shouldBeSynchronized: shouldBeSynchronized)
store.matchSchedulers.deleteDependencies(self._matchSchedulers())
// guard let tournamentStore = self.tournamentStore else { return }
//
// let drawLogs = Array(tournamentStore.drawLogs)
// for drawLog in drawLogs {
// drawLog.deleteDependencies(store: tournamentStore, shouldBeSynchronized: shouldBeSynchronized)
// }
// tournamentStore.drawLogs.deleteDependencies(drawLogs, shouldBeSynchronized: shouldBeSynchronized)
//
// let teams = Array(tournamentStore.teamRegistrations)
// for team in teams {
// team.deleteDependencies(store: tournamentStore, shouldBeSynchronized: shouldBeSynchronized)
// }
// tournamentStore.teamRegistrations.deleteDependencies(teams, shouldBeSynchronized: shouldBeSynchronized)
//
// let groups = Array(tournamentStore.groupStages)
// for group in groups {
// group.deleteDependencies(store: tournamentStore, shouldBeSynchronized: shouldBeSynchronized)
// }
// tournamentStore.groupStages.deleteDependencies(groups, shouldBeSynchronized: shouldBeSynchronized)
//
// let rounds = Array(tournamentStore.rounds)
// for round in rounds {
// round.deleteDependencies(store: tournamentStore, shouldBeSynchronized: shouldBeSynchronized)
// }
// tournamentStore.rounds.deleteDependencies(rounds, shouldBeSynchronized: shouldBeSynchronized)
//
// tournamentStore.matchSchedulers.deleteDependencies(self._matchSchedulers())
}

@ -15,12 +15,12 @@ public class TournamentLibrary {
fileprivate var _stores: [String : TournamentStore] = [:]
func store(tournamentId: String) -> TournamentStore? {
guard let tournament = DataStore.shared.tournaments.first(where: { $0.id == tournamentId }) else { return nil }
guard let _ = DataStore.shared.tournaments.first(where: { $0.id == tournamentId }) else { return nil }
if let store = self._stores[tournamentId] {
return store
}
let store = StoreCenter.main.store(identifier: tournamentId)
let store = StoreCenter.main.requestStore(identifier: tournamentId)
let tournamentStore = TournamentStore(store: store)
self._stores[tournamentId] = tournamentStore
return tournamentStore

@ -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)
}
}

@ -12,6 +12,7 @@ import Testing
enum TestError: Error {
case notAuthenticated
case sameDeviceId
case missingEvent
}
struct PadelClubDataTests {

@ -257,6 +257,71 @@ struct SyncDataAccessTests {
#expect(eventColB.first?.club == club2A.id)
}
// trouver le moyen de tester les sous-Store et voir si on peut essayer de rendre ça propre sans passer par DataStore ou autre
/// In this test, the first user:
/// - creates one event
/// - shares the event with a second user
/// The second user:
/// - changes the club
/// Here we want to test that the first Club is removed and the second one is received
@Test func testRelationshipChangesByAgent() async throws {
guard let userId1 = StoreCenter.main.userId else {
throw TestError.notAuthenticated
}
guard let userId2 = self.secondStoreCenter.userId else {
throw TestError.notAuthenticated
}
// Setup
let eventColA: SyncedCollection<Event> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
let clubColA: SyncedCollection<Club> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
let eventColB: SyncedCollection<Event> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection()
let clubColB: SyncedCollection<Club> = await self.secondStoreCenter.mainStore.asyncLoadingSynchronizedCollection()
if let dataAccessCollection = StoreCenter.main.dataAccessCollection {
try await dataAccessCollection.deleteAsync(contentOfs: Array(dataAccessCollection))
}
try await eventColA.deleteAsync(contentOfs: Array(eventColA))
try await clubColA.deleteAsync(contentOfs: Array(clubColA))
let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync()
#expect(eventColB.count == 0)
#expect(clubColB.count == 0)
// Create
let clubA = Club(creator: userId1, name: "Club 1", acronym: "C1")
try await clubColA.addOrUpdateAsync(instance: clubA)
let eventA = Event(creator: userId1, club: clubA.id, name: "event 1")
try await eventColA.addOrUpdateAsync(instance: eventA)
// Share with user2
try await StoreCenter.main.setAuthorizedUsersAsync(for: eventA, users: [userId2])
let _ = try await self.secondStoreCenter.testSynchronizeOnceAsync()
guard let eventB = eventColB.first else {
throw TestError.missingEvent
}
#expect(eventA.id == eventB.id)
#expect(clubColB.count == 1)
// Second user changes the club
let club2B = Club(creator: userId2, name: "Club 2", acronym: "C2")
try await clubColB.addOrUpdateAsync(instance: club2B)
eventB.club = club2B.id
try await eventColB.addOrUpdateAsync(instance: eventB)
let dataA = try await StoreCenter.main.testSynchronizeOnceAsync()
let syncDataA = try SyncData(data: dataA, storeCenter: StoreCenter.main)
// #expect(syncDataA.sharedRelationshipSets.count == 1)
// #expect(syncDataA.sharedRelationshipRemovals.count == 1)
#expect(eventA.club == club2B.id)
#expect(clubColB.count == 1)
}
}

Loading…
Cancel
Save