merge main + tests

sync3
Laurent 5 months ago
parent 5c7f74a384
commit 5958662655
  1. 36
      PadelClubData/Data/GroupStage.swift
  2. 4
      PadelClubData/Data/MatchScheduler.swift
  3. 2
      PadelClubData/Data/Tournament.swift
  4. 114
      PadelClubDataTests/SyncDataAccessTests.swift

@ -34,7 +34,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
// MARK: - Computed dependencies
public func matches() -> [Match] {
public func _matches() -> [Match] {
guard let tournamentStore = self.tournamentStore else { return [] }
return tournamentStore.matches.filter { $0.groupStage == self.id }.sorted(by: \.index)
// Store.main.filter { $0.groupStage == self.id }
@ -76,15 +76,15 @@ final public class GroupStage: BaseGroupStage, SideStorable {
}
public func isRunning() -> Bool { // at least a match has started
matches().anySatisfy({ $0.isRunning() })
_matches().anySatisfy({ $0.isRunning() })
}
public func hasStarted() -> Bool { // meaning at least one match is over
matches().filter { $0.hasEnded() }.isEmpty == false
_matches().filter { $0.hasEnded() }.isEmpty == false
}
public func hasEnded() -> Bool {
let _matches = matches()
let _matches = _matches()
if _matches.isEmpty { return false }
//guard teams().count == size else { return false }
return _matches.anySatisfy { $0.hasEnded() == false } == false
@ -102,7 +102,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
public func removeReturnMatches(onlyLast: Bool = false) {
var returnMatches = matches().filter({ $0.index >= matchCount })
var returnMatches = _matches().filter({ $0.index >= matchCount })
if onlyLast {
let matchPhaseCount = matchPhaseCount - 1
returnMatches = returnMatches.filter({ $0.index >= matchCount * matchPhaseCount })
@ -115,7 +115,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
}
public var matchPhaseCount: Int {
let count = matches().count
let count = _matches().count
if matchCount > 0 {
return count / matchCount
} else {
@ -153,7 +153,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
matches.append(newMatch)
}
} else {
for match in self.matches() {
for match in self._matches() {
match.resetTeamScores(outsideOf: [])
teamScores.append(contentsOf: match.createTeamScores())
}
@ -168,7 +168,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
}
public func playedMatches() -> [Match] {
let ordered = matches()
let ordered = _matches()
let order = _matchOrder()
let matchCount = max(1, matchCount)
let count = ordered.count / matchCount
@ -256,7 +256,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
matchIndexes.append(index)
}
}
return matches().filter { matchIndexes.contains($0.index%matchCount) }
return _matches().filter { matchIndexes.contains($0.index%matchCount) }
}
public func initialStartDate(forTeam team: TeamRegistration) -> Date? {
@ -273,7 +273,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
matchIndexes.append(index)
}
}
return matches().first(where: { matchIndexes.contains($0.index) })
return _matches().first(where: { matchIndexes.contains($0.index) })
}
public func availableToStart(playedMatches: [Match], in runningMatches: [Match], checkCanPlay: Bool = true) -> [Match] {
@ -321,7 +321,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
}
public func isReturnMatchEnabled() -> Bool {
matches().count > matchCount
_matches().count > matchCount
}
private func _matchOrder() -> [Int] {
@ -359,7 +359,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
public func returnMatchesSuffix(for matchIndex: Int) -> String {
if matchCount > 0 {
let count = matches().count
let count = _matches().count
if count > matchCount * 2 {
return " - vague \((matchIndex / matchCount) + 1)"
}
@ -401,7 +401,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
}
func _removeMatches() {
self.tournamentStore?.matches.delete(contentOfs: matches())
self.tournamentStore?.matches.delete(contentOfs: _matches())
}
func _numberOfMatchesToBuild() -> Int {
@ -420,7 +420,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
let indexes = [teamPosition, otherTeam].compactMap({ $0.groupStagePosition }).sorted()
let combos = Array((0..<size).combinations(ofCount: 2))
let matchIndexes = combos.enumerated().compactMap { $0.element == indexes ? $0.offset : nil }
let matches = matches().filter { matchIndexes.contains($0.index) }
let matches = _matches().filter { matchIndexes.contains($0.index) }
if matches.count > 1 {
let scoreA = calculateScore(for: teamPosition, matches: matches, groupStagePosition: teamPosition.groupStagePosition!)
let scoreB = calculateScore(for: otherTeam, matches: matches, groupStagePosition: otherTeam.groupStagePosition!)
@ -447,7 +447,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
return teamsSorted.first == teamPosition
} else {
if let matchIndex = combos.firstIndex(of: indexes), let match = self.matches().first(where: { $0.index == matchIndex }) {
if let matchIndex = combos.firstIndex(of: indexes), let match = self._matches().first(where: { $0.index == matchIndex }) {
return teamPosition.id == match.losingTeamId
} else {
return false
@ -614,7 +614,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
}
public func computedStartDate() -> Date? {
return self.matches().sorted(by: \.computedStartDateForSorting).first?.startDate
return self._matches().sorted(by: \.computedStartDateForSorting).first?.startDate
}
public override func deleteDependencies(store: Store, actionOption: ActionOption) {
@ -627,7 +627,7 @@ final public class GroupStage: BaseGroupStage, SideStorable {
func insertOnServer() {
self.tournamentStore?.groupStages.writeChangeAndInsertOnServer(instance: self)
for match in self.matches() {
for match in self._matches() {
match.insertOnServer()
}
}
@ -655,7 +655,7 @@ extension GroupStage: Selectable {
}
public func badgeValue() -> Int? {
return runningMatches(playedMatches: matches()).count
return runningMatches(playedMatches: _matches()).count
}
public func badgeValueColor() -> Color? {

@ -46,7 +46,7 @@ final public class MatchScheduler: BaseMatchScheduler, SideStorable {
groupStages = [specificGroupStage]
}
let matches = groupStages.flatMap { $0.matches() }
let matches = groupStages.flatMap { $0._matches() }
matches.forEach({
$0.removeCourt()
$0.startDate = nil
@ -142,7 +142,7 @@ final public class MatchScheduler: BaseMatchScheduler, SideStorable {
let _groupStages = groupStages
// Get the maximum count of matches in any group
let maxMatchesCount = _groupStages.map { $0.matches().count }.max() ?? 0
let maxMatchesCount = _groupStages.map { $0._matches().count }.max() ?? 0
var flattenedMatches = [Match]()
if simultaneousStart {
// Flatten matches in a round-robin order by cycling through each group

@ -825,7 +825,7 @@ defer {
}
public func groupStagesMatches(atStep step: Int = 0) -> [Match] {
return groupStages(atStep: step).flatMap({ $0.matches() })
return groupStages(atStep: step).flatMap({ $0._matches() })
// return Store.main.filter(isIncluded: { $0.groupStage != nil && groupStageIds.contains($0.groupStage!) })
}

@ -607,7 +607,7 @@ struct SyncDataAccessTests {
}
@Test func testMatchSharingThenTournamentDelete() async throws {
@Test func testMatchSharingThenRevoking() async throws {
guard let userId1 = StoreCenter.main.userId else {
throw TestError.notAuthenticated
}
@ -618,12 +618,12 @@ struct SyncDataAccessTests {
// Setup tournament
let tournamentColA: SyncedCollection<Tournament> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
let eventColA: SyncedCollection<Event> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
try await tournamentColA.deleteAsync(contentOfs: tournamentColA)
let event = Event(creator: userId1)
try await eventColA.addOrUpdateAsync(instance: event)
let tournament = Tournament(event: event.id, name: "test_data_access_children")
tournament.relatedUser = userId1
let tournament = Tournament(event: event.id, name: "testMatchSharingThenTournamentDelete")
try await tournamentColA.addOrUpdateAsync(instance: tournament)
let tourStoreA = try StoreCenter.main.store(identifier: tournament.id)
@ -644,12 +644,91 @@ struct SyncDataAccessTests {
try await playerRegColA.addOrUpdateAsync(contentOfs: [pr11, pr12, pr21, pr22])
let round = Round(tournament: tournament.id)
try await roundColA.addOrUpdateAsync(instance: round)
let match = Match(round: round.id)
try await matchColA.addOrUpdateAsync(instance: match)
let ts1 = TeamScore(match: match.id, team: tr1)
let ts2 = TeamScore(match: match.id, team: tr2)
try await teamScoreColA.addOrUpdateAsync(contentOfs: [ts1, ts2])
try await StoreCenter.main.setAuthorizedUsersAsync(for: match, users: [userId2])
let data = try await self.storeCenterB.testSynchronizeOnceAsync()
let syncData = try SyncData(data: data, storeCenter: self.storeCenterB)
let tournamentColB: SyncedCollection<Tournament> = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection()
#expect(syncData.shared.count == 1)
#expect(tournamentColB.count == 1)
let tourStoreB = try self.storeCenterB.store(identifier: tournament.id)
let matchColB: SyncedCollection<Match> = await tourStoreB.asyncLoadingSynchronizedCollection()
let playerRegColB: SyncedCollection<PlayerRegistration> = await tourStoreB.asyncLoadingSynchronizedCollection()
let teamRegColB: SyncedCollection<TeamRegistration> = await tourStoreB.asyncLoadingSynchronizedCollection()
#expect(matchColB.count == 1)
#expect(playerRegColB.count == 4)
#expect(teamRegColB.count == 2)
try await StoreCenter.main.setAuthorizedUsersAsync(for: match, users: [])
let data2 = try await self.storeCenterB.testSynchronizeOnceAsync()
let syncData2 = try SyncData(data: data2, storeCenter: self.storeCenterB)
#expect(syncData2.revocations.count > 0)
#expect(matchColB.count == 0)
#expect(playerRegColB.count == 0)
#expect(teamRegColB.count == 0)
}
@Test func testMatchSharingThenTournamentDelete() async throws {
guard let userId1 = StoreCenter.main.userId else {
throw TestError.notAuthenticated
}
guard let userId2 = self.storeCenterB.userId else {
throw TestError.notAuthenticated
}
// Setup tournament
let tournamentColA: SyncedCollection<Tournament> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
let eventColA: SyncedCollection<Event> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
try await tournamentColA.deleteAsync(contentOfs: tournamentColA)
let event = Event(creator: userId1)
try await eventColA.addOrUpdateAsync(instance: event)
let tournament = Tournament(event: event.id, name: "testMatchSharingThenTournamentDelete")
try await tournamentColA.addOrUpdateAsync(instance: tournament)
let tourStoreA = try StoreCenter.main.store(identifier: tournament.id)
let teamRegColA: SyncedCollection<TeamRegistration> = await tourStoreA.asyncLoadingSynchronizedCollection()
let playerRegColA: SyncedCollection<PlayerRegistration> = await tourStoreA.asyncLoadingSynchronizedCollection()
let roundColA: SyncedCollection<Round> = await tourStoreA.asyncLoadingSynchronizedCollection()
let matchColA: SyncedCollection<Match> = await tourStoreA.asyncLoadingSynchronizedCollection()
let teamScoreColA: SyncedCollection<TeamScore> = await tourStoreA.asyncLoadingSynchronizedCollection()
let tr1 = TeamRegistration(tournament: tournament.id)
let pr11 = PlayerRegistration(teamRegistration: tr1.id, firstName: "f1", lastName: "l1")
let pr12 = PlayerRegistration(teamRegistration: tr1.id, firstName: "f2", lastName: "l2")
let tr2 = TeamRegistration(tournament: tournament.id)
let pr21 = PlayerRegistration(teamRegistration: tr2.id, firstName: "f21", lastName: "l21")
let pr22 = PlayerRegistration(teamRegistration: tr2.id, firstName: "f22", lastName: "l22")
try await teamRegColA.addOrUpdateAsync(contentOfs: [tr1, tr2])
try await playerRegColA.addOrUpdateAsync(contentOfs: [pr11, pr12, pr21, pr22])
let round = Round(tournament: tournament.id)
try await roundColA.addOrUpdateAsync(instance: round)
let match = Match(round: round.id)
try await matchColA.addOrUpdateAsync(instance: match)
let ts1 = TeamScore(match: match.id, team: tr1)
let ts2 = TeamScore(match: match.id, team: tr2)
try await teamScoreColA.addOrUpdateAsync(contentOfs: [ts1, ts2])
try await StoreCenter.main.setAuthorizedUsersAsync(for: match, users: [userId2])
@ -657,35 +736,44 @@ struct SyncDataAccessTests {
let data = try await self.storeCenterB.testSynchronizeOnceAsync()
let syncData = try SyncData(data: data, storeCenter: self.storeCenterB)
#expect(syncData.shared.count == 1)
let tournamentColB: SyncedCollection<Tournament> = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection()
#expect(syncData.shared.count == 1)
#expect(tournamentColB.count == 1)
let tourStoreB = try self.storeCenterB.store(identifier: tournament.id)
let matchColB: SyncedCollection<Match> = await tourStoreB.asyncLoadingSynchronizedCollection()
let playerRegColB: SyncedCollection<PlayerRegistration> = await tourStoreB.asyncLoadingSynchronizedCollection()
let teamRegColB: SyncedCollection<TeamRegistration> = await tourStoreB.asyncLoadingSynchronizedCollection()
let teamScoreColB: SyncedCollection<TeamScore> = await tourStoreB.asyncLoadingSynchronizedCollection()
#expect(matchColB.count == 1)
#expect(playerRegColB.count == 4)
#expect(teamRegColB.count == 2)
#expect(teamScoreColB.count == 2)
try await tournamentColA.deleteAsync(instance: tournament)
let tournaments: [Tournament] = try await StoreCenter.main.service().get()
#expect(tournaments.count == 0)
#expect(tournamentColA.count == 0)
try await roundColA.deleteAsync(instance: round)
#expect(roundColA.count == 0)
try await Task.sleep(nanoseconds: 1_000_000_000) // wait for cascading deletes to be finished
let data2 = try await self.storeCenterB.testSynchronizeOnceAsync()
let syncData2 = try SyncData(data: data2, storeCenter: self.storeCenterB)
#expect(tournamentColB.count == 0)
for deletion in syncData2.deletions {
print(">>> deletion type = \(deletion.type), count = \(deletion.items.count)")
}
for revocation in syncData2.revocations {
print(">>> revocation type = \(revocation.type), count = \(revocation.items.count)")
}
#expect(syncData2.deletions.count > 0)
#expect(syncData2.revocations.count > 0)
#expect(matchColB.count == 0)
#expect(teamScoreColB.count == 0)
// the delete of round deletes the match, which should revoke granted objects like player/teams
#expect(playerRegColB.count == 0)
#expect(teamRegColB.count == 0)
@ -894,7 +982,7 @@ extension GroupStage {
matches.append(newMatch)
}
} else {
for match in self.matches() {
for match in self._matches() {
match.resetTeamScores(outsideOf: [])
teamScores.append(contentsOf: match.createTeamScores())
}

Loading…
Cancel
Save