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>
This commit is contained in:
Trey t
2026-01-16 14:07:41 -06:00
parent 035dd6f5de
commit 8162b4a029
102 changed files with 13409 additions and 9883 deletions

View File

@@ -8,7 +8,12 @@
import Foundation
/// Protocol that all scenario planners must implement.
/// Each scenario (A, B, C) has its own isolated implementation.
/// Each scenario (A, B, C, D) has its own isolated implementation.
///
/// - Invariants:
/// - Always returns either success or explicit failure, never throws
/// - Success contains ranked itinerary options
/// - Failure contains reason and any constraint violations
protocol ScenarioPlanner {
/// Plan itineraries for this scenario.
@@ -17,10 +22,24 @@ protocol ScenarioPlanner {
func plan(request: PlanningRequest) -> ItineraryResult
}
/// Factory for creating the appropriate scenario planner
/// Factory for creating the appropriate scenario planner.
///
/// - Expected Behavior:
/// - followTeamId != nil ScenarioDPlanner
/// - selectedGames not empty ScenarioBPlanner
/// - startLocation AND endLocation != nil ScenarioCPlanner
/// - Otherwise ScenarioAPlanner (default)
///
/// Priority order: D > B > C > A (first matching wins)
enum ScenarioPlannerFactory {
/// Creates the appropriate planner based on the request inputs
/// Creates the appropriate planner based on the request inputs.
///
/// - Expected Behavior:
/// - followTeamId set ScenarioDPlanner
/// - selectedGames not empty ScenarioBPlanner
/// - Both start and end locations ScenarioCPlanner
/// - Otherwise ScenarioAPlanner
static func planner(for request: PlanningRequest) -> ScenarioPlanner {
print("🔍 ScenarioPlannerFactory: Selecting planner...")
print(" - followTeamId: \(request.preferences.followTeamId ?? "nil")")
@@ -51,7 +70,13 @@ enum ScenarioPlannerFactory {
return ScenarioAPlanner()
}
/// Classifies which scenario applies to this request
/// Classifies which scenario applies to this request.
///
/// - Expected Behavior:
/// - followTeamId set .scenarioD
/// - selectedGames not empty .scenarioB
/// - Both start and end locations .scenarioC
/// - Otherwise .scenarioA
static func classify(_ request: PlanningRequest) -> PlanningScenario {
if request.preferences.followTeamId != nil {
return .scenarioD