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

@@ -198,14 +198,16 @@ struct FilterCascadeTests {
let engine = TripPlanningEngine()
let result = engine.planItineraries(request: request)
if case .failure(let failure) = result {
switch result {
case .success:
break // Success is also acceptable engine found a valid non-repeating route
case .failure(let failure):
// Should get either repeatCityViolation or noGamesInRange/noValidRoutes
let isExpectedFailure = failure.reason == .repeatCityViolation(cities: ["New York"])
|| failure.reason == .noValidRoutes
|| failure.reason == .noGamesInRange
#expect(isExpectedFailure, "Should get a clear failure reason, got: \(failure.message)")
}
// Success is also acceptable if engine handles it differently
}
@Test("Must-stop filter with impossible city → clear error")
@@ -235,11 +237,9 @@ struct FilterCascadeTests {
let engine = TripPlanningEngine()
let result = engine.planItineraries(request: request)
if case .failure(let failure) = result {
#expect(failure.violations.contains(where: { $0.type == .mustStop }),
"Should have mustStop violation")
}
// If no routes generated at all (noGamesInRange), that's also an acceptable failure
guard case .failure(let failure) = result else { Issue.record("Expected .failure, got \(result)"); return }
#expect(failure.violations.contains(where: { $0.type == .mustStop }),
"Should have mustStop violation")
}
@Test("Empty sports set produces warning")
@@ -297,11 +297,34 @@ struct FilterCascadeTests {
geographicRationale: "test"
)
// Case 1: No repeat cities filter is a no-op
let options = [option]
let once = RouteFilters.filterRepeatCities(options, allow: false)
let twice = RouteFilters.filterRepeatCities(once, allow: false)
#expect(once.count == twice.count, "Filtering twice should produce same result as once")
#expect(once.count == 1, "NYC→BOS has no repeat cities, should survive filter")
// Case 2: Route with repeat cities filter actually removes it
let stop3 = ItineraryStop(
city: "New York", state: "NY",
coordinate: TestFixtures.coordinates["New York"],
games: ["g3"],
arrivalDate: TestClock.addingDays(2),
departureDate: TestClock.addingDays(3),
location: LocationInput(name: "New York", coordinate: TestFixtures.coordinates["New York"]),
firstGameStart: TestClock.addingDays(2)
)
let seg2 = TestFixtures.travelSegment(from: "Boston", to: "New York")
let repeatOption = ItineraryOption(
rank: 2, stops: [stop1, stop2, stop3],
travelSegments: [segment, seg2],
totalDrivingHours: 7.0, totalDistanceMiles: 430,
geographicRationale: "test"
)
let mixedOnce = RouteFilters.filterRepeatCities([option, repeatOption], allow: false)
let mixedTwice = RouteFilters.filterRepeatCities(mixedOnce, allow: false)
#expect(mixedOnce.count == mixedTwice.count, "Double-filter should be idempotent")
#expect(mixedOnce.count == 1, "Route with repeat NYC should be filtered out")
}
}
@@ -396,13 +419,15 @@ struct ConstraintInteractionTests {
// Engine should handle this gracefully either find a route that visits NYC once
// or return a clear failure
if case .failure(let failure) = result {
switch result {
case .success:
break // Success is fine engine found a valid route visiting NYC exactly once
case .failure(let failure):
let hasReason = failure.reason == .repeatCityViolation(cities: ["New York"])
|| failure.reason == .noValidRoutes
|| failure.reason == .noGamesInRange
#expect(hasReason, "Should fail with a clear reason")
}
// Success is fine too if engine finds a single-NYC-day route
}
@Test("Multiple drivers extend feasible distance")
@@ -513,11 +538,23 @@ struct ConstraintInteractionTests {
let engine = TripPlanningEngine()
let result = engine.planItineraries(request: request)
// Whether success or failure, warnings should be accessible
// If options were filtered, we should see warnings
if result.isSuccess && !engine.warnings.isEmpty {
#expect(engine.warnings.allSatisfy { $0.severity == .warning },
// With must-stop NYC, some routes may be filtered. Verify:
// 1. The warnings property is accessible (doesn't crash)
// 2. If warnings exist, they are all severity .warning
let warnings = engine.warnings
for warning in warnings {
#expect(warning.severity == .warning,
"Exclusion notices should be warnings, not errors")
}
// The engine should produce either success with must-stop satisfied, or failure
switch result {
case .success(let options):
for option in options {
let cities = option.stops.map { $0.city.lowercased() }
#expect(cities.contains("new york"), "Must-stop NYC should be in every option")
}
case .failure:
break // Acceptable if no route can satisfy must-stop
}
}
}