Harden planning test suite with realistic fixtures and output sanity checks
Adds messy/realistic data factories to TestFixtures, new PlannerOutputSanityTests, and updates all scenario planner tests with improved coverage and assertions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -548,41 +548,47 @@ struct ScenarioEPlannerTests {
|
||||
|
||||
@Test("plan: routes sorted by duration ascending")
|
||||
func plan_routesSortedByDurationAscending() {
|
||||
let baseDate = TestClock.now
|
||||
let calendar = TestClock.calendar
|
||||
let baseDate = calendar.startOfDay(for: TestClock.now)
|
||||
|
||||
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
|
||||
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
|
||||
|
||||
// Create multiple windows with different durations
|
||||
// Window 1: Games on day 1 and 2 (shorter trip)
|
||||
// Window 2: Games on day 10 and 14 (longer trip within window)
|
||||
// 2 teams → windowDuration = 4 days. Games must span at least 3 calendar days.
|
||||
// Window 1: Games on day 1 and day 4 (tighter)
|
||||
// Window 2: Games on day 10 and day 13 (separate window)
|
||||
let day1Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 1))!
|
||||
let day4Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 4))!
|
||||
let day10Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 10))!
|
||||
let day13Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 13))!
|
||||
|
||||
let yankeesGame1 = makeGame(
|
||||
id: "yankees-1",
|
||||
homeTeamId: "yankees",
|
||||
awayTeamId: "opponent",
|
||||
stadiumId: "nyc",
|
||||
dateTime: baseDate.addingTimeInterval(86400 * 1)
|
||||
dateTime: day1Evening
|
||||
)
|
||||
let redsoxGame1 = makeGame(
|
||||
id: "redsox-1",
|
||||
homeTeamId: "redsox",
|
||||
awayTeamId: "opponent",
|
||||
stadiumId: "boston",
|
||||
dateTime: baseDate.addingTimeInterval(86400 * 2)
|
||||
dateTime: day4Evening
|
||||
)
|
||||
let yankeesGame2 = makeGame(
|
||||
id: "yankees-2",
|
||||
homeTeamId: "yankees",
|
||||
awayTeamId: "opponent",
|
||||
stadiumId: "nyc",
|
||||
dateTime: baseDate.addingTimeInterval(86400 * 10)
|
||||
dateTime: day10Evening
|
||||
)
|
||||
let redsoxGame2 = makeGame(
|
||||
id: "redsox-2",
|
||||
homeTeamId: "redsox",
|
||||
awayTeamId: "opponent",
|
||||
stadiumId: "boston",
|
||||
dateTime: baseDate.addingTimeInterval(86400 * 12)
|
||||
dateTime: day13Evening
|
||||
)
|
||||
|
||||
let prefs = TripPreferences(
|
||||
@@ -617,30 +623,43 @@ struct ScenarioEPlannerTests {
|
||||
for (index, option) in options.enumerated() {
|
||||
#expect(option.rank == index + 1, "Routes should be ranked 1, 2, 3...")
|
||||
}
|
||||
|
||||
// Verify actual duration ordering: each option's trip duration <= next option's
|
||||
for i in 0..<(options.count - 1) {
|
||||
let daysA = Calendar.current.dateComponents([.day], from: options[i].stops.first!.arrivalDate, to: options[i].stops.last!.departureDate).day ?? 0
|
||||
let daysB = Calendar.current.dateComponents([.day], from: options[i+1].stops.first!.arrivalDate, to: options[i+1].stops.last!.departureDate).day ?? 0
|
||||
#expect(daysA <= daysB, "Option \(i) duration \(daysA)d should be <= option \(i+1) duration \(daysB)d")
|
||||
}
|
||||
}
|
||||
|
||||
@Test("plan: respects max driving time constraint")
|
||||
func plan_respectsMaxDrivingTimeConstraint() {
|
||||
let baseDate = TestClock.now
|
||||
let calendar = TestClock.calendar
|
||||
let baseDate = calendar.startOfDay(for: TestClock.now)
|
||||
|
||||
// NYC and LA are ~40 hours apart by car
|
||||
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
|
||||
let laStadium = makeStadium(id: "la", city: "Los Angeles", coordinate: laCoord)
|
||||
|
||||
// Games on consecutive days - impossible to drive between
|
||||
// 2 teams → windowDuration = 4 days. Games must span at least 3 calendar days.
|
||||
// Spread games apart so the window generator produces a valid window,
|
||||
// but keep them on opposite coasts so the driving constraint rejects the route.
|
||||
let day1Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 1))!
|
||||
let day5Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 5))!
|
||||
|
||||
let yankeesGame = makeGame(
|
||||
id: "yankees-home",
|
||||
homeTeamId: "yankees",
|
||||
awayTeamId: "opponent",
|
||||
stadiumId: "nyc",
|
||||
dateTime: baseDate.addingTimeInterval(86400 * 1)
|
||||
dateTime: day1Evening
|
||||
)
|
||||
let dodgersGame = makeGame(
|
||||
id: "dodgers-home",
|
||||
homeTeamId: "dodgers",
|
||||
awayTeamId: "opponent",
|
||||
stadiumId: "la",
|
||||
dateTime: baseDate.addingTimeInterval(86400 * 2) // Next day - impossible
|
||||
dateTime: day5Evening
|
||||
)
|
||||
|
||||
let prefs = TripPreferences(
|
||||
@@ -650,7 +669,7 @@ struct ScenarioEPlannerTests {
|
||||
endDate: baseDate.addingTimeInterval(86400 * 30),
|
||||
leisureLevel: .moderate,
|
||||
lodgingType: .hotel,
|
||||
numberOfDrivers: 1, // Single driver, 8 hours max
|
||||
numberOfDrivers: 1, // Single driver, 8 hours max — impossible NYC→LA
|
||||
selectedTeamIds: ["yankees", "dodgers"]
|
||||
)
|
||||
|
||||
@@ -989,31 +1008,39 @@ struct ScenarioEPlannerTests {
|
||||
|
||||
let result = planner.plan(request: request)
|
||||
|
||||
if case .success(let options) = result {
|
||||
#expect(options.count <= 10, "Should return at most 10 results")
|
||||
guard case .success(let options) = result else {
|
||||
Issue.record("Expected .success, got \(result)")
|
||||
return
|
||||
}
|
||||
|
||||
#expect(options.count <= 10, "Should return at most 10 results")
|
||||
}
|
||||
|
||||
@Test("Invariant: all routes contain home games from all selected teams")
|
||||
func invariant_allRoutesContainAllSelectedTeams() {
|
||||
let baseDate = TestClock.now
|
||||
let calendar = TestClock.calendar
|
||||
let baseDate = calendar.startOfDay(for: TestClock.now)
|
||||
|
||||
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
|
||||
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
|
||||
|
||||
// 2 teams → windowDuration = 4 days. Games must span at least 3 calendar days.
|
||||
let day1Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 1))!
|
||||
let day4Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 4))!
|
||||
|
||||
let yankeesGame = makeGame(
|
||||
id: "yankees-home",
|
||||
homeTeamId: "yankees",
|
||||
awayTeamId: "opponent",
|
||||
stadiumId: "nyc",
|
||||
dateTime: baseDate.addingTimeInterval(86400 * 1)
|
||||
dateTime: day1Evening
|
||||
)
|
||||
let redsoxGame = makeGame(
|
||||
id: "redsox-home",
|
||||
homeTeamId: "redsox",
|
||||
awayTeamId: "opponent",
|
||||
stadiumId: "boston",
|
||||
dateTime: baseDate.addingTimeInterval(86400 * 2)
|
||||
dateTime: day4Evening
|
||||
)
|
||||
|
||||
let prefs = TripPreferences(
|
||||
@@ -1039,22 +1066,25 @@ struct ScenarioEPlannerTests {
|
||||
|
||||
let result = planner.plan(request: request)
|
||||
|
||||
if case .success(let options) = result {
|
||||
for option in options {
|
||||
let allGameIds = Set(option.stops.flatMap { $0.games })
|
||||
guard case .success(let options) = result else {
|
||||
Issue.record("Expected .success, got \(result)")
|
||||
return
|
||||
}
|
||||
|
||||
// At minimum, should have one game per selected team
|
||||
let hasYankeesGame = allGameIds.contains { gameId in
|
||||
// Check if any game in this route is a Yankees home game
|
||||
request.availableGames.first { $0.id == gameId }?.homeTeamId == "yankees"
|
||||
}
|
||||
let hasRedsoxGame = allGameIds.contains { gameId in
|
||||
request.availableGames.first { $0.id == gameId }?.homeTeamId == "redsox"
|
||||
}
|
||||
for option in options {
|
||||
let allGameIds = Set(option.stops.flatMap { $0.games })
|
||||
|
||||
#expect(hasYankeesGame, "Every route must include a Yankees home game")
|
||||
#expect(hasRedsoxGame, "Every route must include a Red Sox home game")
|
||||
// At minimum, should have one game per selected team
|
||||
let hasYankeesGame = allGameIds.contains { gameId in
|
||||
// Check if any game in this route is a Yankees home game
|
||||
request.availableGames.first { $0.id == gameId }?.homeTeamId == "yankees"
|
||||
}
|
||||
let hasRedsoxGame = allGameIds.contains { gameId in
|
||||
request.availableGames.first { $0.id == gameId }?.homeTeamId == "redsox"
|
||||
}
|
||||
|
||||
#expect(hasYankeesGame, "Every route must include a Yankees home game")
|
||||
#expect(hasRedsoxGame, "Every route must include a Red Sox home game")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1096,50 +1126,58 @@ struct ScenarioEPlannerTests {
|
||||
let planner = ScenarioEPlanner()
|
||||
let result = planner.plan(request: request)
|
||||
|
||||
// Should succeed — both teams have east coast games
|
||||
if case .success(let options) = result {
|
||||
// Should succeed — both teams have east coast games.
|
||||
// Failure is also acceptable if routing constraints prevent a valid route.
|
||||
switch result {
|
||||
case .success(let options):
|
||||
for option in options {
|
||||
let cities = option.stops.map { $0.city }
|
||||
#expect(!cities.contains("Los Angeles"), "East-only filter should exclude LA")
|
||||
}
|
||||
case .failure:
|
||||
break // Acceptable — routing constraints may prevent a valid route
|
||||
}
|
||||
// If it fails, that's also acceptable since routing may not work out
|
||||
}
|
||||
|
||||
@Test("teamFirst: all regions includes everything")
|
||||
func teamFirst_allRegions_includesEverything() {
|
||||
let teamNYC = TestFixtures.team(id: "team_nyc", city: "New York")
|
||||
let teamLA = TestFixtures.team(id: "team_la", city: "Los Angeles")
|
||||
let teamBOS = TestFixtures.team(id: "team_bos", city: "Boston")
|
||||
|
||||
let stadiumNYC = TestFixtures.stadium(id: "stadium_nyc", city: "New York")
|
||||
let stadiumLA = TestFixtures.stadium(id: "stadium_la", city: "Los Angeles")
|
||||
let stadiumBOS = TestFixtures.stadium(id: "stadium_bos", city: "Boston")
|
||||
|
||||
let baseDate = TestFixtures.date(year: 2026, month: 6, day: 1, hour: 19, minute: 0)
|
||||
let day5 = TestClock.calendar.date(byAdding: .day, value: 4, to: baseDate)!
|
||||
// 2 teams → windowDuration = 4 days. Games must be within 3 days to fit in a single window.
|
||||
let day4 = TestClock.calendar.date(byAdding: .day, value: 3, to: baseDate)!
|
||||
|
||||
let gameNYC = TestFixtures.game(id: "game_nyc", city: "New York", dateTime: baseDate, homeTeamId: "team_nyc", stadiumId: "stadium_nyc")
|
||||
let gameLA = TestFixtures.game(id: "game_la", city: "Los Angeles", dateTime: day5, homeTeamId: "team_la", stadiumId: "stadium_la")
|
||||
let gameBOS = TestFixtures.game(id: "game_bos", city: "Boston", dateTime: day4, homeTeamId: "team_bos", stadiumId: "stadium_bos")
|
||||
|
||||
let prefs = TripPreferences(
|
||||
planningMode: .teamFirst,
|
||||
sports: [.mlb],
|
||||
numberOfDrivers: 2,
|
||||
selectedRegions: [.east, .central, .west], // All regions
|
||||
selectedTeamIds: ["team_nyc", "team_la"]
|
||||
selectedTeamIds: ["team_nyc", "team_bos"]
|
||||
)
|
||||
|
||||
let request = PlanningRequest(
|
||||
preferences: prefs,
|
||||
availableGames: [gameNYC, gameLA],
|
||||
teams: ["team_nyc": teamNYC, "team_la": teamLA],
|
||||
stadiums: ["stadium_nyc": stadiumNYC, "stadium_la": stadiumLA]
|
||||
availableGames: [gameNYC, gameBOS],
|
||||
teams: ["team_nyc": teamNYC, "team_bos": teamBOS],
|
||||
stadiums: ["stadium_nyc": stadiumNYC, "stadium_bos": stadiumBOS]
|
||||
)
|
||||
|
||||
let planner = ScenarioEPlanner()
|
||||
let result = planner.plan(request: request)
|
||||
|
||||
// With all regions, both games should be available
|
||||
// (may still fail due to driving constraints, but games won't be region-filtered)
|
||||
#expect(result.isSuccess || result.failure?.reason == .constraintsUnsatisfiable || result.failure?.reason == .noValidRoutes)
|
||||
// With all regions and nearby east-coast cities, planning should succeed
|
||||
guard case .success(let options) = result else {
|
||||
Issue.record("Expected .success with all regions and feasible route, got \(result)")
|
||||
return
|
||||
}
|
||||
#expect(!options.isEmpty, "Should return at least one route option")
|
||||
}
|
||||
|
||||
@Test("teamFirst: empty regions includes everything")
|
||||
@@ -1151,14 +1189,16 @@ struct ScenarioEPlannerTests {
|
||||
let stadiumBOS = TestFixtures.stadium(id: "stadium_bos", city: "Boston")
|
||||
|
||||
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)!
|
||||
// 2 teams → windowDuration = 4 days. Games must be within 3 days to fit in a single window.
|
||||
let day4 = TestClock.calendar.date(byAdding: .day, value: 3, to: baseDate)!
|
||||
|
||||
let gameNYC = TestFixtures.game(id: "game_nyc", city: "New York", dateTime: baseDate, homeTeamId: "team_nyc", stadiumId: "stadium_nyc")
|
||||
let gameBOS = TestFixtures.game(id: "game_bos", city: "Boston", dateTime: day2, homeTeamId: "team_bos", stadiumId: "stadium_bos")
|
||||
let gameBOS = TestFixtures.game(id: "game_bos", city: "Boston", dateTime: day4, homeTeamId: "team_bos", stadiumId: "stadium_bos")
|
||||
|
||||
let prefs = TripPreferences(
|
||||
planningMode: .teamFirst,
|
||||
sports: [.mlb],
|
||||
numberOfDrivers: 2,
|
||||
selectedRegions: [], // Empty = no filtering
|
||||
selectedTeamIds: ["team_nyc", "team_bos"]
|
||||
)
|
||||
@@ -1173,8 +1213,12 @@ struct ScenarioEPlannerTests {
|
||||
let planner = ScenarioEPlanner()
|
||||
let result = planner.plan(request: request)
|
||||
|
||||
// Empty regions = no filtering, so both games should be available
|
||||
#expect(result.isSuccess || result.failure?.reason != .noGamesInRange)
|
||||
// Empty regions = no filtering, so both games should be available and route feasible
|
||||
guard case .success(let options) = result else {
|
||||
Issue.record("Expected .success with empty regions (no filtering) and feasible route, got \(result)")
|
||||
return
|
||||
}
|
||||
#expect(!options.isEmpty, "Should return at least one route option")
|
||||
}
|
||||
|
||||
// MARK: - Past Date Filtering Tests
|
||||
@@ -1256,16 +1300,18 @@ struct ScenarioEPlannerTests {
|
||||
let planner = ScenarioEPlanner(currentDate: currentDate)
|
||||
let result = planner.plan(request: request)
|
||||
|
||||
if case .success(let options) = result {
|
||||
// All returned stops should be on or after currentDate
|
||||
for option in options {
|
||||
for stop in option.stops {
|
||||
#expect(stop.arrivalDate >= calendar.startOfDay(for: currentDate),
|
||||
"All stops should be in the future, got \(stop.arrivalDate)")
|
||||
}
|
||||
guard case .success(let options) = result else {
|
||||
Issue.record("Expected .success, got \(result)")
|
||||
return
|
||||
}
|
||||
|
||||
// All returned stops should be on or after currentDate
|
||||
for option in options {
|
||||
for stop in option.stops {
|
||||
#expect(stop.arrivalDate >= calendar.startOfDay(for: currentDate),
|
||||
"All stops should be in the future, got \(stop.arrivalDate)")
|
||||
}
|
||||
}
|
||||
// Failure is acceptable if routing constraints prevent a valid route
|
||||
}
|
||||
|
||||
@Test("teamFirst: evaluates all sampled windows across full season")
|
||||
@@ -1328,6 +1374,165 @@ struct ScenarioEPlannerTests {
|
||||
#expect(months.count >= 2, "Results should span at least 2 months, got months: \(months.sorted())")
|
||||
}
|
||||
|
||||
// MARK: - Output Sanity
|
||||
|
||||
@Test("plan: all stop dates in the future (synthetic regression)")
|
||||
func plan_allStopDatesInFuture_syntheticRegression() {
|
||||
// Regression for the PHI/WSN/BAL bug: past spring training games in output
|
||||
let currentDate = TestFixtures.date(year: 2026, month: 6, day: 1, hour: 12)
|
||||
let calendar = TestClock.calendar
|
||||
|
||||
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
|
||||
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
|
||||
|
||||
// Mix of past and future games
|
||||
let pastGame1 = makeGame(id: "past-nyc", homeTeamId: "yankees", awayTeamId: "opp",
|
||||
stadiumId: "nyc",
|
||||
dateTime: TestFixtures.date(year: 2026, month: 3, day: 10, hour: 13))
|
||||
let pastGame2 = makeGame(id: "past-bos", homeTeamId: "redsox", awayTeamId: "opp",
|
||||
stadiumId: "boston",
|
||||
dateTime: TestFixtures.date(year: 2026, month: 3, day: 12, hour: 13))
|
||||
let futureGame1 = makeGame(id: "future-nyc", homeTeamId: "yankees", awayTeamId: "opp",
|
||||
stadiumId: "nyc",
|
||||
dateTime: TestFixtures.date(year: 2026, month: 6, day: 5, hour: 19))
|
||||
let futureGame2 = makeGame(id: "future-bos", homeTeamId: "redsox", awayTeamId: "opp",
|
||||
stadiumId: "boston",
|
||||
dateTime: TestFixtures.date(year: 2026, month: 6, day: 7, hour: 19))
|
||||
|
||||
let prefs = TripPreferences(
|
||||
planningMode: .teamFirst,
|
||||
sports: [.mlb],
|
||||
numberOfDrivers: 2,
|
||||
selectedTeamIds: ["yankees", "redsox"]
|
||||
)
|
||||
|
||||
let request = PlanningRequest(
|
||||
preferences: prefs,
|
||||
availableGames: [pastGame1, pastGame2, futureGame1, futureGame2],
|
||||
teams: ["yankees": makeTeam(id: "yankees", name: "Yankees"),
|
||||
"redsox": makeTeam(id: "redsox", name: "Red Sox")],
|
||||
stadiums: ["nyc": nycStadium, "boston": bostonStadium]
|
||||
)
|
||||
|
||||
let planner = ScenarioEPlanner(currentDate: currentDate)
|
||||
let result = planner.plan(request: request)
|
||||
|
||||
guard case .success(let options) = result else {
|
||||
Issue.record("Expected .success, got \(result)")
|
||||
return
|
||||
}
|
||||
|
||||
let startOfDay = calendar.startOfDay(for: currentDate)
|
||||
for option in options {
|
||||
for stop in option.stops {
|
||||
#expect(stop.arrivalDate >= startOfDay,
|
||||
"Stop on \(stop.arrivalDate) is before currentDate \(startOfDay)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test("plan: results cover multiple months when games spread across season")
|
||||
func plan_resultsCoverMultipleMonths() {
|
||||
let currentDate = TestFixtures.date(year: 2026, month: 4, day: 1, hour: 12)
|
||||
let calendar = TestClock.calendar
|
||||
|
||||
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
|
||||
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
|
||||
|
||||
var games: [Game] = []
|
||||
for month in 4...9 {
|
||||
let dt1 = TestFixtures.date(year: 2026, month: month, day: 5, hour: 19)
|
||||
let dt2 = TestFixtures.date(year: 2026, month: month, day: 7, hour: 19)
|
||||
games.append(makeGame(id: "nyc-\(month)", homeTeamId: "yankees", awayTeamId: "opp",
|
||||
stadiumId: "nyc", dateTime: dt1))
|
||||
games.append(makeGame(id: "bos-\(month)", homeTeamId: "redsox", awayTeamId: "opp",
|
||||
stadiumId: "boston", dateTime: dt2))
|
||||
}
|
||||
|
||||
let prefs = TripPreferences(
|
||||
planningMode: .teamFirst,
|
||||
sports: [.mlb],
|
||||
numberOfDrivers: 2,
|
||||
selectedTeamIds: ["yankees", "redsox"]
|
||||
)
|
||||
|
||||
let request = PlanningRequest(
|
||||
preferences: prefs,
|
||||
availableGames: games,
|
||||
teams: ["yankees": makeTeam(id: "yankees", name: "Yankees"),
|
||||
"redsox": makeTeam(id: "redsox", name: "Red Sox")],
|
||||
stadiums: ["nyc": nycStadium, "boston": bostonStadium]
|
||||
)
|
||||
|
||||
let planner = ScenarioEPlanner(currentDate: currentDate)
|
||||
let result = planner.plan(request: request)
|
||||
|
||||
guard case .success(let options) = result else {
|
||||
Issue.record("Expected .success, got \(result)")
|
||||
return
|
||||
}
|
||||
|
||||
#expect(options.count >= 2, "Should have multiple options across season")
|
||||
let months = Set(options.flatMap { opt in
|
||||
opt.stops.map { calendar.component(.month, from: $0.arrivalDate) }
|
||||
})
|
||||
#expect(months.count >= 2,
|
||||
"Results should span multiple months, got: \(months.sorted())")
|
||||
}
|
||||
|
||||
@Test("plan: every option has all selected teams")
|
||||
func plan_everyOptionHasAllSelectedTeams_tighter() {
|
||||
let currentDate = TestClock.now
|
||||
|
||||
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
|
||||
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
|
||||
|
||||
var games: [Game] = []
|
||||
for day in stride(from: 1, through: 30, by: 3) {
|
||||
games.append(makeGame(id: "nyc-\(day)", homeTeamId: "yankees", awayTeamId: "opp",
|
||||
stadiumId: "nyc",
|
||||
dateTime: TestClock.addingDays(day)))
|
||||
games.append(makeGame(id: "bos-\(day)", homeTeamId: "redsox", awayTeamId: "opp",
|
||||
stadiumId: "boston",
|
||||
dateTime: TestClock.addingDays(day + 1)))
|
||||
}
|
||||
|
||||
let prefs = TripPreferences(
|
||||
planningMode: .teamFirst,
|
||||
sports: [.mlb],
|
||||
numberOfDrivers: 2,
|
||||
selectedTeamIds: ["yankees", "redsox"]
|
||||
)
|
||||
|
||||
let request = PlanningRequest(
|
||||
preferences: prefs,
|
||||
availableGames: games,
|
||||
teams: ["yankees": makeTeam(id: "yankees", name: "Yankees"),
|
||||
"redsox": makeTeam(id: "redsox", name: "Red Sox")],
|
||||
stadiums: ["nyc": nycStadium, "boston": bostonStadium]
|
||||
)
|
||||
|
||||
let gameMap = Dictionary(games.map { ($0.id, $0) }, uniquingKeysWith: { f, _ in f })
|
||||
let planner = ScenarioEPlanner(currentDate: currentDate)
|
||||
let result = planner.plan(request: request)
|
||||
|
||||
guard case .success(let options) = result else {
|
||||
Issue.record("Expected .success, got \(result)")
|
||||
return
|
||||
}
|
||||
|
||||
for (idx, option) in options.enumerated() {
|
||||
let homeTeams = Set(
|
||||
option.stops.flatMap { $0.games }
|
||||
.compactMap { gameMap[$0]?.homeTeamId }
|
||||
)
|
||||
#expect(homeTeams.contains("yankees"),
|
||||
"Option \(idx): missing Yankees home game")
|
||||
#expect(homeTeams.contains("redsox"),
|
||||
"Option \(idx): missing Red Sox home game")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helper Methods
|
||||
|
||||
private func makeStadium(
|
||||
|
||||
Reference in New Issue
Block a user