Files
Sportstime/SportsTimeTests/Planning/TripPlanningEngineTests.swift
Trey t 8162b4a029 refactor(tests): TDD rewrite of all unit tests with spec documentation
Complete rewrite of unit test suite using TDD methodology:

Planning Engine Tests:
- GameDAGRouterTests: Beam search, anchor games, transitions
- ItineraryBuilderTests: Stop connection, validators, EV enrichment
- RouteFiltersTests: Region, time window, scoring filters
- ScenarioA/B/C/D PlannerTests: All planning scenarios
- TravelEstimatorTests: Distance, duration, travel days
- TripPlanningEngineTests: Orchestration, caching, preferences

Domain Model Tests:
- AchievementDefinitionsTests, AnySportTests, DivisionTests
- GameTests, ProgressTests, RegionTests, StadiumTests
- TeamTests, TravelSegmentTests, TripTests, TripPollTests
- TripPreferencesTests, TripStopTests, SportTests

Service Tests:
- FreeScoreAPITests, RouteDescriptionGeneratorTests
- SuggestedTripsGeneratorTests

Export Tests:
- ShareableContentTests (card types, themes, dimensions)

Bug fixes discovered through TDD:
- ShareCardDimensions: mapSnapshotSize exceeded available width (960x480)
- ScenarioBPlanner: Added anchor game validation filter

All tests include:
- Specification tests (expected behavior)
- Invariant tests (properties that must always hold)
- Edge case tests (boundary conditions)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 14:07:41 -06:00

184 lines
5.8 KiB
Swift

//
// TripPlanningEngineTests.swift
// SportsTimeTests
//
// TDD specification tests for TripPlanningEngine.
//
import Testing
import Foundation
import CoreLocation
@testable import SportsTime
@Suite("TripPlanningEngine")
struct TripPlanningEngineTests {
// MARK: - Test Data
private let nycCoord = CLLocationCoordinate2D(latitude: 40.7580, longitude: -73.9855)
private let bostonCoord = CLLocationCoordinate2D(latitude: 42.3467, longitude: -71.0972)
private let chicagoCoord = CLLocationCoordinate2D(latitude: 41.8827, longitude: -87.6233)
private let laCoord = CLLocationCoordinate2D(latitude: 34.0430, longitude: -118.2673)
// MARK: - Specification Tests: Planning Mode Selection
@Test("planningMode: dateRange is valid mode")
func planningMode_dateRange() {
let prefs = TripPreferences(
planningMode: .dateRange,
sports: [.mlb]
)
#expect(prefs.planningMode == .dateRange)
}
@Test("planningMode: gameFirst is valid mode")
func planningMode_gameFirst() {
let prefs = TripPreferences(
planningMode: .gameFirst,
sports: [.mlb],
mustSeeGameIds: ["game1"]
)
#expect(prefs.planningMode == .gameFirst)
}
@Test("planningMode: followTeam is valid mode")
func planningMode_followTeam() {
let prefs = TripPreferences(
planningMode: .followTeam,
sports: [.mlb],
followTeamId: "yankees"
)
#expect(prefs.planningMode == .followTeam)
}
@Test("planningMode: locations is valid mode")
func planningMode_locations() {
let prefs = TripPreferences(
planningMode: .locations,
startLocation: LocationInput(name: "Chicago", coordinate: chicagoCoord),
endLocation: LocationInput(name: "New York", coordinate: nycCoord),
sports: [.mlb]
)
#expect(prefs.planningMode == .locations)
}
// MARK: - Specification Tests: Driving Constraints
@Test("DrivingConstraints: calculates maxDailyDrivingHours correctly")
func drivingConstraints_maxDailyHours() {
let constraints = DrivingConstraints(numberOfDrivers: 2, maxHoursPerDriverPerDay: 6.0)
#expect(constraints.maxDailyDrivingHours == 12.0)
}
@Test("DrivingConstraints: clamps negative drivers to 1")
func drivingConstraints_clampsNegativeDrivers() {
let constraints = DrivingConstraints(numberOfDrivers: -5, maxHoursPerDriverPerDay: 8.0)
#expect(constraints.numberOfDrivers == 1)
#expect(constraints.maxDailyDrivingHours >= 1.0)
}
@Test("DrivingConstraints: clamps zero hours to minimum")
func drivingConstraints_clampsZeroHours() {
let constraints = DrivingConstraints(numberOfDrivers: 1, maxHoursPerDriverPerDay: 0)
#expect(constraints.maxHoursPerDriverPerDay == 1.0)
}
// MARK: - Specification Tests: Trip Preferences Computed Properties
@Test("totalDriverHoursPerDay: defaults to 8 hours when nil")
func totalDriverHoursPerDay_default() {
let prefs = TripPreferences(
numberOfDrivers: 1,
maxDrivingHoursPerDriver: nil
)
#expect(prefs.totalDriverHoursPerDay == 8.0)
}
@Test("totalDriverHoursPerDay: multiplies by number of drivers")
func totalDriverHoursPerDay_multipleDrivers() {
let prefs = TripPreferences(
numberOfDrivers: 2,
maxDrivingHoursPerDriver: 6.0
)
#expect(prefs.totalDriverHoursPerDay == 12.0)
}
@Test("effectiveTripDuration: uses explicit tripDuration when set")
func effectiveTripDuration_explicit() {
let prefs = TripPreferences(
tripDuration: 5
)
#expect(prefs.effectiveTripDuration == 5)
}
@Test("effectiveTripDuration: calculates from date range when tripDuration is nil")
func effectiveTripDuration_calculated() {
let calendar = Calendar.current
let startDate = calendar.date(from: DateComponents(year: 2026, month: 6, day: 15))!
let endDate = calendar.date(from: DateComponents(year: 2026, month: 6, day: 22))!
let prefs = TripPreferences(
startDate: startDate,
endDate: endDate,
tripDuration: nil
)
#expect(prefs.effectiveTripDuration == 7)
}
// MARK: - Invariant Tests
@Test("Invariant: totalDriverHoursPerDay > 0")
func invariant_totalDriverHoursPositive() {
let prefs1 = TripPreferences(numberOfDrivers: 1)
#expect(prefs1.totalDriverHoursPerDay > 0)
let prefs2 = TripPreferences(numberOfDrivers: 3, maxDrivingHoursPerDriver: 4)
#expect(prefs2.totalDriverHoursPerDay > 0)
}
@Test("Invariant: effectiveTripDuration >= 1")
func invariant_effectiveTripDurationMinimum() {
let testCases: [Int?] = [nil, 1, 5, 10]
for duration in testCases {
let prefs = TripPreferences(tripDuration: duration)
#expect(prefs.effectiveTripDuration >= 1)
}
}
// MARK: - Helper Methods
private func makeStadium(
id: String,
city: String,
coordinate: CLLocationCoordinate2D
) -> Stadium {
Stadium(
id: id,
name: "\(city) Stadium",
city: city,
state: "XX",
latitude: coordinate.latitude,
longitude: coordinate.longitude,
capacity: 40000,
sport: .mlb
)
}
private func makeGame(
id: String,
stadiumId: String,
dateTime: Date
) -> Game {
Game(
id: id,
homeTeamId: "team1",
awayTeamId: "team2",
stadiumId: stadiumId,
dateTime: dateTime,
sport: .mlb,
season: "2026"
)
}
}