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