// // 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(.serialized) struct ScenarioCPlannerTests { // MARK: - Test Fixtures private func makeStadium( id: UUID = UUID(), name: String, city: String, state: String, latitude: Double, longitude: Double, sport: Sport = .mlb ) -> Stadium { Stadium( id: id, name: name, city: city, state: state, latitude: latitude, longitude: longitude, capacity: 40000, sport: sport ) } 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") } } // MARK: - Feature 1: Travel Corridor Game Inclusion Tests @Test("Direct route with games along path includes all corridor games") func corridor_DirectRouteWithGamesAlongPath_IncludesAllGames() { let planner = ScenarioCPlanner() // Create stadiums: LA → San Jose → SF (direct north on I-5/101) let la = makeStadium(name: "Dodger Stadium", city: "Los Angeles", state: "CA", latitude: 34.0739, longitude: -118.2400) let sj = makeStadium(name: "SAP Center", city: "San Jose", state: "CA", latitude: 37.3326, longitude: -121.9010) // Midpoint between LA and SF let sf = sfStadium // Games along the path let laGame = makeGame(stadiumId: la.id, date: date("2026-06-05 19:00")) let sjGame = makeGame(stadiumId: sj.id, date: date("2026-06-06 19:00")) let sfGame = makeGame(stadiumId: sf.id, date: date("2026-06-07 19:00")) let startLoc = LocationInput( name: "Los Angeles", coordinate: CLLocationCoordinate2D(latitude: la.latitude, longitude: la.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [laGame, sjGame, sfGame], stadiums: [la.id: la, sj.id: sj, 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.isEmpty, "Should return at least one route") // Best option should include all 3 games (LA → SJ → SF) let topOption = options.first! let gameIds = topOption.stops.flatMap { $0.games } #expect(gameIds.contains(laGame.id), "Should include LA game") #expect(gameIds.contains(sjGame.id), "Should include San Jose game (midpoint)") #expect(gameIds.contains(sfGame.id), "Should include SF game") } else { Issue.record("Expected success with games along direct corridor") } } @Test("Game slightly off corridor within tolerance included") func corridor_GameSlightlyOffCorridor_IncludedWithinTolerance() { let planner = ScenarioCPlanner() // LA → SF is north on I-5 // Sacramento is ~20 miles east of I-5, should be within corridor tolerance let la = laStadium let sacramento = makeStadium(name: "Golden 1 Center", city: "Sacramento", state: "CA", latitude: 38.5802, longitude: -121.4996) // Slightly east of direct path let sf = sfStadium let laGame = makeGame(stadiumId: la.id, date: date("2026-06-05 19:00")) let sacGame = makeGame(stadiumId: sacramento.id, date: date("2026-06-06 19:00")) let sfGame = makeGame(stadiumId: sf.id, date: date("2026-06-07 19:00")) let startLoc = LocationInput( name: "Los Angeles", coordinate: CLLocationCoordinate2D(latitude: la.latitude, longitude: la.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [laGame, sacGame, sfGame], stadiums: [la.id: la, sacramento.id: sacramento, 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.isEmpty) // Sacramento should be included (within corridor tolerance) let allGameIds = options.flatMap { $0.stops.flatMap { $0.games } } #expect(allGameIds.contains(sacGame.id), "Sacramento should be included (within ~50mi corridor tolerance)") } else { Issue.record("Expected success with slightly off-corridor game") } } @Test("Game far from corridor excluded") func corridor_GameFarFromCorridor_Excluded() { let planner = ScenarioCPlanner() // LA → SF is north-bound // Phoenix is ~300 miles east, far from corridor let la = laStadium let phoenix = makeStadium(name: "Chase Field", city: "Phoenix", state: "AZ", latitude: 33.4452, longitude: -112.0667) // Far east of LA→SF path let sf = sfStadium let laGame = makeGame(stadiumId: la.id, date: date("2026-06-05 19:00")) let phoenixGame = makeGame(stadiumId: phoenix.id, date: date("2026-06-06 19:00")) let sfGame = makeGame(stadiumId: sf.id, date: date("2026-06-07 19:00")) let startLoc = LocationInput( name: "Los Angeles", coordinate: CLLocationCoordinate2D(latitude: la.latitude, longitude: la.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [laGame, phoenixGame, sfGame], stadiums: [la.id: la, phoenix.id: phoenix, 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.isEmpty) // Phoenix should be excluded (too far from LA→SF corridor) let allGameIds = options.flatMap { $0.stops.flatMap { $0.games } } #expect(!allGameIds.contains(phoenixGame.id), "Phoenix should be excluded (300mi east, far from corridor)") } else { Issue.record("Expected success but with Phoenix excluded") } } @Test func corridor_MultipleGamesMixed_FiltersCorrectly() { let planner = ScenarioCPlanner() // LA → Portland route let la = laStadium let sd = sdStadium // South of LA - wrong direction let sf = sfStadium // On path north let seattle = makeStadium(name: "T-Mobile Park", city: "Seattle", state: "WA", latitude: 47.5914, longitude: -122.3325) // Beyond Portland let portland = makeStadium(name: "Providence Park", city: "Portland", state: "OR", latitude: 45.5212, longitude: -122.6917) let laGame = makeGame(stadiumId: la.id, date: date("2026-06-05 19:00")) let sdGame = makeGame(stadiumId: sd.id, date: date("2026-06-06 19:00")) // Should exclude (south) let sfGame = makeGame(stadiumId: sf.id, date: date("2026-06-07 19:00")) // Should include (on path) let seattleGame = makeGame(stadiumId: seattle.id, date: date("2026-06-08 19:00")) // Should exclude (beyond end) let startLoc = LocationInput( name: "Los Angeles", coordinate: CLLocationCoordinate2D(latitude: la.latitude, longitude: la.longitude) ) let endLoc = LocationInput( name: "Portland", coordinate: CLLocationCoordinate2D(latitude: portland.latitude, longitude: portland.longitude) ) let request = makeRequest( games: [laGame, sdGame, sfGame, seattleGame], stadiums: [la.id: la, sd.id: sd, sf.id: sf, seattle.id: seattle, portland.id: portland], 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) let allGameIds = options.flatMap { $0.stops.flatMap { $0.games } } // Should include games on corridor #expect(allGameIds.contains(laGame.id), "LA should be included (start)") #expect(allGameIds.contains(sfGame.id), "SF should be included (on path north)") // Should exclude off-corridor games #expect(!allGameIds.contains(sdGame.id), "San Diego should be excluded (south, wrong direction)") #expect(!allGameIds.contains(seattleGame.id), "Seattle should be excluded (beyond end point)") } else { Issue.record("Expected success with selective corridor filtering") } } @Test("No games along corridor returns empty route or failure") func corridor_NoGamesAlongCorridor_ReturnsEmptyOrFailure() { let planner = ScenarioCPlanner() // LA → Seattle route (I-5 corridor) let la = laStadium let seattle = makeStadium(name: "T-Mobile Park", city: "Seattle", state: "WA", latitude: 47.5914, longitude: -122.3325) // Games far from corridor let phoenix = makeStadium(name: "Chase Field", city: "Phoenix", state: "AZ", latitude: 33.4452, longitude: -112.0667) // East let denver = makeStadium(name: "Coors Field", city: "Denver", state: "CO", latitude: 39.7559, longitude: -104.9942) // Far east let phoenixGame = makeGame(stadiumId: phoenix.id, date: date("2026-06-05 19:00")) let denverGame = makeGame(stadiumId: denver.id, date: date("2026-06-06 19:00")) let startLoc = LocationInput( name: "Los Angeles", coordinate: CLLocationCoordinate2D(latitude: la.latitude, longitude: la.longitude) ) let endLoc = LocationInput( name: "Seattle", coordinate: CLLocationCoordinate2D(latitude: seattle.latitude, longitude: seattle.longitude) ) let request = makeRequest( games: [phoenixGame, denverGame], stadiums: [la.id: la, seattle.id: seattle, phoenix.id: phoenix, denver.id: denver], 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 either fail (.noGamesInRange or .noValidRoutes) OR return route with no games switch result { case .failure(let failure): // Expected: no games in corridor #expect(failure.reason == .noValidRoutes || failure.reason == .noGamesInRange, "Should fail with noValidRoutes or noGamesInRange") case .success(let options): // If success, route should have only start/end waypoints, no games if let topOption = options.first { let gameCount = topOption.stops.flatMap { $0.games }.count #expect(gameCount == 0, "Route should have no games if all games are off-corridor") } } } // MARK: - Feature 2: Geographic Efficiency Validation (Anti-Backtracking) Tests @Test("Route must start at specified start city") func antiBacktrack_RouteStartsAtSpecifiedCity() { let planner = ScenarioCPlanner() // SF → LA route (south-bound) let sf = sfStadium let la = laStadium let sfGame = makeGame(stadiumId: sf.id, date: date("2026-06-07 19:00")) let laGame = makeGame(stadiumId: la.id, date: date("2026-06-05 19:00")) // Earlier date let startLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let endLoc = LocationInput( name: "Los Angeles", coordinate: CLLocationCoordinate2D(latitude: la.latitude, longitude: la.longitude) ) let request = makeRequest( games: [laGame, sfGame], stadiums: [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.isEmpty) // The route should visit SF before LA (not LA first just because it's earlier) // First stop with games should be SF if let topOption = options.first { let stopsWithGames = topOption.stops.filter { !$0.games.isEmpty } if let firstGameStop = stopsWithGames.first { #expect(firstGameStop.city == "San Francisco", "Route should start at SF (specified start city)") } } } else { Issue.record("Expected success with route starting at SF") } } @Test("Route must end at specified end city") func antiBacktrack_RouteEndsAtSpecifiedCity() { let planner = ScenarioCPlanner() // LA → Seattle route let la = laStadium let sf = sfStadium let seattle = makeStadium(name: "T-Mobile Park", city: "Seattle", state: "WA", latitude: 47.5914, longitude: -122.3325) let laGame = makeGame(stadiumId: la.id, date: date("2026-06-05 19:00")) let sfGame = makeGame(stadiumId: sf.id, date: date("2026-06-06 19:00")) let startLoc = LocationInput( name: "Los Angeles", coordinate: CLLocationCoordinate2D(latitude: la.latitude, longitude: la.longitude) ) let endLoc = LocationInput( name: "Seattle", coordinate: CLLocationCoordinate2D(latitude: seattle.latitude, longitude: seattle.longitude) ) let request = makeRequest( games: [laGame, sfGame], stadiums: [la.id: la, sf.id: sf, seattle.id: seattle], 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) // Route should end at Seattle waypoint (after all games) if let topOption = options.first { // The last stop should be Seattle (or close to it) let lastStop = topOption.stops.last #expect(lastStop != nil, "Route should have stops") // The route should include Seattle as the end destination // Either as a waypoint or the last stop should be very close to Seattle if let lastStop = lastStop, let lastCoord = lastStop.coordinate { let lastLocation = CLLocation(latitude: lastCoord.latitude, longitude: lastCoord.longitude) let seattleLocation = CLLocation(latitude: seattle.latitude, longitude: seattle.longitude) let distance = lastLocation.distance(from: seattleLocation) // Should either be Seattle itself or within 80km (for waypoint tolerance) #expect(distance < 80000, "Route should end at or near Seattle (end city)") } } } else { Issue.record("Expected success with route ending at Seattle") } } @Test("Intermediate games in wrong order rejected or reordered") func antiBacktrack_WrongOrderGamesHandled() { let planner = ScenarioCPlanner() // LA → Portland route let la = laStadium let sf = sfStadium let portland = makeStadium(name: "Providence Park", city: "Portland", state: "OR", latitude: 45.5212, longitude: -122.6917) // Games in suboptimal order: SF first, then LA (backtrack south), then Portland let sfGame = makeGame(stadiumId: sf.id, date: date("2026-06-05 19:00")) let laGame = makeGame(stadiumId: la.id, date: date("2026-06-06 19:00")) // Would require backtrack let portlandGame = makeGame(stadiumId: portland.id, date: date("2026-06-07 19:00")) let startLoc = LocationInput( name: "Los Angeles", coordinate: CLLocationCoordinate2D(latitude: la.latitude, longitude: la.longitude) ) let endLoc = LocationInput( name: "Portland", coordinate: CLLocationCoordinate2D(latitude: portland.latitude, longitude: portland.longitude) ) let request = makeRequest( games: [sfGame, laGame, portlandGame], stadiums: [la.id: la, sf.id: sf, portland.id: portland], 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) // The route should either: // A) Exclude the LA game on June 6 (since it requires backtracking south after SF) // B) Reorder to LA → SF → Portland (optimal order) if let topOption = options.first { let gameIds = topOption.stops.flatMap { $0.games } // If it includes all 3 games, check the order is sensible (LA before SF) if gameIds.count == 3 { let stopsWithGames = topOption.stops.filter { !$0.games.isEmpty } if stopsWithGames.count >= 2 { let firstCity = stopsWithGames[0].city let secondCity = stopsWithGames[1].city // LA should come before SF #expect( (firstCity == "Los Angeles" && secondCity == "San Francisco"), "If all games included, LA should come before SF (no backtracking)" ) } } // Otherwise, it's acceptable to exclude the backtracking game } } else { Issue.record("Expected success with sensible ordering or game exclusion") } } @Test("Multiple route options - least backtracking preferred") func antiBacktrack_LeastBacktrackingPreferred() { let planner = ScenarioCPlanner() // LA → SF route let la = laStadium let sd = sdStadium // South of LA - major backtrack let sj = makeStadium(name: "SAP Center", city: "San Jose", state: "CA", latitude: 37.3387, longitude: -121.8853) let sf = sfStadium // Two potential routes: // Option A: LA → SD (south backtrack) → SF (requires going way south first) // Option B: LA → SJ → SF (direct north) let laGame1 = makeGame(stadiumId: la.id, date: date("2026-06-05 19:00")) let sdGame = makeGame(stadiumId: sd.id, date: date("2026-06-06 19:00")) let laGame2 = makeGame(stadiumId: la.id, date: date("2026-06-05 19:00")) // Duplicate for Option B let sjGame = makeGame(stadiumId: sj.id, date: date("2026-06-06 19:00")) let sfGame1 = makeGame(stadiumId: sf.id, date: date("2026-06-07 19:00")) let sfGame2 = makeGame(stadiumId: sf.id, date: date("2026-06-07 19:00")) let startLoc = LocationInput( name: "Los Angeles", coordinate: CLLocationCoordinate2D(latitude: la.latitude, longitude: la.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [laGame1, sdGame, sjGame, sfGame1], stadiums: [la.id: la, sd.id: sd, sj.id: sj, 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.isEmpty) // Top option should prefer LA → SJ → SF (direct) over LA → SD → SF (backtrack) if let topOption = options.first { let gameIds = topOption.stops.flatMap { $0.games } // Should prefer SJ over SD (less backtracking) if gameIds.contains(sjGame.id) && gameIds.contains(sdGame.id) { Issue.record("Should not include both SJ and SD - prefer less backtracking") } // Better: should include SJ and exclude SD #expect(gameIds.contains(sjGame.id) || !gameIds.contains(sdGame.id), "Should prefer San Jose (direct north) over San Diego (backtrack south)") } } else { Issue.record("Expected success with least-backtracking route preferred") } } @Test("Minor backtracking within tolerance is acceptable") func antiBacktrack_MinorBacktrackingAcceptable() { let planner = ScenarioCPlanner() // LA → SF route with minor backtrack to Anaheim let la = laStadium let anaheim = makeStadium(name: "Honda Center", city: "Anaheim", state: "CA", latitude: 33.8078, longitude: -117.8764) // ~30mi south of LA let sj = makeStadium(name: "SAP Center", city: "San Jose", state: "CA", latitude: 37.3387, longitude: -121.8853) let sf = sfStadium let laGame = makeGame(stadiumId: la.id, date: date("2026-06-05 19:00")) let anaheimGame = makeGame(stadiumId: anaheim.id, date: date("2026-06-06 19:00")) let sjGame = makeGame(stadiumId: sj.id, date: date("2026-06-07 19:00")) let sfGame = makeGame(stadiumId: sf.id, date: date("2026-06-08 19:00")) let startLoc = LocationInput( name: "Los Angeles", coordinate: CLLocationCoordinate2D(latitude: la.latitude, longitude: la.longitude) ) let endLoc = LocationInput( name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: sf.latitude, longitude: sf.longitude) ) let request = makeRequest( games: [laGame, anaheimGame, sjGame, sfGame], stadiums: [la.id: la, anaheim.id: anaheim, sj.id: sj, 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.isEmpty) // Anaheim is only ~30 miles south, which should be acceptable as a minor detour // The route should include Anaheim if time permits if let topOption = options.first { let gameIds = topOption.stops.flatMap { $0.games } // Anaheim could be included (acceptable minor backtrack) // This test just verifies we don't fail completely #expect(true, "Route planning should succeed with minor backtracking scenario") } } else { Issue.record("Expected success - minor backtracking should be acceptable") } } @Test("Excessive backtracking beyond destination rejected") func antiBacktrack_ExcessiveBacktrackingRejected() { let planner = ScenarioCPlanner() // LA → Seattle route (north-bound) let la = laStadium let sd = sdStadium // 120 miles south - excessive backtrack let sf = sfStadium let seattle = makeStadium(name: "T-Mobile Park", city: "Seattle", state: "WA", latitude: 47.5914, longitude: -122.3325) let laGame = makeGame(stadiumId: la.id, date: date("2026-06-05 19:00")) let sdGame = makeGame(stadiumId: sd.id, date: date("2026-06-06 19:00")) // Excessive backtrack let sfGame = makeGame(stadiumId: sf.id, date: date("2026-06-07 19:00")) let seattleGame = makeGame(stadiumId: seattle.id, date: date("2026-06-08 19:00")) let startLoc = LocationInput( name: "Los Angeles", coordinate: CLLocationCoordinate2D(latitude: la.latitude, longitude: la.longitude) ) let endLoc = LocationInput( name: "Seattle", coordinate: CLLocationCoordinate2D(latitude: seattle.latitude, longitude: seattle.longitude) ) let request = makeRequest( games: [laGame, sdGame, sfGame, seattleGame], stadiums: [la.id: la, sd.id: sd, sf.id: sf, seattle.id: seattle], startLocation: startLoc, endLocation: endLoc, startDate: date("2026-06-01 00:00"), endDate: date("2026-06-30 23:59") ) let result = planner.plan(request: request) // Either exclude San Diego (success without it) or return failure if case .success(let options) = result { #expect(!options.isEmpty) // San Diego should be excluded (excessive backtrack going north) let allGameIds = options.flatMap { $0.stops.flatMap { $0.games } } #expect(!allGameIds.contains(sdGame.id), "San Diego should be excluded (120mi south, excessive backtrack on north-bound route)") } // Alternatively, could fail with noValidRoutes if San Diego is required } @Test("Correct directional classification for north-to-south route") func antiBacktrack_NorthToSouthDirectionalCorrect() { let planner = ScenarioCPlanner() // Boston → Miami route (north to south) let boston = makeStadium(name: "TD Garden", city: "Boston", state: "MA", latitude: 42.3662, longitude: -71.0621) let nyc = makeStadium(name: "Madison Square Garden", city: "New York", state: "NY", latitude: 40.7505, longitude: -73.9934) let dc = makeStadium(name: "Capital One Arena", city: "Washington", state: "DC", latitude: 38.8981, longitude: -77.0209) let miami = makeStadium(name: "FTX Arena", city: "Miami", state: "FL", latitude: 25.7814, longitude: -80.1870) let bostonGame = makeGame(stadiumId: boston.id, date: date("2026-06-05 19:00")) let nycGame = makeGame(stadiumId: nyc.id, date: date("2026-06-06 19:00")) let dcGame = makeGame(stadiumId: dc.id, date: date("2026-06-07 19:00")) let miamiGame = makeGame(stadiumId: miami.id, date: date("2026-06-08 19:00")) let startLoc = LocationInput( name: "Boston", coordinate: CLLocationCoordinate2D(latitude: boston.latitude, longitude: boston.longitude) ) let endLoc = LocationInput( name: "Miami", coordinate: CLLocationCoordinate2D(latitude: miami.latitude, longitude: miami.longitude) ) let request = makeRequest( games: [bostonGame, nycGame, dcGame, miamiGame], stadiums: [boston.id: boston, nyc.id: nyc, dc.id: dc, miami.id: miami], 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) // Route should follow north→south progression if let topOption = options.first { let stopsWithGames = topOption.stops.filter { !$0.games.isEmpty } // Verify stops are in geographic north→south order if stopsWithGames.count >= 2 { for i in 0..<(stopsWithGames.count - 1) { guard let currentCoord = stopsWithGames[i].coordinate, let nextCoord = stopsWithGames[i + 1].coordinate else { continue } let currentLat = currentCoord.latitude let nextLat = nextCoord.latitude // Each subsequent stop should be equal or farther south (lower latitude) #expect(nextLat <= currentLat + 1.0, // Allow 1° tolerance for slight variations "Route should progress south (Boston→NYC→DC→Miami)") } } } } else { Issue.record("Expected success with north→south directional route") } } }