test(planning): complete test suite with Phase 11 edge cases
Implement comprehensive test infrastructure and all 124 tests across 11 phases: - Phase 0: Test infrastructure (fixtures, mocks, helpers) - Phases 1-10: Core planning engine tests (previously implemented) - Phase 11: Edge case omnibus (11 new tests) - Data edge cases: nil stadiums, malformed dates, invalid coordinates - Boundary conditions: driving limits, radius boundaries - Time zone cases: cross-timezone games, DST transitions Reorganize test structure under Planning/ directory with proper organization. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
264
SportsTimeTests/Mocks/MockAppDataProvider.swift
Normal file
264
SportsTimeTests/Mocks/MockAppDataProvider.swift
Normal file
@@ -0,0 +1,264 @@
|
||||
//
|
||||
// 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 isLoading = false
|
||||
@Published private(set) var error: Error?
|
||||
@Published private(set) var errorMessage: String?
|
||||
|
||||
// MARK: - Internal Storage
|
||||
|
||||
private var teamsById: [UUID: Team] = [:]
|
||||
private var stadiumsById: [UUID: Stadium] = [:]
|
||||
private var games: [Game] = []
|
||||
private var gamesById: [UUID: 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 fetchGamesCallCount = 0
|
||||
private(set) var fetchRichGamesCallCount = 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 reset() {
|
||||
teams = []
|
||||
stadiums = []
|
||||
games = []
|
||||
teamsById = [:]
|
||||
stadiumsById = [:]
|
||||
gamesById = [:]
|
||||
isLoading = false
|
||||
error = nil
|
||||
errorMessage = nil
|
||||
loadInitialDataCallCount = 0
|
||||
fetchGamesCallCount = 0
|
||||
fetchRichGamesCallCount = 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: UUID) -> Team? {
|
||||
teamsById[id]
|
||||
}
|
||||
|
||||
func stadium(for id: UUID) -> Stadium? {
|
||||
stadiumsById[id]
|
||||
}
|
||||
|
||||
func teams(for sport: Sport) -> [Team] {
|
||||
teams.filter { $0.sport == sport }
|
||||
}
|
||||
|
||||
// MARK: - Game Fetching
|
||||
|
||||
func fetchGames(sports: Set<Sport>, startDate: Date, endDate: Date) async throws -> [Game] {
|
||||
fetchGamesCallCount += 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 fetchGame(by id: UUID) async throws -> Game? {
|
||||
await simulateLatency()
|
||||
|
||||
if config.shouldFailOnFetch {
|
||||
throw DataProviderError.contextNotConfigured
|
||||
}
|
||||
|
||||
return gamesById[id]
|
||||
}
|
||||
|
||||
func fetchRichGames(sports: Set<Sport>, startDate: Date, endDate: Date) async throws -> [RichGame] {
|
||||
fetchRichGamesCallCount += 1
|
||||
let filteredGames = try await fetchGames(sports: sports, startDate: startDate, endDate: endDate)
|
||||
|
||||
return filteredGames.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 games (for test verification)
|
||||
func allGames() -> [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 }
|
||||
}
|
||||
Reference in New Issue
Block a user