diff --git a/SportsTimeTests/ScenarioAPlannerSwiftTests.swift b/SportsTimeTests/ScenarioAPlannerSwiftTests.swift index 959aa5b..db17ce7 100644 --- a/SportsTimeTests/ScenarioAPlannerSwiftTests.swift +++ b/SportsTimeTests/ScenarioAPlannerSwiftTests.swift @@ -867,4 +867,115 @@ struct ScenarioAPlannerSwiftTests { // Game should be excluded (after PST range end) #expect(result.failure?.reason == .noGamesInRange) } + + // MARK: - Same-Day Multi-City Conflict Tests + + @Test("same-day games in close cities are both included in route") + func plan_SameDayGamesCloseCities_BothIncluded() { + // LA game at 1pm, San Diego game at 7pm (120 miles, ~2hr drive) + let laStadium = makeStadium(city: "Los Angeles", latitude: 34.0522, longitude: -118.2437) + let sdStadium = makeStadium(city: "San Diego", latitude: 32.7157, longitude: -117.1611) + + let base = baseDate() + let laGame = makeGame(stadiumId: laStadium.id, dateTime: date(daysFrom: base, days: 0, hour: 13)) + let sdGame = makeGame(stadiumId: sdStadium.id, dateTime: date(daysFrom: base, days: 0, hour: 19)) + + let result = plan( + games: [laGame, sdGame], + stadiums: [laStadium, sdStadium], + dateRange: makeDateRange(start: base, days: 10) + ) + + // Should succeed with both games in route (enough time to drive between) + #expect(result.isSuccess) + let twoStopOption = result.options.first { $0.stops.count == 2 } + #expect(twoStopOption != nil, "Should have route with both cities") + #expect(twoStopOption?.totalGames == 2) + } + + @Test("same-day games in distant cities only one included per route") + func plan_SameDayGamesDistantCities_OnlyOnePerRoute() { + // LA game at 1pm, SF game at 7pm (380 miles, ~6hr drive) + let laStadium = makeStadium(city: "Los Angeles", latitude: 34.0522, longitude: -118.2437) + let sfStadium = makeStadium(city: "San Francisco", latitude: 37.7749, longitude: -122.4194) + + let base = baseDate() + let laGame = makeGame(stadiumId: laStadium.id, dateTime: date(daysFrom: base, days: 0, hour: 13)) + let sfGame = makeGame(stadiumId: sfStadium.id, dateTime: date(daysFrom: base, days: 0, hour: 19)) + + let result = plan( + games: [laGame, sfGame], + stadiums: [laStadium, sfStadium], + dateRange: makeDateRange(start: base, days: 10) + ) + + // Should succeed but each route picks ONE game (cannot attend both same day) + #expect(result.isSuccess) + for option in result.options { + // Each option should have only 1 stop (cannot do both same day) + #expect(option.stops.count == 1, "Route should pick only one game - cannot attend both LA and SF same day") + } + } + + @Test("same-day games on opposite coasts only one included per route") + func plan_SameDayGamesOppositCoasts_OnlyOnePerRoute() { + // LA game at 1pm PST, NY game at 7pm EST (2800 miles, impossible same day) + let laStadium = makeStadium(city: "Los Angeles", latitude: 34.0522, longitude: -118.2437) + let nyStadium = makeStadium(city: "New York", latitude: 40.7128, longitude: -74.0060) + + let base = baseDate() + let laGame = makeGame(stadiumId: laStadium.id, dateTime: date(daysFrom: base, days: 0, hour: 13)) + let nyGame = makeGame(stadiumId: nyStadium.id, dateTime: date(daysFrom: base, days: 0, hour: 19)) + + let result = plan( + games: [laGame, nyGame], + stadiums: [laStadium, nyStadium], + dateRange: makeDateRange(start: base, days: 10) + ) + + // Should succeed but each route picks ONE game (obviously impossible same day) + #expect(result.isSuccess) + for option in result.options { + #expect(option.stops.count == 1, "Route should pick only one game - cannot attend both coasts same day") + } + } + + @Test("three same-day games picks feasible combinations") + func plan_ThreeSameDayGames_PicksFeasibleCombinations() { + // LA 1pm, Anaheim 4pm (30mi), San Diego 7pm (90mi from Anaheim) + // Feasible: LA→Anaheim→SD + // Cannot include NY game same day + let laStadium = makeStadium(city: "Los Angeles", latitude: 34.0522, longitude: -118.2437) + let anaheimStadium = makeStadium(city: "Anaheim", latitude: 33.8003, longitude: -117.8827) + let sdStadium = makeStadium(city: "San Diego", latitude: 32.7157, longitude: -117.1611) + let nyStadium = makeStadium(city: "New York", latitude: 40.7128, longitude: -74.0060) + + let base = baseDate() + let laGame = makeGame(stadiumId: laStadium.id, dateTime: date(daysFrom: base, days: 0, hour: 13)) + let anaheimGame = makeGame(stadiumId: anaheimStadium.id, dateTime: date(daysFrom: base, days: 0, hour: 16)) + let sdGame = makeGame(stadiumId: sdStadium.id, dateTime: date(daysFrom: base, days: 0, hour: 19)) + let nyGame = makeGame(stadiumId: nyStadium.id, dateTime: date(daysFrom: base, days: 0, hour: 19)) + + let result = plan( + games: [laGame, anaheimGame, sdGame, nyGame], + stadiums: [laStadium, anaheimStadium, sdStadium, nyStadium], + dateRange: makeDateRange(start: base, days: 10) + ) + + // Should have options, and best option includes the 3 West Coast games + #expect(result.isSuccess) + + // Should have a 3-stop option (LA→Anaheim→SD) + let threeStopOption = result.options.first { $0.stops.count == 3 } + #expect(threeStopOption != nil, "Should have route with 3 West Coast stops") + #expect(threeStopOption?.totalGames == 3) + + // No option should include NY with any other game from same day + for option in result.options { + let cities = option.stops.map { $0.city } + if cities.contains("New York") { + #expect(option.stops.count == 1, "NY game cannot be combined with West Coast games same day") + } + } + } }