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:
Trey T
2026-04-04 13:38:41 -05:00
parent 188076717b
commit 9b622f8bbb
13 changed files with 2174 additions and 446 deletions

View File

@@ -131,13 +131,14 @@ struct ScenarioAPlannerTests {
// Should succeed with only NYC game (East coast)
guard case .success(let options) = result else {
// May fail for other reasons (no valid routes), but shouldn't include LA
Issue.record("Expected .success, got \(result)")
return
}
// If success, verify only East coast games included
// Verify only East coast games included
let allGameIds = options.flatMap { $0.stops.flatMap { $0.games } }
#expect(!allGameIds.contains("game-la"), "LA game should be filtered out by East region filter")
#expect(allGameIds.contains("game-nyc"), "NYC game should be included in East region filter")
}
// MARK: - Specification Tests: Must-Stop Filtering
@@ -174,13 +175,16 @@ struct ScenarioAPlannerTests {
let result = planner.plan(request: request)
// If success, should only include NYC games
if case .success(let options) = result {
let allGameIds = options.flatMap { $0.stops.flatMap { $0.games } }
#expect(allGameIds.contains("game-nyc"), "NYC game should be included")
// Boston game may or may not be included depending on route logic
// Should include NYC games
guard case .success(let options) = result else {
Issue.record("Expected .success, got \(result)")
return
}
for option in options {
let gameIds = Set(option.stops.flatMap { $0.games })
#expect(gameIds.contains("game-nyc"), "Every option must include NYC game (must-stop constraint)")
}
// Could also fail with noGamesInRange if must-stop filter is strict
}
@Test("plan: mustStopLocation with no games in that city returns noGamesInRange")
@@ -346,6 +350,7 @@ struct ScenarioAPlannerTests {
// Should have 1 stop with 2 games (not 2 stops)
let totalGamesInNYC = nycStops.flatMap { $0.games }.count
#expect(totalGamesInNYC >= 2, "Both games should be in the route")
#expect(nycStops.count == 1, "Two games at same stadium should create exactly one stop, got \(nycStops.count)")
}
}
@@ -379,11 +384,14 @@ struct ScenarioAPlannerTests {
let result = planner.plan(request: request)
if case .success(let options) = result {
let allGameIds = options.flatMap { $0.stops.flatMap { $0.games } }
#expect(allGameIds.contains("in-range"))
#expect(!allGameIds.contains("out-of-range"), "Game outside date range should not be included")
guard case .success(let options) = result else {
Issue.record("Expected .success, got \(result)")
return
}
let allGameIds = options.flatMap { $0.stops.flatMap { $0.games } }
#expect(allGameIds.contains("in-range"))
#expect(!allGameIds.contains("out-of-range"), "Game outside date range should not be included")
}
@Test("Invariant: A-B-A creates 3 stops not 2")
@@ -419,17 +427,19 @@ struct ScenarioAPlannerTests {
let result = planner.plan(request: request)
if case .success(let options) = result {
// Look for an option that includes all 3 games
let optionWithAllGames = options.first { option in
let allGames = option.stops.flatMap { $0.games }
return allGames.contains("nyc1") && allGames.contains("boston1") && allGames.contains("nyc2")
}
guard case .success(let options) = result else {
Issue.record("Expected .success, got \(result)")
return
}
if let option = optionWithAllGames {
// NYC appears first and last, so should have at least 3 stops
#expect(option.stops.count >= 3, "A-B-A pattern should create 3 stops")
}
// Look for an option that includes all 3 games
let optionWithAllGames = options.first { option in
let ids = Set(option.stops.flatMap { $0.games })
return ids.contains("nyc1") && ids.contains("boston1") && ids.contains("nyc2")
}
#expect(optionWithAllGames != nil, "Should have at least one route containing all 3 games")
if let option = optionWithAllGames {
#expect(option.stops.count >= 3, "NYC-BOS-NYC pattern should create at least 3 stops")
}
}
@@ -462,11 +472,54 @@ struct ScenarioAPlannerTests {
let result = planner.plan(request: request)
if case .success(let options) = result {
#expect(!options.isEmpty, "Success must have at least one option")
for option in options {
#expect(!option.stops.isEmpty, "Each option must have at least one stop")
}
guard case .success(let options) = result else {
Issue.record("Expected .success, got \(result)")
return
}
#expect(!options.isEmpty, "Success must have at least one option")
for option in options {
#expect(!option.stops.isEmpty, "Each option must have at least one stop")
}
}
// MARK: - Output Sanity
@Test("plan: game with missing stadium excluded, no crash")
func plan_missingStadiumForGame_gameExcluded() {
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let validGame = makeGame(id: "valid", stadiumId: "nyc", dateTime: TestClock.addingDays(2))
let orphanGame = Game(id: "orphan", homeTeamId: "t2", awayTeamId: "vis",
stadiumId: "no_such_stadium", dateTime: TestClock.addingDays(3),
sport: .mlb, season: "2026", isPlayoff: false)
let prefs = TripPreferences(
planningMode: .dateRange,
sports: [.mlb],
startDate: TestClock.addingDays(1),
endDate: TestClock.addingDays(7),
numberOfDrivers: 2
)
let request = PlanningRequest(
preferences: prefs,
availableGames: [validGame, orphanGame],
teams: [:],
stadiums: ["nyc": nycStadium]
)
let result = planner.plan(request: request)
// Primary assertion: no crash
guard case .success(let options) = result else {
Issue.record("Expected .success, got \(result)")
return
}
for option in options {
let ids = Set(option.stops.flatMap { $0.games })
#expect(!ids.contains("orphan"),
"Game with missing stadium should not appear in output")
}
}