Files
Sportstime/docs/TEST_PLAN.md
Trey t 1bd248c255 test(planning): complete test suite with Phase 11 edge cases
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>
2026-01-11 01:14:40 -06:00

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 Game objects with configurable:
      • Count (5, 50, 500, 2000, 10000)
      • Date range
      • Geographic spread
      • Stadium/team distribution
    • Generate synthetic Stadium dictionary
    • Generate synthetic Trip with configurable stops
    • Deterministic seeding for reproducible tests
  • 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_KnownDistance NYC 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_ReturnsNil Distance requiring 3+ days of driving should return nil

  • 1.6 test_estimate_ValidTrip_ReturnsSegment Verify distanceMeters, durationSeconds, travelMode

  • 1.7 test_calculateTravelDays_SingleDayDrive 4 hours driving = 1 day

  • 1.8 test_calculateTravelDays_MultiDayDrive 20 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_ReturnsEmpty Anchors 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_OverridesWithWarning TDD Note: This test defines a new Trip.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_RespectsLimit Games > 5 days apart should not connect directly

  • 2.18 test_findRoutes_DSTTransition_HandlesCorrectly Spring forward / fall back edge case

  • 2.19 test_findRoutes_MidnightGame_AssignsToCorrectDay Game 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_HandlesSilently Verify no infinite loop, returns valid routes

2I: Beam Search Behavior

  • 2.25 test_findRoutes_LargeDataset_ScalesBeamWidth Verify effectiveBeamWidth kicks 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_CompletesWithin5Minutes Stress 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_NoMemoryLeak Run 100 iterations, verify allocation/deallocation balance

  • 3.10 test_findRoutes_LargeDataset_MemoryBounded 10K 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_ThrowsError TDD Note: Uses existing PlanningFailure.FailureReason.noGamesInRange

  • 4.5 test_planByDates_EndDateBeforeStartDate_ThrowsError

  • 4.6 test_planByDates_SingleGameInRange_ReturnsSingleGameRoute

  • 4.7 test_planByDates_MaxGamesInRange_HandlesGracefully 10K 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_ThrowsError Games 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 against BruteForceRouteVerifier

  • 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_IncludesNearbyGames Uses 50-mile radius

6B: Edge Cases

  • 6.4 test_departReturn_NoGamesAlongRoute_ThrowsError

  • 6.5 test_departReturn_InvalidCity_ThrowsError

  • 6.6 test_departReturn_ExtremeDistance_RespectsConstraints NYC to LA — verify driving constraints applied

6C: Must-Stop Locations

  • 6.7 test_departReturn_WithMustStopLocation_IncludesStop

  • 6.8 test_departReturn_MustStopNoNearbyGames_IncludesStopAnyway Stop 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_ScenariosAreMutuallyExclusive Cannot 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_WhenApplicable TDD Note: Validates Trip.warnings property exists

7C: Constraint Application

  • 7.8 test_engine_NumberOfDrivers_AffectsMaxDailyDriving More 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_ReturnsPlanned TDD Note: Trip.status: TripStatus property 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_CurrentlyUnsafe Document 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_HandlesGracefully Lat > 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_IncludesGame Game 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:

  1. Phase 0 — Infrastructure (no app changes)
  2. Phase 1 — TravelEstimator (foundation, likely passes)
  3. Phase 2 — GameDAGRouter core (highest risk, most edge cases)
  4. Phase 3 — GameDAGRouter scale (set baselines)
  5. Phase 4-6 — Scenario planners (A, B, C)
  6. Phase 7 — TripPlanningEngine integration
  7. Phase 8 — ItineraryBuilder
  8. Phase 9 — RouteFilters
  9. Phase 10 — Concurrency (documentation tests)
  10. 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)