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:
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user