Implement comprehensive test infrastructure and all 124 tests across 11 phases: - Phase 0: Test infrastructure (fixtures, mocks, helpers) - Phases 1-10: Core planning engine tests (previously implemented) - Phase 11: Edge case omnibus (11 new tests) - Data edge cases: nil stadiums, malformed dates, invalid coordinates - Boundary conditions: driving limits, radius boundaries - Time zone cases: cross-timezone games, DST transitions Reorganize test structure under Planning/ directory with proper organization. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
16 KiB
Test Suite Implementation Plan
Scope: TO-DOS items 20.0 - 20.10
Approach: TDD — tests define expected behavior, then app code is updated to pass
Framework: Swift Testing (@Test, #expect, @Suite) with XCTest fallback if needed
Status: Use checkboxes to track progress
Pre-Implementation Setup
Phase 0: Test Infrastructure
Set up foundations before writing any feature tests.
-
0.1 Create
SportsTimeTests/directory structure:SportsTimeTests/ ├── Fixtures/ │ └── FixtureGenerator.swift ├── Mocks/ │ ├── MockCloudKitService.swift │ ├── MockLocationService.swift │ └── MockAppDataProvider.swift ├── Helpers/ │ ├── BruteForceRouteVerifier.swift │ └── TestConstants.swift ├── Planning/ │ ├── GameDAGRouterTests.swift │ ├── ScenarioAPlannerTests.swift │ ├── ScenarioBPlannerTests.swift │ ├── ScenarioCPlannerTests.swift │ ├── TravelEstimatorTests.swift │ ├── ItineraryBuilderTests.swift │ └── TripPlanningEngineTests.swift └── Performance/ └── PlannerScaleTests.swift -
0.2 Implement
FixtureGenerator.swift:- Generate synthetic
Gameobjects with configurable:- Count (5, 50, 500, 2000, 10000)
- Date range
- Geographic spread
- Stadium/team distribution
- Generate synthetic
Stadiumdictionary - Generate synthetic
Tripwith configurable stops - Deterministic seeding for reproducible tests
- Generate synthetic
-
0.3 Implement
MockCloudKitService.swift:- Stub all public methods
- Return fixture data
- Configurable error injection
-
0.4 Implement
MockLocationService.swift:- Stub geocoding
- Stub routing (return pre-calculated distances)
- Configurable latency simulation
-
0.5 Implement
MockAppDataProvider.swift:- Return fixture stadiums/teams/games
- Configurable to simulate empty data
-
0.6 Implement
BruteForceRouteVerifier.swift:- For inputs with ≤8 stops, exhaustively enumerate all permutations
- Compare engine result against true optimal
- Used to validate "no obviously better route exists"
-
0.7 Implement
TestConstants.swift:enum TestConstants { static let nearbyRadiusMiles: Double = 50.0 static let performanceTimeout: TimeInterval = 300.0 // 5 min static let hangTimeout: TimeInterval = 30.0 // Baselines TBD after initial runs static var baseline500Games: TimeInterval = 0 static var baseline2000Games: TimeInterval = 0 static var baseline10000Games: TimeInterval = 0 }
Feature Test Phases
Phase 1: TravelEstimator Tests
Foundation — all planners depend on this.
File: TravelEstimatorTests.swift
-
1.1
test_haversineDistanceMiles_KnownDistanceNYC to LA = ~2,451 miles (verify within 1% tolerance) -
1.2
test_haversineDistanceMiles_SamePoint_ReturnsZero -
1.3
test_haversineDistanceMiles_Antipodal_ReturnsHalfEarthCircumference -
1.4
test_estimate_NilCoordinates_ReturnsNil -
1.5
test_estimate_ExceedsMaxDailyHours_ReturnsNilDistance requiring 3+ days of driving should return nil -
1.6
test_estimate_ValidTrip_ReturnsSegmentVerifydistanceMeters,durationSeconds,travelMode -
1.7
test_calculateTravelDays_SingleDayDrive4 hours driving = 1 day -
1.8
test_calculateTravelDays_MultiDayDrive20 hours driving = 3 days (ceil(20/8)) -
1.9
test_estimateFallbackDistance_SameCity_ReturnsZero -
1.10
test_estimateFallbackDistance_DifferentCity_Returns300
Phase 2: GameDAGRouter Tests — Core Logic
The "scary to touch" component — extensive edge case coverage.
File: GameDAGRouterTests.swift
2A: Empty & Single-Element Cases
-
2.1
test_findRoutes_EmptyGames_ReturnsEmptyArray -
2.2
test_findRoutes_SingleGame_ReturnsSingleRoute -
2.3
test_findRoutes_SingleGame_WithMatchingAnchor_ReturnsSingleRoute -
2.4
test_findRoutes_SingleGame_WithNonMatchingAnchor_ReturnsEmpty
2B: Two-Game Cases
-
2.5
test_findRoutes_TwoGames_FeasibleTransition_ReturnsBothInOrder -
2.6
test_findRoutes_TwoGames_InfeasibleTransition_ReturnsSeparateRoutes -
2.7
test_findRoutes_TwoGames_SameStadiumSameDay_Succeeds(Doubleheader scenario)
2C: Anchor Game Constraints
-
2.8
test_findRoutes_WithAnchors_OnlyReturnsRoutesContainingAllAnchors -
2.9
test_findRoutes_ImpossibleAnchors_ReturnsEmptyAnchors are geographically/temporally impossible to connect -
2.10
test_findRoutes_MultipleAnchors_RouteMustContainAll
2D: Repeat Cities Toggle
-
2.11
test_findRoutes_AllowRepeatCities_SameCityMultipleDays_Allowed -
2.12
test_findRoutes_DisallowRepeatCities_SkipsSecondVisit -
2.13
test_findRoutes_DisallowRepeatCities_OnlyOptionIsRepeat_OverridesWithWarningTDD Note: This test defines a newTrip.warnings: [PlanningWarning]property
2E: Driving Constraints
-
2.14
test_findRoutes_ExceedsMaxDailyDriving_TransitionRejected -
2.15
test_findRoutes_MultiDayDrive_Allowed_IfWithinDailyLimits -
2.16
test_findRoutes_SameDayDifferentStadiums_ChecksAvailableTime
2F: Calendar Day Logic
-
2.17
test_findRoutes_MaxDayLookahead_RespectsLimitGames > 5 days apart should not connect directly -
2.18
test_findRoutes_DSTTransition_HandlesCorrectlySpring forward / fall back edge case -
2.19
test_findRoutes_MidnightGame_AssignsToCorrectDayGame at 12:05 AM belongs to new day
2G: Diversity Selection
-
2.20
test_selectDiverseRoutes_ShortAndLongTrips_BothRepresented -
2.21
test_selectDiverseRoutes_HighAndLowMileage_BothRepresented -
2.22
test_selectDiverseRoutes_FewAndManyCities_BothRepresented -
2.23
test_selectDiverseRoutes_DuplicateRoutes_Deduplicated
2H: Cycle Handling
- 2.24
test_findRoutes_GraphWithPotentialCycle_HandlesSilentlyVerify no infinite loop, returns valid routes
2I: Beam Search Behavior
-
2.25
test_findRoutes_LargeDataset_ScalesBeamWidthVerifyeffectiveBeamWidthkicks in for 5000+ games -
2.26
test_findRoutes_EarlyTermination_TriggersWhenBeamFull
Phase 3: GameDAGRouter Tests — Scale & Performance
Stress tests for 10K+ objects. Periodic/manual execution.
File: GameDAGRouterTests.swift (continued) or separate GameDAGRouterScaleTests.swift
3A: Scale Tests
-
3.1
test_findRoutes_5Games_CompletesWithin5Minutes -
3.2
test_findRoutes_50Games_CompletesWithin5Minutes -
3.3
test_findRoutes_500Games_CompletesWithin5Minutes -
3.4
test_findRoutes_2000Games_CompletesWithin5Minutes -
3.5
test_findRoutes_10000Games_CompletesWithin5Minutes -
3.6
test_findRoutes_50000Nodes_CompletesWithin5MinutesStress test — may need timeout adjustment
3B: Performance Baselines
-
3.7 Record baseline times for 500/2000/10000 games (first run)
-
3.8 After baselines established, add regression assertions
3C: Memory Tests
-
3.9
test_findRoutes_RepeatedCalls_NoMemoryLeakRun 100 iterations, verify allocation/deallocation balance -
3.10
test_findRoutes_LargeDataset_MemoryBounded10K games should not exceed reasonable memory
Phase 4: ScenarioAPlanner Tests (Plan by Dates)
User provides dates, planner finds games.
File: ScenarioAPlannerTests.swift
4A: Valid Inputs
-
4.1
test_planByDates_ValidDateRange_ReturnsGamesInRange -
4.2
test_planByDates_SingleDayRange_ReturnsGamesOnThatDay -
4.3
test_planByDates_MultiWeekRange_ReturnsMultipleGames
4B: Edge Cases
-
4.4
test_planByDates_NoGamesInRange_ThrowsErrorTDD Note: Uses existingPlanningFailure.FailureReason.noGamesInRange -
4.5
test_planByDates_EndDateBeforeStartDate_ThrowsError -
4.6
test_planByDates_SingleGameInRange_ReturnsSingleGameRoute -
4.7
test_planByDates_MaxGamesInRange_HandlesGracefully10K games in range — verify no crash/hang
4C: Integration with DAG
-
4.8
test_planByDates_UsesDAGRouterForRouting -
4.9
test_planByDates_RespectsDriverConstraints
Phase 5: ScenarioBPlanner Tests (Must-See Games)
User selects specific games, planner builds route.
File: ScenarioBPlannerTests.swift
5A: Valid Inputs
-
5.1
test_mustSeeGames_SingleGame_ReturnsTripWithThatGame -
5.2
test_mustSeeGames_MultipleGames_ReturnsOptimalRoute -
5.3
test_mustSeeGames_GamesInDifferentCities_ConnectsThem
5B: Edge Cases
-
5.4
test_mustSeeGames_EmptySelection_ThrowsError -
5.5
test_mustSeeGames_ImpossibleToConnect_ThrowsErrorGames on same day in cities 850+ miles apart (same region) -
5.6
test_mustSeeGames_MaxGamesSelected_HandlesGracefully
5C: Optimality Verification
-
5.7
test_mustSeeGames_SmallInput_MatchesBruteForceOptimal≤8 games — verify againstBruteForceRouteVerifier -
5.8
test_mustSeeGames_LargeInput_NoObviouslyBetterRoute
Phase 6: ScenarioCPlanner Tests (Depart/Return Cities)
User specifies origin and destination.
File: ScenarioCPlannerTests.swift
6A: Valid Inputs
-
6.1
test_departReturn_SameCity_ReturnsRoundTrip -
6.2
test_departReturn_DifferentCities_ReturnsOneWayRoute -
6.3
test_departReturn_GamesAlongCorridor_IncludesNearbyGamesUses 50-mile radius
6B: Edge Cases
-
6.4
test_departReturn_NoGamesAlongRoute_ThrowsError -
6.5
test_departReturn_InvalidCity_ThrowsError -
6.6
test_departReturn_ExtremeDistance_RespectsConstraintsNYC to LA — verify driving constraints applied
6C: Must-Stop Locations
-
6.7
test_departReturn_WithMustStopLocation_IncludesStop -
6.8
test_departReturn_MustStopNoNearbyGames_IncludesStopAnywayStop is included but may not have games -
6.9
test_departReturn_MultipleMustStops_AllIncluded -
6.10
test_departReturn_MustStopConflictsWithRoute_FindsCompromise
Phase 7: TripPlanningEngine Integration Tests
Main orchestrator — tests all scenarios together.
File: TripPlanningEngineTests.swift
7A: Scenario Routing
-
7.1
test_engine_ScenarioA_DelegatesCorrectly -
7.2
test_engine_ScenarioB_DelegatesCorrectly -
7.3
test_engine_ScenarioC_DelegatesCorrectly -
7.4
test_engine_ScenariosAreMutuallyExclusiveCannot mix scenarios in single request
7B: Result Structure
-
7.5
test_engine_Result_ContainsTravelSegments -
7.6
test_engine_Result_ContainsItineraryDays -
7.7
test_engine_Result_IncludesWarnings_WhenApplicableTDD Note: ValidatesTrip.warningsproperty exists
7C: Constraint Application
-
7.8
test_engine_NumberOfDrivers_AffectsMaxDailyDrivingMore drivers = can drive more per day -
7.9
test_engine_MaxDrivingPerDay_Respected -
7.10
test_engine_AllowRepeatCities_PropagatedToDAG
7D: Error Handling
-
7.11
test_engine_ImpossibleConstraints_ReturnsNoResult -
7.12
test_engine_EmptyInput_ThrowsError
Phase 8: ItineraryBuilder Tests
Builds day-by-day itinerary from route.
File: ItineraryBuilderTests.swift
-
8.1
test_builder_SingleGame_CreatesSingleDay -
8.2
test_builder_MultiCity_CreatesTravelSegmentsBetween -
8.3
test_builder_SameCity_MultipleGames_GroupsOnSameDay -
8.4
test_builder_TravelDays_InsertedWhenDrivingExceeds8Hours -
8.5
test_builder_ArrivalTimeBeforeGame_Calculated -
8.6
test_builder_EmptyRoute_ReturnsEmptyItinerary
Phase 9: RouteFilters Tests
Filtering on All Trips list.
File: RouteFiltersTests.swift
9A: Single Filters
-
9.1
test_filterBySport_SingleSport_ReturnsMatching -
9.2
test_filterBySport_MultipleSports_ReturnsUnion -
9.3
test_filterBySport_AllSports_ReturnsAll -
9.4
test_filterByDateRange_ReturnsTripsInRange(+ bonus:test_filterByDateRange_IncludesOverlappingTrips) -
9.5
test_filterByStatus_Planned_ReturnsPlannedTDD Note:Trip.status: TripStatusproperty added -
9.6
test_filterByStatus_InProgress_ReturnsInProgress -
9.7
test_filterByStatus_Completed_ReturnsCompleted
9B: Combined Filters
-
9.8
test_combinedFilters_SportAndDate_ReturnsIntersection -
9.9
test_combinedFilters_AllFilters_ReturnsIntersection
9C: Edge Cases
-
9.10
test_filter_NoMatches_ReturnsEmptyArray -
9.11
test_filter_AllMatch_ReturnsAll -
9.12
test_filter_EmptyInput_ReturnsEmptyArray
Phase 10: Concurrency Tests
Prove current implementation is NOT thread-safe (for future work).
File: ConcurrencyTests.swift
-
10.1
test_engine_ConcurrentRequests_CurrentlyUnsafeDocument existing behavior (may crash/race) -
10.2
test_engine_SequentialRequests_Succeeds
Future (out of scope for now):
test_engine_ConcurrentRequests_ThreadSafe(after refactor)
Phase 11: Edge Case Omnibus
Catch-all for extreme/unusual inputs.
File: EdgeCaseTests.swift
11A: Data Edge Cases
-
11.1
test_nilStadium_HandlesGracefully -
11.2
test_malformedDate_HandlesGracefully -
11.3
test_invalidCoordinates_HandlesGracefullyLat > 90, Lon > 180 -
11.4
test_missingRequiredFields_HandlesGracefully
11B: Boundary Conditions
-
11.5
test_exactlyAtDrivingLimit_Succeeds -
11.6
test_oneMileOverLimit_Fails -
11.7
test_exactlyAtRadiusBoundary_IncludesGameGame at exactly 50 miles -
11.8
test_oneFootOverRadius_ExcludesGame
11C: Time Zone Cases
-
11.9
test_gameInDifferentTimeZone_NormalizesToUTC -
11.10
test_dstSpringForward_HandlesCorrectly -
11.11
test_dstFallBack_HandlesCorrectly
API Proposals (TDD Discoveries)
These APIs will be defined by tests and need implementation:
| Property/Type | Defined In | Description |
|---|---|---|
Trip.warnings: [PlanningWarning] |
2.13 | Warnings when planner overrides preferences |
PlanningWarning enum |
2.13 | .repeatCityOverridden(city: String), etc. |
PlanningError.noGamesFound |
4.4 | Thrown when date range has no games |
Trip.status: TripStatus |
9.5 | .planned, .inProgress, .completed |
Execution Order
Recommended batch order for TDD cycle:
- Phase 0 — Infrastructure (no app changes)
- Phase 1 — TravelEstimator (foundation, likely passes)
- Phase 2 — GameDAGRouter core (highest risk, most edge cases)
- Phase 3 — GameDAGRouter scale (set baselines)
- Phase 4-6 — Scenario planners (A, B, C)
- Phase 7 — TripPlanningEngine integration
- Phase 8 — ItineraryBuilder
- Phase 9 — RouteFilters
- Phase 10 — Concurrency (documentation tests)
- Phase 11 — Edge cases
Progress Tracking
| Phase | Tests | Passing | Status |
|---|---|---|---|
| 0 | 7 | 7 | Complete |
| 1 | 10 | 10 | Complete |
| 2 | 26 | 26 | Complete |
| 3 | 10 | 10 | Complete |
| 4 | 9 | 9 | Complete |
| 5 | 8 | 8 | Complete |
| 6 | 10 | 10 | Complete |
| 7 | 12 | 12 | Complete |
| 8 | 6 | 6 | Complete |
| 9 | 13 | 13 | Complete |
| 10 | 2 | 2 | Complete |
| 11 | 11 | 11 | Complete |
| Total | 124 | 124 |
Notes
- Hang timeout: 30 seconds (tests marked as hanging if exceeded)
- Performance timeout: 5 minutes per test (scale tests)
- Nearby radius: 50 miles driving distance (
TestConstants.nearbyRadiusMiles) - Brute force threshold: ≤8 stops for exact optimality verification
- Baselines: Will be recorded after initial runs, then hardcoded
Last updated: 2026-01-11 (Phase 11 complete — All tests passing)