// // MockAppDataProvider.swift // SportsTimeTests // // Mock implementation of AppDataProvider for testing without SwiftData dependencies. // import Foundation import Combine @testable import SportsTime // MARK: - Mock App Data Provider @MainActor final class MockAppDataProvider: ObservableObject { // MARK: - Published State @Published private(set) var teams: [Team] = [] @Published private(set) var stadiums: [Stadium] = [] @Published private(set) var dynamicSports: [DynamicSport] = [] @Published private(set) var isLoading = false @Published private(set) var error: Error? @Published private(set) var errorMessage: String? // MARK: - Internal Storage private var teamsById: [String: Team] = [:] private var stadiumsById: [String: Stadium] = [:] private var dynamicSportsById: [String: DynamicSport] = [:] private var games: [Game] = [] private var gamesById: [String: Game] = [:] // MARK: - Configuration struct Configuration { var simulatedLatency: TimeInterval = 0 var shouldFailOnLoad: Bool = false var shouldFailOnFetch: Bool = false var isEmpty: Bool = false static var `default`: Configuration { Configuration() } static var empty: Configuration { Configuration(isEmpty: true) } static var failing: Configuration { Configuration(shouldFailOnLoad: true) } static var slow: Configuration { Configuration(simulatedLatency: 1.0) } } private var config: Configuration // MARK: - Call Tracking private(set) var loadInitialDataCallCount = 0 private(set) var filterGamesCallCount = 0 private(set) var filterRichGamesCallCount = 0 private(set) var allGamesCallCount = 0 private(set) var allRichGamesCallCount = 0 // MARK: - Initialization init(config: Configuration = .default) { self.config = config } // MARK: - Configuration Methods func configure(_ newConfig: Configuration) { self.config = newConfig } func setTeams(_ newTeams: [Team]) { self.teams = newTeams self.teamsById = Dictionary(uniqueKeysWithValues: newTeams.map { ($0.id, $0) }) } func setStadiums(_ newStadiums: [Stadium]) { self.stadiums = newStadiums self.stadiumsById = Dictionary(uniqueKeysWithValues: newStadiums.map { ($0.id, $0) }) } func setGames(_ newGames: [Game]) { self.games = newGames self.gamesById = Dictionary(uniqueKeysWithValues: newGames.map { ($0.id, $0) }) } func setDynamicSports(_ newSports: [DynamicSport]) { self.dynamicSports = newSports self.dynamicSportsById = Dictionary(uniqueKeysWithValues: newSports.map { ($0.id, $0) }) } func reset() { teams = [] stadiums = [] dynamicSports = [] games = [] teamsById = [:] stadiumsById = [:] dynamicSportsById = [:] gamesById = [:] isLoading = false error = nil errorMessage = nil loadInitialDataCallCount = 0 filterGamesCallCount = 0 filterRichGamesCallCount = 0 allGamesCallCount = 0 allRichGamesCallCount = 0 config = .default } // MARK: - Simulated Network private func simulateLatency() async { if config.simulatedLatency > 0 { try? await Task.sleep(nanoseconds: UInt64(config.simulatedLatency * 1_000_000_000)) } } // MARK: - Data Loading func loadInitialData() async { loadInitialDataCallCount += 1 if config.isEmpty { teams = [] stadiums = [] return } isLoading = true error = nil errorMessage = nil await simulateLatency() if config.shouldFailOnLoad { error = DataProviderError.contextNotConfigured errorMessage = "Mock load failure" isLoading = false return } isLoading = false } func clearError() { error = nil errorMessage = nil } func retry() async { await loadInitialData() } // MARK: - Data Access func team(for id: String) -> Team? { teamsById[id] } func stadium(for id: String) -> Stadium? { stadiumsById[id] } func teams(for sport: Sport) -> [Team] { teams.filter { $0.sport == sport } } func dynamicSport(for id: String) -> DynamicSport? { dynamicSportsById[id] } /// All sports: built-in Sport enum cases + CloudKit-defined DynamicSports var allSports: [any AnySport] { let builtIn: [any AnySport] = Sport.allCases let dynamic: [any AnySport] = dynamicSports return builtIn + dynamic } // MARK: - Game Filtering (Local Queries) func filterGames(sports: Set, startDate: Date, endDate: Date) async throws -> [Game] { filterGamesCallCount += 1 await simulateLatency() if config.shouldFailOnFetch { throw DataProviderError.contextNotConfigured } return games.filter { game in sports.contains(game.sport) && game.dateTime >= startDate && game.dateTime <= endDate }.sorted { $0.dateTime < $1.dateTime } } func allGames(for sports: Set) async throws -> [Game] { allGamesCallCount += 1 await simulateLatency() if config.shouldFailOnFetch { throw DataProviderError.contextNotConfigured } return games.filter { game in sports.contains(game.sport) }.sorted { $0.dateTime < $1.dateTime } } func fetchGame(by id: String) async throws -> Game? { await simulateLatency() if config.shouldFailOnFetch { throw DataProviderError.contextNotConfigured } return gamesById[id] } func filterRichGames(sports: Set, startDate: Date, endDate: Date) async throws -> [RichGame] { filterRichGamesCallCount += 1 let filteredGames = try await filterGames(sports: sports, startDate: startDate, endDate: endDate) return filteredGames.compactMap { game in richGame(from: game) } } func allRichGames(for sports: Set) async throws -> [RichGame] { allRichGamesCallCount += 1 let allFilteredGames = try await allGames(for: sports) return allFilteredGames.compactMap { game in richGame(from: game) } } func richGame(from game: Game) -> RichGame? { guard let homeTeam = teamsById[game.homeTeamId], let awayTeam = teamsById[game.awayTeamId], let stadium = stadiumsById[game.stadiumId] else { return nil } return RichGame(game: game, homeTeam: homeTeam, awayTeam: awayTeam, stadium: stadium) } } // MARK: - Convenience Extensions extension MockAppDataProvider { /// Load fixture data from FixtureGenerator func loadFixtures(_ data: FixtureGenerator.GeneratedData) { setTeams(data.teams) setStadiums(data.stadiums) setGames(data.games) } /// Create a mock provider with fixture data pre-loaded static func withFixtures(_ config: FixtureGenerator.Configuration = .default) -> MockAppDataProvider { let mock = MockAppDataProvider() let data = FixtureGenerator.generate(with: config) mock.loadFixtures(data) return mock } /// Create a mock provider configured as empty static var empty: MockAppDataProvider { MockAppDataProvider(config: .empty) } /// Create a mock provider configured to fail static var failing: MockAppDataProvider { MockAppDataProvider(config: .failing) } } // MARK: - Test Helpers extension MockAppDataProvider { /// Add a single game func addGame(_ game: Game) { games.append(game) gamesById[game.id] = game } /// Add a single team func addTeam(_ team: Team) { teams.append(team) teamsById[team.id] = team } /// Add a single stadium func addStadium(_ stadium: Stadium) { stadiums.append(stadium) stadiumsById[stadium.id] = stadium } /// Get all stored games (for test verification) func getAllStoredGames() -> [Game] { games } /// Get games count var gamesCount: Int { games.count } /// Get teams count var teamsCount: Int { teams.count } /// Get stadiums count var stadiumsCount: Int { stadiums.count } /// Add a single dynamic sport func addDynamicSport(_ sport: DynamicSport) { dynamicSports.append(sport) dynamicSportsById[sport.id] = sport } /// Get dynamic sports count var dynamicSportsCount: Int { dynamicSports.count } }