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.
945 lines
42 KiB
945 lines
42 KiB
//
|
|
// DataAccessSyncTests.swift
|
|
// PadelClubDataTests
|
|
//
|
|
// Created by Laurent Morvillier on 02/05/2025.
|
|
//
|
|
|
|
import Testing
|
|
@testable import PadelClubData
|
|
@testable import LeStorage
|
|
|
|
struct SyncDataAccessTests {
|
|
|
|
let username1: String = "UserDataTests"
|
|
let password1: String = "MyPass1234--"
|
|
|
|
let username2: String = "seconduser"
|
|
let password2: String = "MyPass1234--"
|
|
|
|
var storeCenterA: StoreCenter
|
|
var storeCenterB: StoreCenter
|
|
|
|
init() async throws {
|
|
|
|
let conf = Config.server
|
|
|
|
let dir = "storage"
|
|
let dirA = "storageA"
|
|
let dirB = "storageB"
|
|
|
|
FileManager.default.deleteDirectoryInDocuments(directoryName: dir)
|
|
FileManager.default.deleteDirectoryInDocuments(directoryName: dirA)
|
|
FileManager.default.deleteDirectoryInDocuments(directoryName: dirB)
|
|
|
|
self.storeCenterA = StoreCenter(directoryName: dirA)
|
|
self.storeCenterB = StoreCenter(directoryName: dirB)
|
|
|
|
// StoreCenter.main
|
|
StoreCenter.main.configureURLs(secureScheme: conf.secure, domain: conf.domain, webSockets: false, useSynchronization: true)
|
|
StoreCenter.main.tokenKeychain = MockKeychainStore(fileName: "\(dir)/token.json")
|
|
StoreCenter.main.deviceKeychain = MockKeychainStore(fileName: "\(dir)/device.json")
|
|
try StoreCenter.main.deviceKeychain.add(value: UUID().uuidString)
|
|
StoreCenter.main.classProject = "PadelClubData"
|
|
|
|
// if conf.inMemory {
|
|
// try await self._createUsers()
|
|
// }
|
|
|
|
let token = try? StoreCenter.main.rawTokenShouldNotBeUsed()
|
|
if token == nil {
|
|
try await self.login(storeCenter: StoreCenter.main, username: self.username1, password: self.password1)
|
|
}
|
|
|
|
// StoreCenter A
|
|
self.storeCenterA.configureURLs(secureScheme: conf.secure, domain: conf.domain, webSockets: false, useSynchronization: true)
|
|
self.storeCenterA.tokenKeychain = MockKeychainStore(fileName: "\(dirA)/token.json")
|
|
self.storeCenterA.deviceKeychain = MockKeychainStore(fileName: "\(dirA)/device.json")
|
|
try self.storeCenterA.deviceKeychain.add(value: UUID().uuidString)
|
|
self.storeCenterA.classProject = "PadelClubData"
|
|
|
|
let tokenA = try? self.storeCenterA.rawTokenShouldNotBeUsed()
|
|
if tokenA == nil {
|
|
try await self.login(storeCenter: self.storeCenterA, username: self.username1, password: self.password1)
|
|
}
|
|
|
|
// StoreCenter B
|
|
self.storeCenterB.configureURLs(secureScheme: conf.secure, domain: conf.domain, webSockets: false, useSynchronization: true)
|
|
self.storeCenterB.tokenKeychain = MockKeychainStore(fileName: "\(dirB)/token.json")
|
|
self.storeCenterB.deviceKeychain = MockKeychainStore(fileName: "\(dirB)/device.json")
|
|
try self.storeCenterB.deviceKeychain.add(value: UUID().uuidString)
|
|
|
|
self.storeCenterB.classProject = "PadelClubData"
|
|
|
|
let tokenB = try? self.storeCenterB.rawTokenShouldNotBeUsed()
|
|
if tokenB == nil {
|
|
try await self.login(storeCenter: self.storeCenterB, username: self.username2, password: self.password2)
|
|
}
|
|
|
|
}
|
|
|
|
fileprivate func _createUsers() async throws {
|
|
|
|
let user1 = CustomUser(username: self.username1)
|
|
let form1 = UserCreationForm(user: user1, username: self.username1, password: self.password1, firstName: "f", lastName: "l", email: "test1@lolomo.net", phone: "", country: "")
|
|
let _: CustomUser = try await StoreCenter.main.service().createAccount(user: form1)
|
|
|
|
let user2 = CustomUser(username: self.username2)
|
|
let form2 = UserCreationForm(user: user2, username: self.username2, password: self.password2, firstName: "f2", lastName: "l2", email: "test2@lolomo.net", phone: "", country: "")
|
|
let _: CustomUser = try await StoreCenter.main.service().createAccount(user: form2)
|
|
}
|
|
|
|
mutating func login(storeCenter: StoreCenter, username: String, password: String) async throws {
|
|
let _: CustomUser = try await storeCenter.service().login(username: username, password: password)
|
|
}
|
|
|
|
@Test func testSetup() async throws {
|
|
#expect(self.storeCenterA.isAuthenticated)
|
|
#expect(self.storeCenterB.isAuthenticated)
|
|
|
|
guard let userId1 = self.storeCenterA.userId else {
|
|
throw TestError.notAuthenticated
|
|
}
|
|
guard let userId2 = self.storeCenterB.userId else {
|
|
throw TestError.notAuthenticated
|
|
}
|
|
#expect(userId1 != userId2)
|
|
}
|
|
|
|
/// In this test, the first user:
|
|
/// - creates an event and a tournament
|
|
/// - shares the tournament with a second user
|
|
/// - remove the sharing with the second user
|
|
/// We test that the data is properly received and removed upon the sharing actions
|
|
@Test func testTournamentSharing() async throws {
|
|
|
|
guard let userId1 = self.storeCenterA.userId else {
|
|
throw TestError.notAuthenticated
|
|
}
|
|
guard let userId2 = self.storeCenterB.userId else {
|
|
throw TestError.notAuthenticated
|
|
}
|
|
|
|
// Setup
|
|
let eventColA: SyncedCollection<Event> = await self.storeCenterA.mainStore.asyncLoadingSynchronizedCollection()
|
|
let tournamentColA: SyncedCollection<Tournament> = await self.storeCenterA.mainStore.asyncLoadingSynchronizedCollection()
|
|
let eventColB: SyncedCollection<Event> = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection()
|
|
let tournamentColB: SyncedCollection<Tournament> = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection()
|
|
|
|
var dataAccesses: [DataAccess] = try await self.storeCenterA.service().get()
|
|
if let dataAccessCollection = self.storeCenterA.dataAccessCollection {
|
|
try await dataAccessCollection.deleteAsync(contentOfs: dataAccesses)
|
|
} else {
|
|
Issue.record("dataAccessCollection should not be nil")
|
|
}
|
|
|
|
try await eventColA.deleteAsync(contentOfs: Array(eventColA))
|
|
try await tournamentColA.deleteAsync(contentOfs: Array(tournamentColA))
|
|
|
|
let _ = try await self.storeCenterB.testSynchronizeOnceAsync()
|
|
|
|
#expect(eventColB.count == 0)
|
|
#expect(tournamentColB.count == 0)
|
|
|
|
// Create
|
|
let eventA = Event(creator: userId1)
|
|
try await eventColA.addOrUpdateAsync(instance: eventA)
|
|
let tournamentA = Tournament(event: eventA.id, name: "P100")
|
|
try await tournamentColA.addOrUpdateAsync(instance: tournamentA)
|
|
|
|
// Share with user2
|
|
try await self.storeCenterA.setAuthorizedUsersAsync(for: tournamentA, users: [userId2])
|
|
|
|
var dataB = try await self.storeCenterB.testSynchronizeOnceAsync()
|
|
var syncDataB = try SyncData(data: dataB, storeCenter: self.storeCenterB)
|
|
#expect(syncDataB.shared.count == 1) // the tournament
|
|
#expect(syncDataB.grants.count == 1) // the granted event
|
|
|
|
#expect(eventColB.count == 1)
|
|
#expect(eventColB.first?.sharing == .granted)
|
|
#expect(tournamentColB.count == 1)
|
|
#expect(tournamentColB.first?.sharing == .shared)
|
|
|
|
// Remove sharing from user2
|
|
try await self.storeCenterA.setAuthorizedUsersAsync(for: tournamentA, users: [])
|
|
|
|
dataB = try await self.storeCenterB.testSynchronizeOnceAsync()
|
|
syncDataB = try SyncData(data: dataB, storeCenter: self.storeCenterB)
|
|
#expect(syncDataB.revocations.count == 1)
|
|
#expect(syncDataB.revocationParents.count == 1)
|
|
|
|
#expect(eventColB.count == 0)
|
|
#expect(tournamentColB.count == 0)
|
|
|
|
dataAccesses = try await self.storeCenterA.service().get()
|
|
#expect(dataAccesses.count == 0)
|
|
}
|
|
|
|
/// In this test, the first user:
|
|
/// - creates a club and 2 events in that club
|
|
/// - shares both events with a second user
|
|
/// - removes the sharing of 1 event
|
|
/// Here we want to test that the Club instance remains even if one event is removed from the second user
|
|
@Test func testSharedRelationship() async throws {
|
|
|
|
guard let userId1 = self.storeCenterA.userId else {
|
|
throw TestError.notAuthenticated
|
|
}
|
|
guard let userId2 = self.storeCenterB.userId else {
|
|
throw TestError.notAuthenticated
|
|
}
|
|
|
|
// Setup
|
|
let eventColA: SyncedCollection<Event> = await self.storeCenterA.mainStore.asyncLoadingSynchronizedCollection()
|
|
let clubColA: SyncedCollection<Club> = await self.storeCenterA.mainStore.asyncLoadingSynchronizedCollection()
|
|
let eventColB: SyncedCollection<Event> = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection()
|
|
let clubColB: SyncedCollection<Club> = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection()
|
|
|
|
if let dataAccessCollection = self.storeCenterA.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.storeCenterB.testSynchronizeOnceAsync()
|
|
|
|
#expect(eventColB.count == 0)
|
|
#expect(clubColB.count == 0)
|
|
|
|
// Create
|
|
let clubA = Club(creator: userId1, name: "Club A", acronym: "CA")
|
|
try await clubColA.addOrUpdateAsync(instance: clubA)
|
|
|
|
let event1A = Event(creator: userId1, club: clubA.id, name: "event 1")
|
|
let event2A = Event(creator: userId1, club: clubA.id, name: "event 2")
|
|
try await eventColA.addOrUpdateAsync(contentOfs: [event1A, event2A])
|
|
|
|
// Share with user2
|
|
try await self.storeCenterA.setAuthorizedUsersAsync(for: event1A, users: [userId2])
|
|
try await self.storeCenterA.setAuthorizedUsersAsync(for: event2A, users: [userId2])
|
|
|
|
var dataB = try await self.storeCenterB.testSynchronizeOnceAsync()
|
|
var syncDataB = try SyncData(data: dataB, storeCenter: self.storeCenterB)
|
|
|
|
#expect(syncDataB.shared.count == 1)
|
|
#expect(syncDataB.grants.count == 1)
|
|
|
|
let clubGrants = syncDataB.grants.first { $0.type == Club.self }
|
|
let eventGrants = syncDataB.shared.first { $0.type == Event.self }
|
|
#expect(clubGrants?.items.count == 1)
|
|
#expect(eventGrants?.items.count == 2)
|
|
|
|
#expect(eventColB.count == 2)
|
|
#expect(clubColB.count == 1)
|
|
|
|
// Remove sharing from user2
|
|
try await self.storeCenterA.setAuthorizedUsersAsync(for: event1A, users: [])
|
|
|
|
dataB = try await self.storeCenterB.testSynchronizeOnceAsync()
|
|
syncDataB = try SyncData(data: dataB, storeCenter: self.storeCenterB)
|
|
#expect(syncDataB.revocations.count == 1)
|
|
#expect(syncDataB.revocationParents.count == 1)
|
|
|
|
#expect(eventColB.count == 1)
|
|
#expect(clubColB.count == 1) // club remains because used in event2A
|
|
}
|
|
|
|
/// In this test, the first user:
|
|
/// - creates one event and 2 clubs
|
|
/// - shares the event with a second user
|
|
/// - changes the club on the event
|
|
/// Here we want to test that the first Club is removed and the second one is received
|
|
@Test func testRelationshipChange() async throws {
|
|
|
|
guard let userId1 = self.storeCenterA.userId else {
|
|
throw TestError.notAuthenticated
|
|
}
|
|
guard let userId2 = self.storeCenterB.userId else {
|
|
throw TestError.notAuthenticated
|
|
}
|
|
|
|
// Setup
|
|
let eventColA: SyncedCollection<Event> = await self.storeCenterA.mainStore.asyncLoadingSynchronizedCollection()
|
|
let clubColA: SyncedCollection<Club> = await self.storeCenterA.mainStore.asyncLoadingSynchronizedCollection()
|
|
let eventColB: SyncedCollection<Event> = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection()
|
|
let clubColB: SyncedCollection<Club> = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection()
|
|
|
|
if let dataAccessCollection = self.storeCenterA.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.storeCenterB.testSynchronizeOnceAsync()
|
|
|
|
#expect(eventColB.count == 0)
|
|
#expect(clubColB.count == 0)
|
|
|
|
// Create
|
|
let club1A = Club(creator: userId1, name: "Club 1", acronym: "C1")
|
|
try await clubColA.addOrUpdateAsync(instance: club1A)
|
|
let club2A = Club(creator: userId1, name: "Club 2", acronym: "C2")
|
|
try await clubColA.addOrUpdateAsync(instance: club2A)
|
|
|
|
let eventA = Event(creator: userId1, club: club1A.id, name: "event 1")
|
|
try await eventColA.addOrUpdateAsync(instance: eventA)
|
|
|
|
// Share with user2
|
|
try await self.storeCenterA.setAuthorizedUsersAsync(for: eventA, users: [userId2])
|
|
let _ = try await self.storeCenterB.testSynchronizeOnceAsync()
|
|
#expect(eventColB.count == 1)
|
|
#expect(clubColB.count == 1)
|
|
|
|
// Change the club
|
|
eventA.club = club2A.id
|
|
try await eventColA.addOrUpdateAsync(instance: eventA)
|
|
|
|
let dataB = try await self.storeCenterB.testSynchronizeOnceAsync()
|
|
|
|
let syncDataB = try SyncData(data: dataB, storeCenter: self.storeCenterB)
|
|
|
|
#expect(syncDataB.updates.count == 1) // event update
|
|
#expect(syncDataB.sharedRelationshipSets.count == 1)
|
|
#expect(syncDataB.sharedRelationshipRemovals.count == 1)
|
|
|
|
print("club1A = \(club1A.id)")
|
|
#expect(eventColB.first?.club == club2A.id)
|
|
}
|
|
|
|
/// In this test, the first user:
|
|
/// - creates one event and 2 clubs
|
|
/// - shares the event with a second user
|
|
/// - changes the club on the event
|
|
/// Here we want to test that the first Club is removed and the second one is received
|
|
@Test func testRelationshipChangeFromSharedUser() async throws {
|
|
|
|
guard let userId1 = self.storeCenterA.userId else {
|
|
throw TestError.notAuthenticated
|
|
}
|
|
guard let userId2 = self.storeCenterB.userId else {
|
|
throw TestError.notAuthenticated
|
|
}
|
|
|
|
// Setup
|
|
let eventColA: SyncedCollection<Event> = await self.storeCenterA.mainStore.asyncLoadingSynchronizedCollection()
|
|
let clubColA: SyncedCollection<Club> = await self.storeCenterA.mainStore.asyncLoadingSynchronizedCollection()
|
|
let eventColB: SyncedCollection<Event> = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection()
|
|
let clubColB: SyncedCollection<Club> = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection()
|
|
|
|
if let dataAccessCollection = self.storeCenterA.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.storeCenterB.testSynchronizeOnceAsync()
|
|
|
|
#expect(eventColB.count == 0)
|
|
#expect(clubColB.count == 0)
|
|
|
|
let eventA = Event(creator: userId1, name: "event 1")
|
|
try await eventColA.addOrUpdateAsync(instance: eventA)
|
|
|
|
// Share with user2
|
|
try await self.storeCenterA.setAuthorizedUsersAsync(for: eventA, users: [userId2])
|
|
let _ = try await self.storeCenterB.testSynchronizeOnceAsync()
|
|
|
|
#expect(eventColB.count == 1)
|
|
let eventB = eventColB.first!
|
|
|
|
// Create
|
|
let club1B = Club(creator: userId2, name: "Club 1", acronym: "C1")
|
|
try await clubColB.addOrUpdateAsync(instance: club1B)
|
|
|
|
// Change the club
|
|
eventB.club = club1B.id
|
|
try await eventColB.addOrUpdateAsync(instance: eventB)
|
|
|
|
let dataA = try await self.storeCenterA.testSynchronizeOnceAsync()
|
|
|
|
print("club1A = \(club1B.id)")
|
|
#expect(eventColA.first != nil)
|
|
#expect(eventColA.first?.club == club1B.id)
|
|
#expect(clubColA.first?.id == club1B.id)
|
|
|
|
}
|
|
|
|
/// 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 = self.storeCenterA.userId else {
|
|
throw TestError.notAuthenticated
|
|
}
|
|
guard let userId2 = self.storeCenterB.userId else {
|
|
throw TestError.notAuthenticated
|
|
}
|
|
|
|
// Setup
|
|
let eventColA: SyncedCollection<Event> = await self.storeCenterA.mainStore.asyncLoadingSynchronizedCollection()
|
|
let clubColA: SyncedCollection<Club> = await self.storeCenterA.mainStore.asyncLoadingSynchronizedCollection()
|
|
let courtsColA: SyncedCollection<Court> = await self.storeCenterA.mainStore.asyncLoadingSynchronizedCollection()
|
|
let eventColB: SyncedCollection<Event> = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection()
|
|
let clubColB: SyncedCollection<Club> = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection()
|
|
let courtsColB: SyncedCollection<Court> = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection()
|
|
|
|
if let dataAccessCollection = self.storeCenterA.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.storeCenterB.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)
|
|
|
|
let court1A = Court(index: 0, club: clubA.id)
|
|
let court2A = Court(index: 1, club: clubA.id)
|
|
try await courtsColA.addOrUpdateAsync(contentOfs: [court1A, court2A])
|
|
|
|
// Share with user2
|
|
try await self.storeCenterA.setAuthorizedUsersAsync(for: eventA, users: [userId2])
|
|
let _ = try await self.storeCenterB.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 data = try await self.storeCenterA.testSynchronizeOnceAsync()
|
|
let syncData = try SyncData(data: data, storeCenter: self.storeCenterB)
|
|
|
|
#expect(eventA.club == club2B.id)
|
|
#expect(clubColB.count == 1)
|
|
#expect(courtsColB.count == 0)
|
|
|
|
}
|
|
|
|
@Test func testBuildEverything() async throws {
|
|
|
|
guard let _ = StoreCenter.main.userId else {
|
|
throw TestError.notAuthenticated
|
|
}
|
|
guard let userId2 = self.storeCenterB.userId else {
|
|
throw TestError.notAuthenticated
|
|
}
|
|
|
|
// Cleanup
|
|
|
|
let tournamentColA: SyncedCollection<Tournament> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
|
|
let tournamentColB: SyncedCollection<Tournament> = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection()
|
|
|
|
let tournamentsToDelete: [Tournament] = try await StoreCenter.main.service().get()
|
|
try await tournamentColA.deleteAsync(contentOfs: tournamentsToDelete)
|
|
|
|
// Setup tournament + build everything
|
|
let tournament = Tournament()
|
|
|
|
try await tournamentColA.addOrUpdateAsync(instance: tournament)
|
|
try await tournament.deleteAndBuildEverythingAsync()
|
|
let tourStoreA = try StoreCenter.main.store(identifier: tournament.id)
|
|
|
|
let gsColA: SyncedCollection<GroupStage> = try tourStoreA.syncedCollection()
|
|
let roundColA: SyncedCollection<Round> = try tourStoreA.syncedCollection()
|
|
let matchesColA: SyncedCollection<Match> = try tourStoreA.syncedCollection()
|
|
#expect(gsColA.count == 4)
|
|
#expect(roundColA.count == 15)
|
|
#expect(matchesColA.count == 56)
|
|
|
|
// todo add sharing
|
|
try await StoreCenter.main.setAuthorizedUsersAsync(for: tournament, users: [userId2])
|
|
|
|
// Sync with 2nd store
|
|
try await self.storeCenterB.testSynchronizeOnceAsync()
|
|
#expect(tournamentColB.count == 1)
|
|
|
|
let tourStoreB = try self.storeCenterB.store(identifier: tournament.id)
|
|
|
|
let gsColB: SyncedCollection<GroupStage> = await tourStoreB.asyncLoadingSynchronizedCollection()
|
|
let roundColB: SyncedCollection<Round> = await tourStoreB.asyncLoadingSynchronizedCollection()
|
|
let matchesColB: SyncedCollection<Match> = await tourStoreB.asyncLoadingSynchronizedCollection()
|
|
#expect(gsColB.count == 4)
|
|
#expect(roundColB.count == 15)
|
|
#expect(matchesColB.count == 56)
|
|
|
|
// change setup + build everything
|
|
tournament.groupStageCount = 2
|
|
tournament.teamCount = 20
|
|
try await tournamentColA.addOrUpdateAsync(instance: tournament)
|
|
try await tournament.deleteAndBuildEverythingAsync()
|
|
|
|
#expect(gsColA.count == 2)
|
|
#expect(roundColA.count == 15)
|
|
#expect(matchesColA.count == 44)
|
|
|
|
// Sync with 2nd store
|
|
let data = try await self.storeCenterB.testSynchronizeOnceAsync()
|
|
let _ = try SyncData(data: data, storeCenter: self.storeCenterB)
|
|
|
|
#expect(gsColB.count == 2)
|
|
#expect(roundColB.count == 15)
|
|
#expect(matchesColB.count == 44)
|
|
|
|
}
|
|
|
|
@Test func testBuildEverythingFromShared() async throws {
|
|
|
|
guard let _ = StoreCenter.main.userId else {
|
|
throw TestError.notAuthenticated
|
|
}
|
|
guard let userId2 = self.storeCenterB.userId else {
|
|
throw TestError.notAuthenticated
|
|
}
|
|
|
|
// Cleanup
|
|
let tournamentColA: SyncedCollection<Tournament> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
|
|
let tournamentColB: SyncedCollection<Tournament> = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection()
|
|
|
|
let tournamentsToDelete: [Tournament] = try await StoreCenter.main.service().get()
|
|
try await tournamentColA.deleteAsync(contentOfs: tournamentsToDelete)
|
|
|
|
// Setup tournament + build everything
|
|
let tournament = Tournament()
|
|
try await tournamentColA.addOrUpdateAsync(instance: tournament)
|
|
|
|
// todo add sharing
|
|
try await StoreCenter.main.setAuthorizedUsersAsync(for: tournament, users: [userId2])
|
|
|
|
// Sync with 2nd store
|
|
try await self.storeCenterB.testSynchronizeOnceAsync()
|
|
#expect(tournamentColB.count == 1)
|
|
|
|
let tournamentB = tournamentColB.first!
|
|
try await tournamentB.deleteAndBuildEverythingAsync()
|
|
|
|
#expect(tournamentColB.count == 1)
|
|
|
|
// Sync with 2nd store
|
|
let data = try await self.storeCenterA.testSynchronizeOnceAsync()
|
|
let _ = try SyncData(data: data, storeCenter: self.storeCenterA)
|
|
|
|
#expect(tournamentColA.count == 1)
|
|
|
|
}
|
|
|
|
@Test func testTournamentSharingTeamScoreIssue() async throws {
|
|
guard let userId2 = self.storeCenterB.userId else {
|
|
throw TestError.notAuthenticated
|
|
}
|
|
|
|
// Setup tournament
|
|
let tournamentColA: SyncedCollection<Tournament> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
|
|
|
|
let tournament = Tournament(name: "test_data_access_children")
|
|
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)
|
|
let match = Match(round: round.id)
|
|
let ts1 = TeamScore(match: match.id, team: tr1)
|
|
let ts2 = TeamScore(match: match.id, team: tr2)
|
|
|
|
try await roundColA.addOrUpdateAsync(instance: round)
|
|
try await matchColA.addOrUpdateAsync(instance: match)
|
|
try await teamScoreColA.addOrUpdateAsync(contentOfs: [ts1, ts2])
|
|
|
|
try await StoreCenter.main.setAuthorizedUsersAsync(for: tournament, users: [userId2])
|
|
|
|
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(tournamentColB.count == 1)
|
|
|
|
let tourStoreB = try self.storeCenterB.store(identifier: tournament.id)
|
|
let matchColB: SyncedCollection<Match> = await tourStoreB.asyncLoadingSynchronizedCollection()
|
|
let teamScoreColB: SyncedCollection<TeamScore> = await tourStoreB.asyncLoadingSynchronizedCollection()
|
|
|
|
#expect(teamScoreColB.count == 2)
|
|
|
|
let matchB = matchColB.first!
|
|
matchB.courtIndex = 2
|
|
try await matchColB.addOrUpdateAsync(instance: matchB)
|
|
|
|
#expect(teamScoreColB.count == 2)
|
|
|
|
}
|
|
|
|
@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()
|
|
|
|
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
|
|
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)
|
|
let match = Match(round: round.id)
|
|
let ts1 = TeamScore(match: match.id, team: tr1)
|
|
let ts2 = TeamScore(match: match.id, team: tr2)
|
|
|
|
try await roundColA.addOrUpdateAsync(instance: round)
|
|
try await matchColA.addOrUpdateAsync(instance: match)
|
|
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)
|
|
|
|
#expect(syncData.shared.count == 1)
|
|
|
|
let tournamentColB: SyncedCollection<Tournament> = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection()
|
|
|
|
#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 tournamentColA.deleteAsync(instance: tournament)
|
|
let tournaments: [Tournament] = try await StoreCenter.main.service().get()
|
|
#expect(tournaments.count == 0)
|
|
|
|
#expect(tournamentColA.count == 0)
|
|
|
|
let data2 = try await self.storeCenterB.testSynchronizeOnceAsync()
|
|
let syncData2 = try SyncData(data: data2, storeCenter: self.storeCenterB)
|
|
|
|
#expect(tournamentColB.count == 0)
|
|
|
|
#expect(syncData2.deletions.count > 0)
|
|
|
|
#expect(matchColB.count == 0)
|
|
#expect(playerRegColB.count == 0)
|
|
#expect(teamRegColB.count == 0)
|
|
|
|
}
|
|
|
|
// needs to run on a postgreSQL, otherwise fails because of sqlite database locks
|
|
@Test func testDataAccessForChildren() async throws {
|
|
|
|
guard let userId2 = self.storeCenterB.userId else {
|
|
throw TestError.notAuthenticated
|
|
}
|
|
// Setup tournament
|
|
let tournamentColA: SyncedCollection<Tournament> = await StoreCenter.main.mainStore.asyncLoadingSynchronizedCollection()
|
|
let tournamentColB: SyncedCollection<Tournament> = await self.storeCenterB.mainStore.asyncLoadingSynchronizedCollection()
|
|
|
|
let tournament = Tournament(name: "test_data_access_children")
|
|
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()
|
|
|
|
// cleanup sync residues
|
|
let _ = try await self.storeCenterB.testSynchronizeOnceAsync()
|
|
|
|
try await StoreCenter.main.setAuthorizedUsersAsync(for: tournament, users: [userId2])
|
|
|
|
var teamRegistrations: [TeamRegistration] = []
|
|
var playerRegistrations: [PlayerRegistration] = []
|
|
|
|
let count = 5
|
|
for i in (0..<count) {
|
|
let tr = TeamRegistration(tournament: tournament.id, name: "Team \(i)")
|
|
let pr1 = PlayerRegistration(teamRegistration: tr.id, firstName: "f1\(i)", lastName: "l1\(i)")
|
|
let pr2 = PlayerRegistration(teamRegistration: tr.id, firstName: "f2\(i)", lastName: "l2\(i)")
|
|
teamRegistrations.append(tr)
|
|
playerRegistrations.append(contentsOf: [pr1, pr2])
|
|
}
|
|
|
|
try await teamRegColA.addOrUpdateAsync(contentOfs: teamRegistrations)
|
|
try await playerRegColA.addOrUpdateAsync(contentOfs: playerRegistrations)
|
|
|
|
let _ = try await self.storeCenterB.testSynchronizeOnceAsync()
|
|
|
|
let tourStoreB = try self.storeCenterB.store(identifier: tournament.id)
|
|
let teamRegColB: SyncedCollection<TeamRegistration> = await tourStoreB.asyncLoadingSynchronizedCollection()
|
|
let playerRegColB: SyncedCollection<PlayerRegistration> = await tourStoreB.asyncLoadingSynchronizedCollection()
|
|
|
|
#expect(tournamentColB.count == 1)
|
|
#expect(teamRegColB.count == count)
|
|
#expect(playerRegColB.count == count * 2)
|
|
|
|
for team in teamRegistrations {
|
|
try await teamRegColA.deleteAsync(instance: team)
|
|
}
|
|
|
|
try await Task.sleep(for: .milliseconds(100)) // without this, it looks like the sync date is smaller than the ModelLogs, so we don't get them all
|
|
|
|
let data = try await self.storeCenterB.testSynchronizeOnceAsync()
|
|
let syncData = try SyncData(data: data, storeCenter: self.storeCenterB)
|
|
|
|
#expect(syncData.deletions.count == 2)
|
|
|
|
let teamRegDeletions = syncData.deletions.first(where: { $0.type == TeamRegistration.self })
|
|
#expect(teamRegDeletions?.items.count == 5)
|
|
let playerRegDeletions = syncData.deletions.first(where: { $0.type == PlayerRegistration.self })
|
|
#expect(playerRegDeletions?.items.count == 10)
|
|
|
|
#expect(teamRegColB.count == 0)
|
|
#expect(playerRegColB.count == 0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
extension Tournament {
|
|
|
|
@MainActor
|
|
public func deleteAndBuildEverythingAsync(preset: PadelTournamentStructurePreset = .manual) async throws {
|
|
resetBracketPosition()
|
|
try await deleteStructureAsync()
|
|
try await deleteGroupStagesAsync()
|
|
|
|
switch preset {
|
|
case .doubleGroupStage:
|
|
try await buildGroupStagesAsync()
|
|
try await addNewGroupStageStepAsync()
|
|
qualifiedPerGroupStage = 0
|
|
groupStageAdditionalQualified = 0
|
|
default:
|
|
try await buildGroupStagesAsync()
|
|
try await buildBracketAsync()
|
|
}
|
|
}
|
|
|
|
public func addNewGroupStageStepAsync() async throws {
|
|
let lastStep = lastStep() + 1
|
|
for i in 0..<teamsPerGroupStage {
|
|
let gs = GroupStage(tournament: id, index: i, size: groupStageCount, step: lastStep)
|
|
try await self.tournamentStore?.groupStages.addOrUpdateAsync(instance: gs)
|
|
}
|
|
|
|
groupStages(atStep: 1).forEach { $0.buildMatches() }
|
|
}
|
|
|
|
public func deleteStructureAsync() async throws {
|
|
try await self.tournamentStore?.rounds.deleteAsync(contentOfs: rounds())
|
|
}
|
|
|
|
public func deleteGroupStagesAsync() async throws {
|
|
try await self.tournamentStore?.groupStages.deleteAsync(contentOfs: allGroupStages())
|
|
}
|
|
|
|
func buildGroupStagesAsync() async throws {
|
|
guard groupStages().isEmpty, let _ = self.tournamentStore else {
|
|
return
|
|
}
|
|
|
|
var _groupStages = [GroupStage]()
|
|
for index in 0..<groupStageCount {
|
|
let groupStage = GroupStage(tournament: id, index: index, size: teamsPerGroupStage, format: groupStageFormat)
|
|
_groupStages.append(groupStage)
|
|
}
|
|
|
|
try await self.tournamentStore?.groupStages.addOrUpdateAsync(contentOfs: _groupStages)
|
|
try await refreshGroupStagesAsync()
|
|
}
|
|
|
|
public func refreshGroupStagesAsync(keepExistingMatches: Bool = false) async throws {
|
|
unsortedTeams().forEach { team in
|
|
team.groupStage = nil
|
|
team.groupStagePosition = nil
|
|
}
|
|
|
|
if groupStageCount > 0 {
|
|
switch groupStageOrderingMode {
|
|
case .random:
|
|
try await setGroupStageAsync(randomize: true, keepExistingMatches: keepExistingMatches)
|
|
case .snake:
|
|
try await setGroupStageAsync(randomize: false, keepExistingMatches: keepExistingMatches)
|
|
case .swiss:
|
|
try await setGroupStageAsync(randomize: true, keepExistingMatches: keepExistingMatches)
|
|
}
|
|
}
|
|
}
|
|
|
|
public func setGroupStageAsync(randomize: Bool, keepExistingMatches: Bool = false) async throws {
|
|
let groupStages = groupStages()
|
|
let numberOfBracketsAsInt = groupStages.count
|
|
// let teamsPerBracket = teamsPerBracket
|
|
if groupStageCount != numberOfBracketsAsInt {
|
|
try await deleteGroupStagesAsync()
|
|
try await buildGroupStagesAsync()
|
|
} else {
|
|
// setGroupStageTeams(randomize: randomize)
|
|
for groupStage in groupStages {
|
|
try await groupStage.buildMatchesAsync(keepExistingMatches: keepExistingMatches)
|
|
}
|
|
}
|
|
}
|
|
|
|
public func buildBracketAsync(minimalBracketTeamCount: Int? = nil) async throws {
|
|
guard rounds().isEmpty else { return }
|
|
let roundCount = RoundRule.numberOfRounds(forTeams: minimalBracketTeamCount ?? bracketTeamCount())
|
|
let matchCount = RoundRule.numberOfMatches(forTeams: minimalBracketTeamCount ?? bracketTeamCount())
|
|
|
|
let rounds = (0..<roundCount).map { //index 0 is the final
|
|
return Round(tournament: id, index: $0, format: matchFormat, loserBracketMode: loserBracketMode)
|
|
}
|
|
|
|
if rounds.isEmpty {
|
|
return
|
|
}
|
|
|
|
try await self.tournamentStore?.rounds.addOrUpdateAsync(contentOfs: rounds)
|
|
|
|
let matches = (0..<matchCount).map { //0 is final match
|
|
let roundIndex = RoundRule.roundIndex(fromMatchIndex: $0)
|
|
let round = rounds[roundIndex]
|
|
return Match(round: round.id, index: $0, format: round.matchFormat, name: Match.setServerTitle(upperRound: round, matchIndex: RoundRule.matchIndexWithinRound(fromMatchIndex: $0)))
|
|
}
|
|
|
|
try await self.tournamentStore?.matches.addOrUpdateAsync(contentOfs: matches)
|
|
|
|
for round in rounds {
|
|
try await round.buildLoserBracketAsync()
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
extension GroupStage {
|
|
public func buildMatchesAsync(keepExistingMatches: Bool = false) async throws {
|
|
var teamScores = [TeamScore]()
|
|
var matches = [Match]()
|
|
clearScoreCache()
|
|
|
|
if keepExistingMatches == false {
|
|
_removeMatches()
|
|
|
|
for i in 0..<_numberOfMatchesToBuild() {
|
|
let newMatch = self.createMatch(index: i)
|
|
// let newMatch = Match(groupStage: self.id, index: i, matchFormat: self.matchFormat, name: localizedMatchUpLabel(for: i))
|
|
teamScores.append(contentsOf: newMatch.createTeamScores())
|
|
matches.append(newMatch)
|
|
}
|
|
} else {
|
|
for match in self.matches() {
|
|
match.resetTeamScores(outsideOf: [])
|
|
teamScores.append(contentsOf: match.createTeamScores())
|
|
}
|
|
}
|
|
|
|
try await self.tournamentStore?.matches.addOrUpdateAsync(contentOfs: matches)
|
|
try await self.tournamentStore?.teamScores.addOrUpdateAsync(contentOfs: teamScores)
|
|
}
|
|
|
|
}
|
|
|
|
extension Round {
|
|
|
|
public func buildLoserBracketAsync() async throws {
|
|
guard loserRounds().isEmpty else { return }
|
|
self.invalidateCache()
|
|
let currentRoundMatchCount = RoundRule.numberOfMatches(forRoundIndex: index)
|
|
guard currentRoundMatchCount > 1 else { return }
|
|
guard let tournamentStore else { return }
|
|
let roundCount = RoundRule.numberOfRounds(forTeams: currentRoundMatchCount)
|
|
let loserBracketMatchFormat = tournamentObject()?.loserBracketMatchFormat
|
|
|
|
let rounds = (0..<roundCount).map { //index 0 is the final
|
|
let round = Round(tournament: tournament, index: $0, format: loserBracketMatchFormat)
|
|
round.parent = id //parent
|
|
//titles[round.id] = round.roundTitle(initialMode: true)
|
|
return round
|
|
}
|
|
try await tournamentStore.rounds.addOrUpdateAsync(contentOfs: rounds)
|
|
let matchCount = RoundRule.numberOfMatches(forTeams: currentRoundMatchCount)
|
|
|
|
let matches = (0..<matchCount).map { //0 is final match
|
|
let roundIndex = RoundRule.roundIndex(fromMatchIndex: $0)
|
|
let round = rounds[roundIndex]
|
|
//let title = titles[round.id]
|
|
return Match(round: round.id, index: $0, format: loserBracketMatchFormat)
|
|
//initial mode let the roundTitle give a name without considering the playable match
|
|
}
|
|
|
|
try await tournamentStore.matches.addOrUpdateAsync(contentOfs: matches)
|
|
|
|
for round in rounds {
|
|
try await round.buildLoserBracketAsync()
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|