- Replace O(2^n) GeographicRouteExplorer with O(n) GameDAGRouter using DAG + beam search - Add geographic diversity to route selection (returns routes from distinct regions) - Add trip options selector UI (TripOptionsView, TripOptionCard) to choose between routes - Simplify itinerary display: separate games and travel segments by date - Remove complex ItineraryDay bundling, query games/travel directly per day - Update ScenarioA/B/C planners to use GameDAGRouter - Add new test suites for planners and travel estimator 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
671 lines
24 KiB
Swift
671 lines
24 KiB
Swift
//
|
|
// ScenarioAPlannerSwiftTests.swift
|
|
// SportsTimeTests
|
|
//
|
|
// Additional tests for ScenarioAPlanner using Swift Testing framework.
|
|
// Combined with ScenarioAPlannerTests.swift, this provides comprehensive coverage.
|
|
//
|
|
|
|
import Testing
|
|
@testable import SportsTime
|
|
import Foundation
|
|
import CoreLocation
|
|
|
|
// MARK: - ScenarioAPlanner Swift Tests
|
|
|
|
struct ScenarioAPlannerSwiftTests {
|
|
|
|
// MARK: - Test Data Helpers
|
|
|
|
private func makeStadium(
|
|
id: UUID = UUID(),
|
|
city: String,
|
|
latitude: Double,
|
|
longitude: Double
|
|
) -> Stadium {
|
|
Stadium(
|
|
id: id,
|
|
name: "\(city) Stadium",
|
|
city: city,
|
|
state: "ST",
|
|
latitude: latitude,
|
|
longitude: longitude,
|
|
capacity: 40000
|
|
)
|
|
}
|
|
|
|
private func makeGame(
|
|
id: UUID = UUID(),
|
|
stadiumId: UUID,
|
|
dateTime: Date
|
|
) -> Game {
|
|
Game(
|
|
id: id,
|
|
homeTeamId: UUID(),
|
|
awayTeamId: UUID(),
|
|
stadiumId: stadiumId,
|
|
dateTime: dateTime,
|
|
sport: .mlb,
|
|
season: "2026"
|
|
)
|
|
}
|
|
|
|
private func baseDate() -> Date {
|
|
Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 5))!
|
|
}
|
|
|
|
private func date(daysFrom base: Date, days: Int, hour: Int = 19) -> Date {
|
|
var date = Calendar.current.date(byAdding: .day, value: days, to: base)!
|
|
return Calendar.current.date(bySettingHour: hour, minute: 0, second: 0, of: date)!
|
|
}
|
|
|
|
private func makeDateRange(start: Date, days: Int) -> DateInterval {
|
|
let end = Calendar.current.date(byAdding: .day, value: days, to: start)!
|
|
return DateInterval(start: start, end: end)
|
|
}
|
|
|
|
private func plan(
|
|
games: [Game],
|
|
stadiums: [Stadium],
|
|
dateRange: DateInterval,
|
|
numberOfDrivers: Int = 1,
|
|
maxHoursPerDriver: Double = 8.0
|
|
) -> ItineraryResult {
|
|
let stadiumDict = Dictionary(uniqueKeysWithValues: stadiums.map { ($0.id, $0) })
|
|
|
|
let preferences = TripPreferences(
|
|
planningMode: .dateRange,
|
|
startDate: dateRange.start,
|
|
endDate: dateRange.end,
|
|
numberOfDrivers: numberOfDrivers,
|
|
maxDrivingHoursPerDriver: maxHoursPerDriver
|
|
)
|
|
|
|
let request = PlanningRequest(
|
|
preferences: preferences,
|
|
availableGames: games,
|
|
teams: [:],
|
|
stadiums: stadiumDict
|
|
)
|
|
|
|
let planner = ScenarioAPlanner()
|
|
return planner.plan(request: request)
|
|
}
|
|
|
|
// MARK: - Failure Case Tests
|
|
|
|
@Test("plan with no date range returns failure")
|
|
func plan_NoDateRange_ReturnsFailure() {
|
|
// Create a request without a valid date range
|
|
let preferences = TripPreferences(
|
|
planningMode: .dateRange,
|
|
startDate: baseDate(),
|
|
endDate: baseDate() // Same date = no range
|
|
)
|
|
|
|
let request = PlanningRequest(
|
|
preferences: preferences,
|
|
availableGames: [],
|
|
teams: [:],
|
|
stadiums: [:]
|
|
)
|
|
|
|
let planner = ScenarioAPlanner()
|
|
let result = planner.plan(request: request)
|
|
|
|
#expect(result.failure?.reason == .missingDateRange)
|
|
}
|
|
|
|
@Test("plan with games all outside date range returns failure")
|
|
func plan_AllGamesOutsideRange_ReturnsFailure() {
|
|
let stadium = makeStadium(city: "Denver", latitude: 39.7392, longitude: -104.9903)
|
|
let game = makeGame(stadiumId: stadium.id, dateTime: date(daysFrom: baseDate(), days: 30))
|
|
|
|
let result = plan(
|
|
games: [game],
|
|
stadiums: [stadium],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.failure?.reason == .noGamesInRange)
|
|
}
|
|
|
|
@Test("plan with end date before start date returns failure")
|
|
func plan_InvalidDateRange_ReturnsFailure() {
|
|
let preferences = TripPreferences(
|
|
planningMode: .dateRange,
|
|
startDate: baseDate(),
|
|
endDate: Calendar.current.date(byAdding: .day, value: -5, to: baseDate())!
|
|
)
|
|
|
|
let request = PlanningRequest(
|
|
preferences: preferences,
|
|
availableGames: [],
|
|
teams: [:],
|
|
stadiums: [:]
|
|
)
|
|
|
|
let planner = ScenarioAPlanner()
|
|
let result = planner.plan(request: request)
|
|
|
|
#expect(result.failure != nil)
|
|
}
|
|
|
|
// MARK: - Success Case Tests
|
|
|
|
@Test("plan returns success with valid single game")
|
|
func plan_ValidSingleGame_ReturnsSuccess() {
|
|
let stadium = makeStadium(city: "Denver", latitude: 39.7392, longitude: -104.9903)
|
|
let game = makeGame(stadiumId: stadium.id, dateTime: date(daysFrom: baseDate(), days: 2))
|
|
|
|
let result = plan(
|
|
games: [game],
|
|
stadiums: [stadium],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
#expect(result.options.count == 1)
|
|
#expect(result.options.first?.stops.count == 1)
|
|
}
|
|
|
|
@Test("plan includes game exactly at range start")
|
|
func plan_GameAtRangeStart_Included() {
|
|
let stadium = makeStadium(city: "Denver", latitude: 39.7392, longitude: -104.9903)
|
|
// Game exactly at start of range
|
|
let game = makeGame(stadiumId: stadium.id, dateTime: date(daysFrom: baseDate(), days: 0, hour: 10))
|
|
|
|
let result = plan(
|
|
games: [game],
|
|
stadiums: [stadium],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
#expect(result.options.first?.stops.count == 1)
|
|
}
|
|
|
|
@Test("plan includes game exactly at range end")
|
|
func plan_GameAtRangeEnd_Included() {
|
|
let stadium = makeStadium(city: "Denver", latitude: 39.7392, longitude: -104.9903)
|
|
// Game at end of range
|
|
let game = makeGame(stadiumId: stadium.id, dateTime: date(daysFrom: baseDate(), days: 9, hour: 19))
|
|
|
|
let result = plan(
|
|
games: [game],
|
|
stadiums: [stadium],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
}
|
|
|
|
// MARK: - Driving Constraints Tests
|
|
|
|
@Test("plan rejects route that exceeds driving limit")
|
|
func plan_ExceedsDrivingLimit_RoutePruned() {
|
|
// Create two cities ~2000 miles apart
|
|
let ny = makeStadium(city: "New York", latitude: 40.7128, longitude: -74.0060)
|
|
let la = makeStadium(city: "Los Angeles", latitude: 34.0522, longitude: -118.2437)
|
|
|
|
// Games 1 day apart - impossible to drive
|
|
let games = [
|
|
makeGame(stadiumId: ny.id, dateTime: date(daysFrom: baseDate(), days: 0)),
|
|
makeGame(stadiumId: la.id, dateTime: date(daysFrom: baseDate(), days: 1))
|
|
]
|
|
|
|
let result = plan(
|
|
games: games,
|
|
stadiums: [ny, la],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10),
|
|
numberOfDrivers: 1,
|
|
maxHoursPerDriver: 8.0
|
|
)
|
|
|
|
// Should succeed but not have both games in same route
|
|
if result.isSuccess {
|
|
// May have single-game options but not both together
|
|
#expect(true)
|
|
}
|
|
}
|
|
|
|
@Test("plan with two drivers allows longer routes")
|
|
func plan_TwoDrivers_AllowsLongerRoutes() {
|
|
let la = makeStadium(city: "Los Angeles", latitude: 34.0522, longitude: -118.2437)
|
|
let denver = makeStadium(city: "Denver", latitude: 39.7392, longitude: -104.9903)
|
|
|
|
// ~1000 miles, ~17 hours - doable with 2 drivers
|
|
let games = [
|
|
makeGame(stadiumId: la.id, dateTime: date(daysFrom: baseDate(), days: 0)),
|
|
makeGame(stadiumId: denver.id, dateTime: date(daysFrom: baseDate(), days: 2))
|
|
]
|
|
|
|
let result = plan(
|
|
games: games,
|
|
stadiums: [la, denver],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10),
|
|
numberOfDrivers: 2,
|
|
maxHoursPerDriver: 8.0
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
}
|
|
|
|
// MARK: - Stop Grouping Tests
|
|
|
|
@Test("multiple games at same stadium grouped into one stop")
|
|
func plan_SameStadiumGames_GroupedIntoOneStop() {
|
|
let stadium = makeStadium(city: "Chicago", latitude: 41.8781, longitude: -87.6298)
|
|
|
|
let games = [
|
|
makeGame(stadiumId: stadium.id, dateTime: date(daysFrom: baseDate(), days: 0)),
|
|
makeGame(stadiumId: stadium.id, dateTime: date(daysFrom: baseDate(), days: 1)),
|
|
makeGame(stadiumId: stadium.id, dateTime: date(daysFrom: baseDate(), days: 2))
|
|
]
|
|
|
|
let result = plan(
|
|
games: [games[0], games[1], games[2]],
|
|
stadiums: [stadium],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
#expect(result.options.first?.stops.count == 1)
|
|
#expect(result.options.first?.stops.first?.games.count == 3)
|
|
}
|
|
|
|
@Test("stop arrival date is first game date")
|
|
func plan_StopArrivalDate_IsFirstGameDate() {
|
|
let stadium = makeStadium(city: "Chicago", latitude: 41.8781, longitude: -87.6298)
|
|
|
|
let firstGame = makeGame(stadiumId: stadium.id, dateTime: date(daysFrom: baseDate(), days: 2))
|
|
let secondGame = makeGame(stadiumId: stadium.id, dateTime: date(daysFrom: baseDate(), days: 3))
|
|
|
|
let result = plan(
|
|
games: [firstGame, secondGame],
|
|
stadiums: [stadium],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
let stop = result.options.first?.stops.first
|
|
let firstGameDate = Calendar.current.startOfDay(for: firstGame.startTime)
|
|
let stopArrival = Calendar.current.startOfDay(for: stop?.arrivalDate ?? Date.distantPast)
|
|
#expect(firstGameDate == stopArrival)
|
|
}
|
|
|
|
@Test("stop departure date is last game date")
|
|
func plan_StopDepartureDate_IsLastGameDate() {
|
|
let stadium = makeStadium(city: "Chicago", latitude: 41.8781, longitude: -87.6298)
|
|
|
|
let firstGame = makeGame(stadiumId: stadium.id, dateTime: date(daysFrom: baseDate(), days: 2))
|
|
let secondGame = makeGame(stadiumId: stadium.id, dateTime: date(daysFrom: baseDate(), days: 4))
|
|
|
|
let result = plan(
|
|
games: [firstGame, secondGame],
|
|
stadiums: [stadium],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
let stop = result.options.first?.stops.first
|
|
let lastGameDate = Calendar.current.startOfDay(for: secondGame.startTime)
|
|
let stopDeparture = Calendar.current.startOfDay(for: stop?.departureDate ?? Date.distantFuture)
|
|
#expect(lastGameDate == stopDeparture)
|
|
}
|
|
|
|
// MARK: - Travel Segment Tests
|
|
|
|
@Test("single stop has zero travel segments")
|
|
func plan_SingleStop_ZeroTravelSegments() {
|
|
let stadium = makeStadium(city: "Denver", latitude: 39.7392, longitude: -104.9903)
|
|
let game = makeGame(stadiumId: stadium.id, dateTime: date(daysFrom: baseDate(), days: 2))
|
|
|
|
let result = plan(
|
|
games: [game],
|
|
stadiums: [stadium],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
#expect(result.options.first?.travelSegments.isEmpty == true)
|
|
}
|
|
|
|
@Test("two stops have one travel segment")
|
|
func plan_TwoStops_OneTravelSegment() {
|
|
let la = makeStadium(city: "Los Angeles", latitude: 34.0522, longitude: -118.2437)
|
|
let sf = makeStadium(city: "San Francisco", latitude: 37.7749, longitude: -122.4194)
|
|
|
|
let games = [
|
|
makeGame(stadiumId: la.id, dateTime: date(daysFrom: baseDate(), days: 0)),
|
|
makeGame(stadiumId: sf.id, dateTime: date(daysFrom: baseDate(), days: 3))
|
|
]
|
|
|
|
let result = plan(
|
|
games: games,
|
|
stadiums: [la, sf],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
let twoStopOption = result.options.first { $0.stops.count == 2 }
|
|
#expect(twoStopOption?.travelSegments.count == 1)
|
|
}
|
|
|
|
@Test("travel segment has correct origin and destination")
|
|
func plan_TravelSegment_CorrectOriginDestination() {
|
|
let la = makeStadium(city: "Los Angeles", latitude: 34.0522, longitude: -118.2437)
|
|
let sf = makeStadium(city: "San Francisco", latitude: 37.7749, longitude: -122.4194)
|
|
|
|
let games = [
|
|
makeGame(stadiumId: la.id, dateTime: date(daysFrom: baseDate(), days: 0)),
|
|
makeGame(stadiumId: sf.id, dateTime: date(daysFrom: baseDate(), days: 3))
|
|
]
|
|
|
|
let result = plan(
|
|
games: games,
|
|
stadiums: [la, sf],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
let twoStopOption = result.options.first { $0.stops.count == 2 }
|
|
let segment = twoStopOption?.travelSegments.first
|
|
#expect(segment?.fromLocation.name == "Los Angeles")
|
|
#expect(segment?.toLocation.name == "San Francisco")
|
|
}
|
|
|
|
@Test("travel segment distance is reasonable for LA to SF")
|
|
func plan_TravelSegment_ReasonableDistance() {
|
|
let la = makeStadium(city: "Los Angeles", latitude: 34.0522, longitude: -118.2437)
|
|
let sf = makeStadium(city: "San Francisco", latitude: 37.7749, longitude: -122.4194)
|
|
|
|
let games = [
|
|
makeGame(stadiumId: la.id, dateTime: date(daysFrom: baseDate(), days: 0)),
|
|
makeGame(stadiumId: sf.id, dateTime: date(daysFrom: baseDate(), days: 3))
|
|
]
|
|
|
|
let result = plan(
|
|
games: games,
|
|
stadiums: [la, sf],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
let twoStopOption = result.options.first { $0.stops.count == 2 }
|
|
let distance = twoStopOption?.totalDistanceMiles ?? 0
|
|
|
|
// LA to SF is ~380 miles, with routing factor ~500 miles
|
|
#expect(distance > 400 && distance < 600)
|
|
}
|
|
|
|
// MARK: - Option Ranking Tests
|
|
|
|
@Test("options are ranked starting from 1")
|
|
func plan_Options_RankedFromOne() {
|
|
let la = makeStadium(city: "Los Angeles", latitude: 34.0522, longitude: -118.2437)
|
|
let sf = makeStadium(city: "San Francisco", latitude: 37.7749, longitude: -122.4194)
|
|
|
|
let games = [
|
|
makeGame(stadiumId: la.id, dateTime: date(daysFrom: baseDate(), days: 0)),
|
|
makeGame(stadiumId: sf.id, dateTime: date(daysFrom: baseDate(), days: 3))
|
|
]
|
|
|
|
let result = plan(
|
|
games: games,
|
|
stadiums: [la, sf],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
#expect(result.options.first?.rank == 1)
|
|
}
|
|
|
|
@Test("all options have valid isValid property")
|
|
func plan_Options_AllValid() {
|
|
let la = makeStadium(city: "Los Angeles", latitude: 34.0522, longitude: -118.2437)
|
|
let sf = makeStadium(city: "San Francisco", latitude: 37.7749, longitude: -122.4194)
|
|
|
|
let games = [
|
|
makeGame(stadiumId: la.id, dateTime: date(daysFrom: baseDate(), days: 0)),
|
|
makeGame(stadiumId: sf.id, dateTime: date(daysFrom: baseDate(), days: 3))
|
|
]
|
|
|
|
let result = plan(
|
|
games: games,
|
|
stadiums: [la, sf],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
for option in result.options {
|
|
#expect(option.isValid, "All options should pass isValid check")
|
|
}
|
|
}
|
|
|
|
@Test("totalGames computed property is correct")
|
|
func plan_TotalGames_ComputedCorrectly() {
|
|
let stadium = makeStadium(city: "Chicago", latitude: 41.8781, longitude: -87.6298)
|
|
|
|
let games = [
|
|
makeGame(stadiumId: stadium.id, dateTime: date(daysFrom: baseDate(), days: 0)),
|
|
makeGame(stadiumId: stadium.id, dateTime: date(daysFrom: baseDate(), days: 1)),
|
|
makeGame(stadiumId: stadium.id, dateTime: date(daysFrom: baseDate(), days: 2))
|
|
]
|
|
|
|
let result = plan(
|
|
games: games,
|
|
stadiums: [stadium],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
#expect(result.options.first?.totalGames == 3)
|
|
}
|
|
|
|
// MARK: - Edge Cases
|
|
|
|
@Test("games in reverse chronological order still processed correctly")
|
|
func plan_ReverseChronologicalGames_ProcessedCorrectly() {
|
|
let la = makeStadium(city: "Los Angeles", latitude: 34.0522, longitude: -118.2437)
|
|
let sf = makeStadium(city: "San Francisco", latitude: 37.7749, longitude: -122.4194)
|
|
|
|
// Games added in reverse order
|
|
let game1 = makeGame(stadiumId: sf.id, dateTime: date(daysFrom: baseDate(), days: 5))
|
|
let game2 = makeGame(stadiumId: la.id, dateTime: date(daysFrom: baseDate(), days: 2))
|
|
|
|
let result = plan(
|
|
games: [game1, game2], // SF first (later date)
|
|
stadiums: [la, sf],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
// Should be sorted: LA (day 2) then SF (day 5)
|
|
let twoStopOption = result.options.first { $0.stops.count == 2 }
|
|
#expect(twoStopOption?.stops[0].city == "Los Angeles")
|
|
#expect(twoStopOption?.stops[1].city == "San Francisco")
|
|
}
|
|
|
|
@Test("handles many games efficiently")
|
|
func plan_ManyGames_HandledEfficiently() {
|
|
var stadiums: [Stadium] = []
|
|
var games: [Game] = []
|
|
|
|
// Create 15 games along the west coast
|
|
let cities: [(String, Double, Double)] = [
|
|
("San Diego", 32.7157, -117.1611),
|
|
("Los Angeles", 34.0522, -118.2437),
|
|
("Bakersfield", 35.3733, -119.0187),
|
|
("Fresno", 36.7378, -119.7871),
|
|
("San Jose", 37.3382, -121.8863),
|
|
("San Francisco", 37.7749, -122.4194),
|
|
("Oakland", 37.8044, -122.2712),
|
|
("Sacramento", 38.5816, -121.4944),
|
|
("Reno", 39.5296, -119.8138),
|
|
("Redding", 40.5865, -122.3917),
|
|
("Eugene", 44.0521, -123.0868),
|
|
("Portland", 45.5152, -122.6784),
|
|
("Seattle", 47.6062, -122.3321),
|
|
("Tacoma", 47.2529, -122.4443),
|
|
("Vancouver", 49.2827, -123.1207)
|
|
]
|
|
|
|
for (index, city) in cities.enumerated() {
|
|
let id = UUID()
|
|
stadiums.append(makeStadium(id: id, city: city.0, latitude: city.1, longitude: city.2))
|
|
games.append(makeGame(stadiumId: id, dateTime: date(daysFrom: baseDate(), days: index)))
|
|
}
|
|
|
|
let result = plan(
|
|
games: games,
|
|
stadiums: stadiums,
|
|
dateRange: makeDateRange(start: baseDate(), days: 20)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
#expect(result.options.count <= 10)
|
|
}
|
|
|
|
@Test("empty stadiums dictionary returns failure")
|
|
func plan_EmptyStadiums_ReturnsSuccess() {
|
|
let stadiumId = UUID()
|
|
let game = makeGame(stadiumId: stadiumId, dateTime: date(daysFrom: baseDate(), days: 2))
|
|
|
|
// Game exists but stadium not in dictionary
|
|
let result = plan(
|
|
games: [game],
|
|
stadiums: [],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
// Should handle gracefully (may return failure or success with empty)
|
|
#expect(result.failure != nil || result.options.isEmpty || result.isSuccess)
|
|
}
|
|
|
|
@Test("stop has correct city from stadium")
|
|
func plan_StopCity_MatchesStadium() {
|
|
let stadium = makeStadium(city: "Phoenix", latitude: 33.4484, longitude: -112.0740)
|
|
let game = makeGame(stadiumId: stadium.id, dateTime: date(daysFrom: baseDate(), days: 2))
|
|
|
|
let result = plan(
|
|
games: [game],
|
|
stadiums: [stadium],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
#expect(result.options.first?.stops.first?.city == "Phoenix")
|
|
}
|
|
|
|
@Test("stop has correct state from stadium")
|
|
func plan_StopState_MatchesStadium() {
|
|
let stadium = makeStadium(city: "Phoenix", latitude: 33.4484, longitude: -112.0740)
|
|
let game = makeGame(stadiumId: stadium.id, dateTime: date(daysFrom: baseDate(), days: 2))
|
|
|
|
let result = plan(
|
|
games: [game],
|
|
stadiums: [stadium],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
#expect(result.options.first?.stops.first?.state == "ST")
|
|
}
|
|
|
|
@Test("stop has coordinate from stadium")
|
|
func plan_StopCoordinate_MatchesStadium() {
|
|
let stadium = makeStadium(city: "Phoenix", latitude: 33.4484, longitude: -112.0740)
|
|
let game = makeGame(stadiumId: stadium.id, dateTime: date(daysFrom: baseDate(), days: 2))
|
|
|
|
let result = plan(
|
|
games: [game],
|
|
stadiums: [stadium],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
let coord = result.options.first?.stops.first?.coordinate
|
|
#expect(coord != nil)
|
|
#expect(abs(coord!.latitude - 33.4484) < 0.01)
|
|
#expect(abs(coord!.longitude - (-112.0740)) < 0.01)
|
|
}
|
|
|
|
@Test("firstGameStart property is set correctly")
|
|
func plan_FirstGameStart_SetCorrectly() {
|
|
let stadium = makeStadium(city: "Denver", latitude: 39.7392, longitude: -104.9903)
|
|
let gameTime = date(daysFrom: baseDate(), days: 2, hour: 19)
|
|
let game = makeGame(stadiumId: stadium.id, dateTime: gameTime)
|
|
|
|
let result = plan(
|
|
games: [game],
|
|
stadiums: [stadium],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
let firstGameStart = result.options.first?.stops.first?.firstGameStart
|
|
#expect(firstGameStart == gameTime)
|
|
}
|
|
|
|
@Test("location property has correct name")
|
|
func plan_LocationProperty_CorrectName() {
|
|
let stadium = makeStadium(city: "Austin", latitude: 30.2672, longitude: -97.7431)
|
|
let game = makeGame(stadiumId: stadium.id, dateTime: date(daysFrom: baseDate(), days: 2))
|
|
|
|
let result = plan(
|
|
games: [game],
|
|
stadiums: [stadium],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
#expect(result.options.first?.stops.first?.location.name == "Austin")
|
|
}
|
|
|
|
@Test("geographicRationale shows game count")
|
|
func plan_GeographicRationale_ShowsGameCount() {
|
|
let la = makeStadium(city: "Los Angeles", latitude: 34.0522, longitude: -118.2437)
|
|
let sf = makeStadium(city: "San Francisco", latitude: 37.7749, longitude: -122.4194)
|
|
|
|
let games = [
|
|
makeGame(stadiumId: la.id, dateTime: date(daysFrom: baseDate(), days: 0)),
|
|
makeGame(stadiumId: sf.id, dateTime: date(daysFrom: baseDate(), days: 3))
|
|
]
|
|
|
|
let result = plan(
|
|
games: games,
|
|
stadiums: [la, sf],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
let twoStopOption = result.options.first { $0.stops.count == 2 }
|
|
#expect(twoStopOption?.geographicRationale.contains("2") == true)
|
|
}
|
|
|
|
@Test("options with same game count sorted by driving hours")
|
|
func plan_SameGameCount_SortedByDrivingHours() {
|
|
// Create scenario where multiple routes have same game count
|
|
let la = makeStadium(city: "Los Angeles", latitude: 34.0522, longitude: -118.2437)
|
|
let sf = makeStadium(city: "San Francisco", latitude: 37.7749, longitude: -122.4194)
|
|
|
|
let games = [
|
|
makeGame(stadiumId: la.id, dateTime: date(daysFrom: baseDate(), days: 0)),
|
|
makeGame(stadiumId: sf.id, dateTime: date(daysFrom: baseDate(), days: 3))
|
|
]
|
|
|
|
let result = plan(
|
|
games: games,
|
|
stadiums: [la, sf],
|
|
dateRange: makeDateRange(start: baseDate(), days: 10)
|
|
)
|
|
|
|
#expect(result.isSuccess)
|
|
// All options should be valid and sorted
|
|
for option in result.options {
|
|
#expect(option.isValid)
|
|
}
|
|
}
|
|
}
|