diff --git a/docs/plans/2026-01-16-tdd-test-rewrite-design.md b/docs/plans/2026-01-16-tdd-test-rewrite-design.md new file mode 100644 index 0000000..4c686a1 --- /dev/null +++ b/docs/plans/2026-01-16-tdd-test-rewrite-design.md @@ -0,0 +1,170 @@ +# TDD Test Rewrite Design + +## Goal + +Delete all existing tests and rewrite using true TDD methodology (specification-first + property-based) to surface logic bugs in the implementation. Document expected behavior for every tested function. + +## Methodology + +### Specification-First + Property-Based TDD + +- **Specification-First**: Define expected behavior from requirements/intent, not current implementation +- **Property-Based**: Define invariants that must always hold regardless of input +- **Document-First**: Add input/output comments to source functions before writing tests + +### The Process + +1. **Read** - Examine function, callers, and context to understand intent +2. **Document** - Add `/// - Expected:` comment block to source file +3. **Test** - Write tests that verify documented expectations +4. **Batch Run** - Run all tests at the end +5. **Fix** - Failures indicate logic bugs; fix implementation to match expectations + +## Documentation Standard + +Every tested function gets this comment format: + +```swift +/// Calculates optimal routes between game locations. +/// +/// - Parameter games: List of games to route between (must be chronologically sorted) +/// - Parameter maxDailyMiles: Maximum driving distance per day (default: 500) +/// - Returns: Array of valid routes, each containing sequential games +/// +/// - Expected Behavior: +/// - Empty input → returns empty array +/// - Single game → returns array with one single-game route +/// - Games on same day in distant cities → excludes impossible transitions +/// - Routes respect maxDailyMiles constraint +/// - Routes are chronologically ordered +/// +/// - Invariants: +/// - All returned routes have games in chronological order +/// - No route exceeds maxDailyMiles between consecutive same-day games +func findRoutes(games: [Game], maxDailyMiles: Int = 500) -> [[Game]] +``` + +## Test Structure + +Each test file has three sections: + +```swift +@Suite("ComponentName") +struct ComponentNameTests { + + // MARK: - Specification Tests + // Tests derived from "Expected Behavior" comments + + // MARK: - Property Tests + // Tests that verify invariants for any input + + // MARK: - Edge Case Tests + // Boundary conditions and unusual inputs +} +``` + +### Property-Based Patterns + +| Pattern | Example | Applies To | +|---------|---------|------------| +| Idempotency | `filter(filter(x)) == filter(x)` | RouteFilters | +| Roundtrip | `decode(encode(x)) == x` | All Codable models | +| Ordering | Routes always chronological | GameDAGRouter | +| Bounds | Distance ≤ maxDailyMiles | TravelEstimator | +| Conservation | Total games in = total games out | ItineraryBuilder | + +## Execution Phases + +### Phase 1: Delete & Infrastructure (~15 min) +- Delete all 53 existing test files +- Create `TestFixtures.swift` with factories +- Create `MockServices.swift` with test doubles + +### Phase 2: Planning Engine (~2-3 hours) +Document + test in dependency order: +1. `TravelEstimator` (no dependencies) +2. `RouteFilters` (no dependencies) +3. `PlanningModels` (data structures) +4. `GameDAGRouter` (uses TravelEstimator) +5. `ItineraryBuilder` (uses filters) +6. `ScenarioPlanner` + A/B/C/D variants +7. `TripPlanningEngine` (orchestrator) + +### Phase 3: Domain Models (~1.5-2 hours) +15 files: Game, Trip, Stadium, Team, Sport, Region, Division, TripStop, TravelSegment, TripPreferences, Progress, DynamicSport, TripPoll, AnySport, AchievementDefinitions + +Focus: Codable roundtrips, computed properties, classification logic + +### Phase 4: Services (~2-3 hours) +23 files covering: +- Error enums (CloudKitError, SyncError, etc.) +- Result types (SyncResult, ScoreResolutionResult) +- Configuration states (unconfigured, invalid) +- Rate limiting, caching, network states + +### Phase 5: Export (~30 min) +2 files: ShareableContent types, POISearchService types + +### Phase 6: Run & Fix (~1-2 hours) +- Run full test suite +- Failures = logic bugs in implementation +- Fix source code to match documented expectations +- If expectation was wrong, discuss and adjust + +## Bug Handling + +| Situation | Action | +|-----------|--------| +| Implementation bug | Fix source code to match documented expectation | +| Expectation wrong | Discuss, update documentation, adjust test | + +Failures are treated as **bugs to fix**, not tests to adjust. + +## Success Criteria + +1. Every public function has documented expectations in source +2. Tests verify expectations, not current implementation +3. Property invariants hold for all inputs +4. No "tests written to pass" - tests define correct behavior independently +5. All tests pass after fixing surfaced bugs + +## Files to Test + +### Planning (11 files) +- TripPlanningEngine.swift +- GameDAGRouter.swift +- ItineraryBuilder.swift +- RouteFilters.swift +- TravelEstimator.swift +- ScenarioPlanner.swift +- ScenarioAPlanner.swift +- ScenarioBPlanner.swift +- ScenarioCPlanner.swift +- ScenarioDPlanner.swift +- PlanningModels.swift + +### Domain (15 files) +- Game.swift, Stadium.swift, Team.swift, Trip.swift +- TripStop.swift, TravelSegment.swift, TripPreferences.swift +- Sport.swift, Region.swift, Division.swift +- Progress.swift, DynamicSport.swift, TripPoll.swift +- AnySport.swift, AchievementDefinitions.swift + +### Services (23 files) +- CloudKitService.swift, DataProvider.swift, BootstrapService.swift +- CanonicalSyncService.swift, BackgroundSyncManager.swift +- LocationService.swift, LocationPermissionManager.swift +- AchievementEngine.swift, EVChargingService.swift +- GameMatcher.swift, StadiumProximityMatcher.swift +- FreeScoreAPI.swift, HistoricalGameScraper.swift +- NetworkMonitor.swift, RateLimiter.swift +- PollService.swift, DeepLinkHandler.swift +- RouteDescriptionGenerator.swift, SuggestedTripsGenerator.swift +- SyncCancellationToken.swift, VisitPhotoService.swift +- PhotoMetadataExtractor.swift, StadiumIdentityService.swift + +### Export (2 files) +- ShareableContent.swift +- POISearchService.swift + +**Total: ~52 source files, estimated 8-10 hours**