// // MustStopValidationTests.swift // SportsTimeTests // // Tests for must-stop location filtering across all scenario planners. // import Testing import CoreLocation @testable import SportsTime @Suite("Must-Stop Validation") struct MustStopValidationTests { // MARK: - Centralized Must-Stop Filter (TripPlanningEngine) @Test("scenarioA: must stops filter routes to include required cities") func scenarioA_mustStops_routesContainRequiredCities() { let baseDate = TestFixtures.date(year: 2026, month: 6, day: 1, hour: 19, minute: 0) let day2 = TestClock.calendar.date(byAdding: .day, value: 1, to: baseDate)! let day3 = TestClock.calendar.date(byAdding: .day, value: 2, to: baseDate)! let gameNYC = TestFixtures.game(city: "New York", dateTime: baseDate) let gameBOS = TestFixtures.game(city: "Boston", dateTime: day2) let gamePHL = TestFixtures.game(city: "Philadelphia", dateTime: day3) let stadiums = TestFixtures.stadiumMap(for: [gameNYC, gameBOS, gamePHL]) let prefs = TripPreferences( planningMode: .dateRange, sports: [.mlb], startDate: baseDate, endDate: day3, mustStopLocations: [LocationInput(name: "Boston")] ) let request = PlanningRequest( preferences: prefs, availableGames: [gameNYC, gameBOS, gamePHL], teams: [:], stadiums: stadiums ) let engine = TripPlanningEngine() let result = engine.planItineraries(request: request) if case .success(let options) = result { for option in options { let cities = option.stops.map { $0.city.lowercased() } #expect(cities.contains("boston"), "Every route must include Boston as a must-stop") } } } @Test("must stop impossible city returns failure") func mustStops_impossibleCity_returnsFailure() { let baseDate = TestFixtures.date(year: 2026, month: 6, day: 1, hour: 19, minute: 0) let day2 = TestClock.calendar.date(byAdding: .day, value: 1, to: baseDate)! let gameNYC = TestFixtures.game(city: "New York", dateTime: baseDate) let gameBOS = TestFixtures.game(city: "Boston", dateTime: day2) let stadiums = TestFixtures.stadiumMap(for: [gameNYC, gameBOS]) // Require a city with no games let prefs = TripPreferences( planningMode: .dateRange, sports: [.mlb], startDate: baseDate, endDate: day2, mustStopLocations: [LocationInput(name: "Denver")] ) let request = PlanningRequest( preferences: prefs, availableGames: [gameNYC, gameBOS], teams: [:], stadiums: stadiums ) let engine = TripPlanningEngine() let result = engine.planItineraries(request: request) // Should fail because no route can include Denver #expect(!result.isSuccess) } @Test("scenarioB: must stops enforced via centralized filter") func scenarioB_mustStops_routesContainRequiredCities() { let baseDate = TestFixtures.date(year: 2026, month: 6, day: 1, hour: 19, minute: 0) let day2 = TestClock.calendar.date(byAdding: .day, value: 1, to: baseDate)! let day3 = TestClock.calendar.date(byAdding: .day, value: 2, to: baseDate)! let gameNYC = TestFixtures.game(id: "must_see_1", city: "New York", dateTime: baseDate) let gameBOS = TestFixtures.game(city: "Boston", dateTime: day2) let gamePHL = TestFixtures.game(city: "Philadelphia", dateTime: day3) let stadiums = TestFixtures.stadiumMap(for: [gameNYC, gameBOS, gamePHL]) let prefs = TripPreferences( planningMode: .gameFirst, sports: [.mlb], mustSeeGameIds: [gameNYC.id], startDate: baseDate, endDate: day3, mustStopLocations: [LocationInput(name: "Boston")] ) let request = PlanningRequest( preferences: prefs, availableGames: [gameNYC, gameBOS, gamePHL], teams: [:], stadiums: stadiums ) let engine = TripPlanningEngine() let result = engine.planItineraries(request: request) if case .success(let options) = result { for option in options { let cities = option.stops.map { $0.city.lowercased() } #expect(cities.contains("boston"), "Must-stop filter should ensure Boston is included") } } } @Test("scenarioD: must stops enforced via centralized filter") func scenarioD_mustStops_routesContainRequiredCities() { let baseDate = TestFixtures.date(year: 2026, month: 6, day: 1, hour: 19, minute: 0) let day2 = TestClock.calendar.date(byAdding: .day, value: 1, to: baseDate)! let day3 = TestClock.calendar.date(byAdding: .day, value: 2, to: baseDate)! let teamId = "team_mlb_new_york" let gameNYC = TestFixtures.game(city: "New York", dateTime: baseDate, homeTeamId: teamId) let gameBOS = TestFixtures.game(city: "Boston", dateTime: day2, homeTeamId: "team_mlb_boston", awayTeamId: teamId) let gamePHL = TestFixtures.game(city: "Philadelphia", dateTime: day3, homeTeamId: "team_mlb_philadelphia", awayTeamId: teamId) let stadiums = TestFixtures.stadiumMap(for: [gameNYC, gameBOS, gamePHL]) let prefs = TripPreferences( planningMode: .followTeam, sports: [.mlb], startDate: baseDate, endDate: day3, mustStopLocations: [LocationInput(name: "Boston")], followTeamId: teamId ) let request = PlanningRequest( preferences: prefs, availableGames: [gameNYC, gameBOS, gamePHL], teams: [teamId: TestFixtures.team(city: "New York")], stadiums: stadiums ) let engine = TripPlanningEngine() let result = engine.planItineraries(request: request) if case .success(let options) = result { for option in options { let cities = option.stops.map { $0.city.lowercased() } #expect(cities.contains("boston"), "Must-stop filter should ensure Boston is included in follow-team mode") } } } @Test("scenarioE: must stops enforced via centralized filter") func scenarioE_mustStops_routesContainRequiredCities() { let baseDate = TestFixtures.date(year: 2026, month: 6, day: 1, hour: 19, minute: 0) let day1 = TestClock.calendar.date(byAdding: .day, value: 1, to: baseDate)! let day2 = TestClock.calendar.date(byAdding: .day, value: 2, to: baseDate)! let teamNYC = "team_mlb_new_york" let teamBOS = "team_mlb_boston" let gameNYC = TestFixtures.game(city: "New York", dateTime: baseDate, homeTeamId: teamNYC) let gameBOS = TestFixtures.game(city: "Boston", dateTime: day1, homeTeamId: teamBOS) let gamePHL = TestFixtures.game(city: "Philadelphia", dateTime: day2, homeTeamId: "team_mlb_philadelphia") let stadiums = TestFixtures.stadiumMap(for: [gameNYC, gameBOS, gamePHL]) let prefs = TripPreferences( planningMode: .teamFirst, sports: [.mlb], startDate: baseDate, endDate: day2, mustStopLocations: [LocationInput(name: "Boston")], selectedTeamIds: [teamNYC, teamBOS] ) let request = PlanningRequest( preferences: prefs, availableGames: [gameNYC, gameBOS, gamePHL], teams: [ teamNYC: TestFixtures.team(city: "New York"), teamBOS: TestFixtures.team(city: "Boston"), ], stadiums: stadiums ) let engine = TripPlanningEngine() let result = engine.planItineraries(request: request) if case .success(let options) = result { for option in options { let cities = option.stops.map { $0.city.lowercased() } #expect(cities.contains("boston"), "Must-stop filter should ensure Boston is included in team-first mode") } } } @Test("scenarioC: must stops enforced via centralized filter") func scenarioC_mustStops_routesContainRequiredCities() { let baseDate = TestFixtures.date(year: 2026, month: 6, day: 1, hour: 19, minute: 0) let day2 = TestClock.calendar.date(byAdding: .day, value: 1, to: baseDate)! let day3 = TestClock.calendar.date(byAdding: .day, value: 2, to: baseDate)! let nycCoord = TestFixtures.coordinates["New York"]! let bosCoord = TestFixtures.coordinates["Boston"]! let gameNYC = TestFixtures.game(city: "New York", dateTime: baseDate) let gamePHL = TestFixtures.game(city: "Philadelphia", dateTime: day2) let gameBOS = TestFixtures.game(city: "Boston", dateTime: day3) let stadiums = TestFixtures.stadiumMap(for: [gameNYC, gamePHL, gameBOS]) let prefs = TripPreferences( planningMode: .locations, startLocation: LocationInput(name: "New York", coordinate: nycCoord), endLocation: LocationInput(name: "Boston", coordinate: bosCoord), sports: [.mlb], startDate: baseDate, endDate: day3, mustStopLocations: [LocationInput(name: "Philadelphia")] ) let request = PlanningRequest( preferences: prefs, availableGames: [gameNYC, gamePHL, gameBOS], teams: [:], stadiums: stadiums ) let engine = TripPlanningEngine() let result = engine.planItineraries(request: request) if case .success(let options) = result { for option in options { let cities = option.stops.map { $0.city.lowercased() } #expect(cities.contains("philadelphia"), "Must-stop filter should ensure Philadelphia is included in route mode") } } } }