// // ScenarioCPlannerTests.swift // SportsTimeTests // // Comprehensive tests for ScenarioCPlanner: Directional route planning. // Tests start/end location validation, directional stadium filtering, // monotonic progress, and date range generation. // import Testing import Foundation import CoreLocation @testable import SportsTime @Suite("ScenarioCPlanner Tests") struct ScenarioCPlannerTests { // MARK: - Test Fixtures private func makeStadium( id: UUID = UUID(), name: String, city: String, state: String, latitude: Double, longitude: Double ) -> Stadium { Stadium( id: id, name: name, city: city, state: state, latitude: latitude, longitude: longitude, capacity: 40000 ) } private func makeGame( id: UUID = UUID(), stadiumId: UUID, date: Date ) -> Game { Game( id: id, homeTeamId: UUID(), awayTeamId: UUID(), stadiumId: stadiumId, dateTime: date, sport: .mlb, season: "2026" ) } private func date(_ string: String) -> Date { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm" formatter.timeZone = TimeZone(identifier: "America/Los_Angeles") return formatter.date(from: string)! } private func makeRequest( games: [Game], stadiums: [UUID: Stadium], startLocation: LocationInput?, endLocation: LocationInput?, startDate: Date, endDate: Date, tripDuration: Int? = nil, numberOfDrivers: Int = 1 ) -> PlanningRequest { let prefs = TripPreferences( startLocation: startLocation, endLocation: endLocation, startDate: startDate, endDate: endDate, tripDuration: tripDuration, numberOfDrivers: numberOfDrivers ) return PlanningRequest( preferences: prefs, availableGames: games, teams: [:], stadiums: stadiums ) } // West Coast stadiums for testing directional routes private var sdStadium: Stadium { makeStadium(id: UUID(), name: "Petco Park", city: "San Diego", state: "CA", latitude: 32.7076, longitude: -117.1570) } private var laStadium: Stadium { makeStadium(id: UUID(), name: "Dodger Stadium", city: "Los Angeles", state: "CA", latitude: 34.0739, longitude: -118.2400) } private var sfStadium: Stadium { makeStadium(id: UUID(), name: "Oracle Park", city: "San Francisco", state: "CA", latitude: 37.7786, longitude: -122.3893) } // Cross-country stadiums for directional testing private var chicagoStadium: Stadium { makeStadium(id: UUID(), name: "Wrigley Field", city: "Chicago", state: "IL", latitude: 41.9484, longitude: -87.6553) } private var detroitStadium: Stadium { makeStadium(id: UUID(), name: "Comerica Park", city: "Detroit", state: "MI", latitude: 42.3390, longitude: -83.0485) } private var clevelandStadium: Stadium { makeStadium(id: UUID(), name: "Progressive Field", city: "Cleveland", state: "OH", latitude: 41.4962, longitude: -81.6852) } private var pittsburghStadium: Stadium { makeStadium(id: UUID(), name: "PNC Park", city: "Pittsburgh", state: "PA", latitude: 40.4469, longitude: -80.0057) } private var nyStadium: Stadium { makeStadium(id: UUID(), name: "Yankee Stadium", city: "New York", state: "NY", latitude: 40.8296, longitude: -73.9262) } private var minneapolisStadium: Stadium { makeStadium(id: UUID(), name: "Target Field", city: "Minneapolis", state: "MN", latitude: 44.9817, longitude: -93.2776) } // MARK: - Basic Success Tests @Test("Single game between start and end succeeds") func plan_SingleGameBetweenStartAndEnd_Succeeds() { let planner = ScenarioCPlanner() let chicago = chicagoStadium let cleveland = clevelandStadium let ny = nyStadium let game = makeGame(stadiumId: cleveland.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "Chicago", coordinate: CLLocationCoordinate2D(latitude: chicago.latitude, longitude: chicago.longitude) ) let endLoc = LocationInput( name: "New York", coordinate: CLLocationCoordinate2D(latitude: ny.latitude, longitude: ny.longitude) ) let request = makeRequest( games: [game], stadiums: [chicago.id: chicago, cleveland.id: cleveland, ny.id: ny], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { #expect(!options.isEmpty) } else { Issue.record("Expected success") } } @Test("Multiple games along route succeeds") func plan_MultipleGamesAlongRoute_Succeeds() { let planner = ScenarioCPlanner() let chicago = chicagoStadium let cleveland = clevelandStadium let pittsburgh = pittsburghStadium let ny = nyStadium let game1 = makeGame(stadiumId: cleveland.id, date: date("2026-06-10 19:00")) let game2 = makeGame(stadiumId: pittsburgh.id, date: date("2026-06-12 19:00")) let startLoc = LocationInput( name: "Chicago", coordinate: CLLocationCoordinate2D(latitude: chicago.latitude, longitude: chicago.longitude) ) let endLoc = LocationInput( name: "New York", coordinate: CLLocationCoordinate2D(latitude: ny.latitude, longitude: ny.longitude) ) let request = makeRequest( games: [game1, game2], stadiums: [ chicago.id: chicago, cleveland.id: cleveland, pittsburgh.id: pittsburgh, ny.id: ny ], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { #expect(!options.isEmpty) } else { Issue.record("Expected success") } } // MARK: - Missing Location Tests @Test("Missing start location fails") func plan_MissingStartLocation_Fails() { let planner = ScenarioCPlanner() let ny = nyStadium let endLoc = LocationInput( name: "New York", coordinate: CLLocationCoordinate2D(latitude: ny.latitude, longitude: ny.longitude) ) let request = makeRequest( games: [], stadiums: [ny.id: ny], startLocation: nil, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .failure(let failure) = result { #expect(failure.reason == .missingLocations) } else { Issue.record("Expected failure for missing start location") } } @Test("Missing end location fails") func plan_MissingEndLocation_Fails() { let planner = ScenarioCPlanner() let chicago = chicagoStadium let startLoc = LocationInput( name: "Chicago", coordinate: CLLocationCoordinate2D(latitude: chicago.latitude, longitude: chicago.longitude) ) let request = makeRequest( games: [], stadiums: [chicago.id: chicago], startLocation: startLoc, endLocation: nil, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .failure(let failure) = result { #expect(failure.reason == .missingLocations) } else { Issue.record("Expected failure for missing end location") } } @Test("Both locations missing fails") func plan_BothLocationsMissing_Fails() { let planner = ScenarioCPlanner() let request = makeRequest( games: [], stadiums: [:], startLocation: nil, endLocation: nil, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .failure(let failure) = result { #expect(failure.reason == .missingLocations) } else { Issue.record("Expected failure for missing locations") } } @Test("Start location without coordinates fails") func plan_StartLocationNoCoordinates_Fails() { let planner = ScenarioCPlanner() let ny = nyStadium let startLoc = LocationInput(name: "Chicago", coordinate: nil) let endLoc = LocationInput( name: "New York", coordinate: CLLocationCoordinate2D(latitude: ny.latitude, longitude: ny.longitude) ) let request = makeRequest( games: [], stadiums: [ny.id: ny], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .failure(let failure) = result { #expect(failure.reason == .missingLocations) } else { Issue.record("Expected failure for start location without coordinates") } } @Test("End location without coordinates fails") func plan_EndLocationNoCoordinates_Fails() { let planner = ScenarioCPlanner() let chicago = chicagoStadium let startLoc = LocationInput( name: "Chicago", coordinate: CLLocationCoordinate2D(latitude: chicago.latitude, longitude: chicago.longitude) ) let endLoc = LocationInput(name: "New York", coordinate: nil) let request = makeRequest( games: [], stadiums: [chicago.id: chicago], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .failure(let failure) = result { #expect(failure.reason == .missingLocations) } else { Issue.record("Expected failure for end location without coordinates") } } // MARK: - Stadium City Tests @Test("No stadiums in start city fails") func plan_NoStadiumsInStartCity_Fails() { let planner = ScenarioCPlanner() let ny = nyStadium let startLoc = LocationInput( name: "Boston", // No Boston stadium in our list coordinate: CLLocationCoordinate2D(latitude: 42.3601, longitude: -71.0589) ) let endLoc = LocationInput( name: "New York", coordinate: CLLocationCoordinate2D(latitude: ny.latitude, longitude: ny.longitude) ) let request = makeRequest( games: [], stadiums: [ny.id: ny], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .failure(let failure) = result { #expect(failure.reason == .noGamesInRange) #expect(failure.violations.first?.description.contains("start city") == true) } else { Issue.record("Expected failure for no stadiums in start city") } } @Test("No stadiums in end city fails") func plan_NoStadiumsInEndCity_Fails() { let planner = ScenarioCPlanner() let chicago = chicagoStadium let startLoc = LocationInput( name: "Chicago", coordinate: CLLocationCoordinate2D(latitude: chicago.latitude, longitude: chicago.longitude) ) let endLoc = LocationInput( name: "Boston", // No Boston stadium coordinate: CLLocationCoordinate2D(latitude: 42.3601, longitude: -71.0589) ) let request = makeRequest( games: [], stadiums: [chicago.id: chicago], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .failure(let failure) = result { #expect(failure.reason == .noGamesInRange) #expect(failure.violations.first?.description.contains("end city") == true) } else { Issue.record("Expected failure for no stadiums in end city") } } @Test("City name matching is case insensitive") func plan_CityNameMatchingCaseInsensitive() { let planner = ScenarioCPlanner() let chicago = chicagoStadium let ny = nyStadium let cleveland = clevelandStadium let game = makeGame(stadiumId: cleveland.id, date: date("2026-06-10 19:00")) // Use different case for city names let startLoc = LocationInput( name: "CHICAGO", // uppercase coordinate: CLLocationCoordinate2D(latitude: chicago.latitude, longitude: chicago.longitude) ) let endLoc = LocationInput( name: "new york", // lowercase coordinate: CLLocationCoordinate2D(latitude: ny.latitude, longitude: ny.longitude) ) let request = makeRequest( games: [game], stadiums: [chicago.id: chicago, ny.id: ny, cleveland.id: cleveland], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) // Should still work despite case differences if case .failure = result { Issue.record("Should handle case-insensitive city names") } } // MARK: - Directional Filtering Tests @Test("Backtracking stadium is filtered out") func plan_BacktrackingStadium_FilteredOut() { let planner = ScenarioCPlanner() let chicago = chicagoStadium let minneapolis = minneapolisStadium // West of Chicago - wrong direction let ny = nyStadium // Game in Minneapolis - going wrong way from Chicago to NY let game = makeGame(stadiumId: minneapolis.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "Chicago", coordinate: CLLocationCoordinate2D(latitude: chicago.latitude, longitude: chicago.longitude) ) let endLoc = LocationInput( name: "New York", coordinate: CLLocationCoordinate2D(latitude: ny.latitude, longitude: ny.longitude) ) let request = makeRequest( games: [game], stadiums: [chicago.id: chicago, minneapolis.id: minneapolis, ny.id: ny], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) // Should fail because Minneapolis is not directional (west of Chicago when going east to NY) if case .failure = result { // Expected - no valid directional routes } else if case .success(let options) = result { // If it succeeded, Minneapolis should not appear in stops for option in options { for stop in option.stops { #expect(stop.city != "Minneapolis", "Minneapolis should be filtered out") } } } } @Test("Directional stadiums included in route") func plan_DirectionalStadiums_Included() { let planner = ScenarioCPlanner() let chicago = chicagoStadium let cleveland = clevelandStadium let pittsburgh = pittsburghStadium let ny = nyStadium // Cleveland and Pittsburgh are directional (east of Chicago, toward NY) let game1 = makeGame(stadiumId: cleveland.id, date: date("2026-06-10 19:00")) let game2 = makeGame(stadiumId: pittsburgh.id, date: date("2026-06-12 19:00")) let startLoc = LocationInput( name: "Chicago", coordinate: CLLocationCoordinate2D(latitude: chicago.latitude, longitude: chicago.longitude) ) let endLoc = LocationInput( name: "New York", coordinate: CLLocationCoordinate2D(latitude: ny.latitude, longitude: ny.longitude) ) let request = makeRequest( games: [game1, game2], stadiums: [ chicago.id: chicago, cleveland.id: cleveland, pittsburgh.id: pittsburgh, ny.id: ny ], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { #expect(!options.isEmpty) // At least one option should include Cleveland and/or Pittsburgh let allCities = options.flatMap { $0.stops.map { $0.city } } let hasDirectionalCities = allCities.contains("Cleveland") || allCities.contains("Pittsburgh") #expect(hasDirectionalCities) } else { Issue.record("Expected success with directional stadiums") } } // MARK: - Output Structure Tests @Test("Max 5 options returned") func plan_Max5OptionsReturned() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium // Create many games to potentially generate many options var games: [Game] = [] for day in 1...15 { games.append(makeGame( stadiumId: la.id, date: date("2026-06-\(String(format: "%02d", day)) 19:00") )) } let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: games, stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { #expect(options.count <= 5, "Should return at most 5 options") } else { Issue.record("Expected success") } } @Test("Options ranked from 1") func plan_OptionsRankedFromOne() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { #expect(options.first?.rank == 1) for (index, option) in options.enumerated() { #expect(option.rank == index + 1) } } else { Issue.record("Expected success") } } @Test("Options have valid structure") func plan_OptionsHaveValidStructure() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { for option in options { #expect(option.rank > 0) #expect(!option.stops.isEmpty) #expect(!option.geographicRationale.isEmpty) #expect(option.totalDrivingHours >= 0) #expect(option.totalDistanceMiles >= 0) } } else { Issue.record("Expected success") } } @Test("Travel segments count equals stops count minus 1") func plan_TravelSegmentsCountCorrect() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { for option in options { #expect(option.travelSegments.count == option.stops.count - 1, "Travel segments should be stops - 1") } } else { Issue.record("Expected success") } } @Test("Travel segments are drive mode") func plan_TravelSegmentsAreDriveMode() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { for option in options { for segment in option.travelSegments { #expect(segment.travelMode == .drive) } } } else { Issue.record("Expected success") } } // MARK: - Geographic Rationale Tests @Test("Geographic rationale shows start and end cities") func plan_GeographicRationale_ShowsStartAndEnd() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { if let rationale = options.first?.geographicRationale { #expect(rationale.contains("San Diego")) #expect(rationale.contains("San Francisco")) } } else { Issue.record("Expected success") } } @Test("Geographic rationale shows game count") func plan_GeographicRationale_ShowsGameCount() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game1 = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let game2 = makeGame(stadiumId: la.id, date: date("2026-06-11 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game1, game2], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { if let rationale = options.first?.geographicRationale { #expect(rationale.contains("game") || rationale.contains("2")) } } else { Issue.record("Expected success") } } // MARK: - Stop Tests @Test("Stops include start city") func plan_StopsIncludeStartCity() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { if let firstStop = options.first?.stops.first { #expect(firstStop.city == "San Diego") } } else { Issue.record("Expected success") } } @Test("Stops include end city") func plan_StopsIncludeEndCity() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { if let lastStop = options.first?.stops.last { #expect(lastStop.city == "San Francisco") } } else { Issue.record("Expected success") } } @Test("Start stop has no games") func plan_StartStopHasNoGames() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { if let firstStop = options.first?.stops.first { #expect(firstStop.games.isEmpty, "Start stop should have no games") } } else { Issue.record("Expected success") } } @Test("End stop has no games") func plan_EndStopHasNoGames() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { if let lastStop = options.first?.stops.last { #expect(lastStop.games.isEmpty, "End stop should have no games") } } else { Issue.record("Expected success") } } @Test("Game stops have games") func plan_GameStopsHaveGames() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { // Middle stops (not first or last) should have games if let stops = options.first?.stops, stops.count > 2 { let middleStops = stops.dropFirst().dropLast() for stop in middleStops { #expect(!stop.games.isEmpty, "Middle stops should have games") } } } else { Issue.record("Expected success") } } @Test("Stop has correct city from stadium") func plan_StopHasCorrectCity() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { let allCities = options.first?.stops.map { $0.city } ?? [] #expect(allCities.contains("Los Angeles")) } else { Issue.record("Expected success") } } // MARK: - Ranking Tests @Test("More games ranked higher") func plan_MoreGamesRankedHigher() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium // Create games that allow different route options let game1 = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let game2 = makeGame(stadiumId: la.id, date: date("2026-06-11 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game1, game2], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { if options.count >= 2 { let firstGames = options[0].stops.flatMap { $0.games }.count let secondGames = options[1].stops.flatMap { $0.games }.count #expect(firstGames >= secondGames, "More games should be ranked first") } } else { Issue.record("Expected success") } } @Test("Less driving hours ranked higher for equal games") func plan_LessDrivingRankedHigher() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { if options.count >= 2 { let firstGames = options[0].stops.flatMap { $0.games }.count let secondGames = options[1].stops.flatMap { $0.games }.count if firstGames == secondGames { #expect(options[0].totalDrivingHours <= options[1].totalDrivingHours) } } } else { Issue.record("Expected success") } } // MARK: - Date Range Tests @Test("Explicit date range used when provided") func plan_ExplicitDateRangeUsed() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium // Games outside and inside date range let outsideGame = makeGame(stadiumId: la.id, date: date("2026-05-10 19:00")) let insideGame = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [outsideGame, insideGame], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { // Only inside game should be in results let allGameIds = options.flatMap { $0.stops.flatMap { $0.games } } #expect(allGameIds.contains(insideGame.id)) #expect(!allGameIds.contains(outsideGame.id)) } else { Issue.record("Expected success") } } @Test("No games in date range fails") func plan_NoGamesInDateRange_Fails() { let planner = ScenarioCPlanner() let chicago = chicagoStadium let ny = nyStadium let cleveland = clevelandStadium // Game outside date range let game = makeGame(stadiumId: cleveland.id, date: date("2026-05-10 19:00")) let startLoc = LocationInput( name: "Chicago", coordinate: CLLocationCoordinate2D(latitude: chicago.latitude, longitude: chicago.longitude) ) let endLoc = LocationInput( name: "New York", coordinate: CLLocationCoordinate2D(latitude: ny.latitude, longitude: ny.longitude) ) let request = makeRequest( games: [game], stadiums: [chicago.id: chicago, ny.id: ny, cleveland.id: cleveland], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .failure = result { // Expected - no games within date range } else { Issue.record("Expected failure for no games in date range") } } // MARK: - Driving Constraints Tests @Test("Two drivers allows longer routes") func plan_TwoDrivers_AllowsLongerRoutes() { let planner = ScenarioCPlanner() let chicago = chicagoStadium let ny = nyStadium let cleveland = clevelandStadium let game = makeGame(stadiumId: cleveland.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "Chicago", coordinate: CLLocationCoordinate2D(latitude: chicago.latitude, longitude: chicago.longitude) ) let endLoc = LocationInput( name: "New York", coordinate: CLLocationCoordinate2D(latitude: ny.latitude, longitude: ny.longitude) ) // With 2 drivers, longer routes should be feasible let request = makeRequest( games: [game], stadiums: [chicago.id: chicago, ny.id: ny, cleveland.id: cleveland], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59"), numberOfDrivers: 2 ) let result = planner.plan(request: request) // Should succeed with 2 drivers if case .success(let options) = result { #expect(!options.isEmpty) } } @Test("Trip duration generates date ranges from games") func plan_TripDurationGeneratesDateRanges() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium // Games at start (SD) and end (SF) cities to generate date ranges let startCityGame = makeGame(stadiumId: sd.id, date: date("2026-06-05 19:00")) let middleGame = makeGame(stadiumId: la.id, date: date("2026-06-07 19:00")) let endCityGame = makeGame(stadiumId: sf.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) // Use trip duration instead of explicit dates let request = makeRequest( games: [startCityGame, middleGame, endCityGame], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59"), tripDuration: 7 ) let result = planner.plan(request: request) // Should succeed with trip duration mode switch result { case .success, .failure: // Just verify it handles trip duration without crashing break } } // MARK: - Failure Tests @Test("Failure contains violation details") func plan_Failure_ContainsViolationDetails() { let planner = ScenarioCPlanner() let request = makeRequest( games: [], stadiums: [:], startLocation: nil, endLocation: nil, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .failure(let failure) = result { #expect(!failure.violations.isEmpty) #expect(failure.violations.first?.severity == .error) } else { Issue.record("Expected failure") } } @Test("No valid directional routes fails") func plan_NoValidDirectionalRoutes_Fails() { let planner = ScenarioCPlanner() let chicago = chicagoStadium let ny = nyStadium let minneapolis = minneapolisStadium // Only game is in wrong direction (Minneapolis is west of Chicago) let game = makeGame(stadiumId: minneapolis.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "Chicago", coordinate: CLLocationCoordinate2D(latitude: chicago.latitude, longitude: chicago.longitude) ) let endLoc = LocationInput( name: "New York", coordinate: CLLocationCoordinate2D(latitude: ny.latitude, longitude: ny.longitude) ) let request = makeRequest( games: [game], stadiums: [chicago.id: chicago, ny.id: ny, minneapolis.id: minneapolis], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) // Should fail because Minneapolis is not directional if case .failure = result { // Expected } else { Issue.record("Expected failure for non-directional route") } } // MARK: - Edge Cases @Test("Same city for start and end") func plan_SameCityStartAndEnd() { let planner = ScenarioCPlanner() let la = laStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "Los Angeles", coordinate: CLLocationCoordinate2D(latitude: la.latitude, longitude: la.longitude) ) let endLoc = LocationInput( name: "Los Angeles", coordinate: CLLocationCoordinate2D(latitude: la.latitude, longitude: la.longitude) ) let request = makeRequest( games: [game], stadiums: [la.id: la], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) // May succeed or fail depending on implementation // Just verify it doesn't crash switch result { case .success, .failure: break } } @Test("Very short date range with game") func plan_ShortDateRangeWithGame() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) // Just one day let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-10 00:00"), endDate: date("2026-06-10 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { #expect(!options.isEmpty) } } @Test("Multiple games at same stadium") func plan_MultipleGamesAtSameStadium() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game1 = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let game2 = makeGame(stadiumId: la.id, date: date("2026-06-11 19:00")) let game3 = makeGame(stadiumId: la.id, date: date("2026-06-12 13:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game1, game2, game3], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { // Games at same stadium should be grouped for option in options { let laStops = option.stops.filter { $0.city == "Los Angeles" } #expect(laStops.count <= 1, "Same stadium games should be grouped") } } else { Issue.record("Expected success") } } @Test("Empty games array") func plan_EmptyGamesArray() { let planner = ScenarioCPlanner() let chicago = chicagoStadium let ny = nyStadium let startLoc = LocationInput( name: "Chicago", coordinate: CLLocationCoordinate2D(latitude: chicago.latitude, longitude: chicago.longitude) ) let endLoc = LocationInput( name: "New York", coordinate: CLLocationCoordinate2D(latitude: ny.latitude, longitude: ny.longitude) ) let request = makeRequest( games: [], stadiums: [chicago.id: chicago, ny.id: ny], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) // Should fail - no games if case .failure = result { // Expected } else { Issue.record("Expected failure for empty games") } } @Test("Handles long cross-country route") func plan_LongCrossCountryRoute() { let planner = ScenarioCPlanner() let sf = sfStadium let chicago = chicagoStadium let cleveland = clevelandStadium let ny = nyStadium // SF to NY is a long route let game1 = makeGame(stadiumId: chicago.id, date: date("2026-06-10 19:00")) let game2 = makeGame(stadiumId: cleveland.id, date: date("2026-06-15 19:00")) let startLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let endLoc = LocationInput( name: "New York", coordinate: CLLocationCoordinate2D(latitude: ny.latitude, longitude: ny.longitude) ) let request = makeRequest( games: [game1, game2], stadiums: [sf.id: sf, chicago.id: chicago, cleveland.id: cleveland, ny.id: ny], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59"), numberOfDrivers: 2 // More drivers for longer route ) let result = planner.plan(request: request) // Should handle without crashing switch result { case .success, .failure: break } } @Test("Stop coordinates match stadium") func plan_StopCoordinatesMatchStadium() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { // Find LA stop if let laStop = options.first?.stops.first(where: { $0.city == "Los Angeles" }) { if let coord = laStop.coordinate { #expect(abs(coord.latitude - la.latitude) < 0.01) #expect(abs(coord.longitude - la.longitude) < 0.01) } } } else { Issue.record("Expected success") } } @Test("Total distance is positive") func plan_TotalDistanceIsPositive() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { for option in options { #expect(option.totalDistanceMiles > 0) } } else { Issue.record("Expected success") } } @Test("Total driving hours is positive") func plan_TotalDrivingHoursIsPositive() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { for option in options { #expect(option.totalDrivingHours > 0) } } else { Issue.record("Expected success") } } @Test("Travel segment has valid origin and destination") func plan_TravelSegmentHasValidOriginDestination() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { for option in options { for segment in option.travelSegments { #expect(!segment.fromLocation.name.isEmpty) #expect(!segment.toLocation.name.isEmpty) } } } else { Issue.record("Expected success") } } @Test("Travel segment has positive distance") func plan_TravelSegmentHasPositiveDistance() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { for option in options { for segment in option.travelSegments { #expect(segment.distanceMeters > 0) } } } else { Issue.record("Expected success") } } @Test("Travel segment has positive duration") func plan_TravelSegmentHasPositiveDuration() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { for option in options { for segment in option.travelSegments { #expect(segment.durationSeconds > 0) } } } else { Issue.record("Expected success") } } // Note: "Departure time before arrival time" test removed // Travel segments are now location-based, not time-based // The user decides when to travel; segments only describe route info @Test("Games filtered to date range") func plan_GamesFilteredToDateRange() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium // Game outside range let earlyGame = makeGame(stadiumId: la.id, date: date("2026-05-01 19:00")) // Game inside range let validGame = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) // Game outside range let lateGame = makeGame(stadiumId: la.id, date: date("2026-07-01 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [earlyGame, validGame, lateGame], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { let allGameIds = options.flatMap { $0.stops.flatMap { $0.games } } #expect(allGameIds.contains(validGame.id)) #expect(!allGameIds.contains(earlyGame.id)) #expect(!allGameIds.contains(lateGame.id)) } else { Issue.record("Expected success") } } @Test("Many games handles efficiently") func plan_ManyGames_HandlesEfficiently() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium // Create many games var games: [Game] = [] for day in 1...30 { games.append(makeGame( stadiumId: la.id, date: date("2026-06-\(String(format: "%02d", day)) 19:00") )) } let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: games, stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) // Should complete without timeout switch result { case .success, .failure: break } } @Test("Route progresses toward end") func plan_RouteProgressesTowardEnd() { let planner = ScenarioCPlanner() let chicago = chicagoStadium let cleveland = clevelandStadium let pittsburgh = pittsburghStadium let ny = nyStadium let game1 = makeGame(stadiumId: cleveland.id, date: date("2026-06-10 19:00")) let game2 = makeGame(stadiumId: pittsburgh.id, date: date("2026-06-12 19:00")) let startLoc = LocationInput( name: "Chicago", coordinate: CLLocationCoordinate2D(latitude: chicago.latitude, longitude: chicago.longitude) ) let endLoc = LocationInput( name: "New York", coordinate: CLLocationCoordinate2D(latitude: ny.latitude, longitude: ny.longitude) ) let request = makeRequest( games: [game1, game2], stadiums: [ chicago.id: chicago, cleveland.id: cleveland, pittsburgh.id: pittsburgh, ny.id: ny ], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { // Verify each stop gets closer to (or doesn't significantly move away from) NY for option in options { var lastDistance = Double.infinity for stop in option.stops { if let coord = stop.coordinate { let distance = sqrt(pow(coord.latitude - ny.latitude, 2) + pow(coord.longitude - ny.longitude, 2)) // Allow small tolerance for backtracking #expect(distance <= lastDistance * 1.2, "Route should generally progress toward end") lastDistance = distance } } } } else { Issue.record("Expected success") } } @Test("Stop location has name") func plan_StopLocationHasName() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { for option in options { for stop in option.stops { #expect(!stop.location.name.isEmpty) } } } else { Issue.record("Expected success") } } @Test("Games at directional stadium included") func plan_GamesAtDirectionalStadium_Included() { let planner = ScenarioCPlanner() let sd = sdStadium let la = laStadium let sf = sfStadium // LA is between SD and SF (directional) let game = makeGame(stadiumId: la.id, date: date("2026-06-10 19:00")) let startLoc = LocationInput( name: "San Diego", coordinate: CLLocationCoordinate2D(latitude: sd.latitude, longitude: sd.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [game], stadiums: [sd.id: sd, la.id: la, sf.id: sf], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) if case .success(let options) = result { let allGameIds = options.flatMap { $0.stops.flatMap { $0.games } } #expect(allGameIds.contains(game.id)) } else { Issue.record("Expected success") } } }