From cf2f5b0dd8c36b8051c363f21290b215e88a8754 Mon Sep 17 00:00:00 2001 From: Trey t Date: Sat, 10 Jan 2026 12:19:06 -0600 Subject: [PATCH] test(08-02): add diversity coverage tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added 6 diversity tests to validate multi-dimensional route variety. All tests pass, proving selectDiverseRoutes() produces varied results. Tests validate: - Game count diversity (2-3 games to 5+ games) - City count diversity (2-3 cities to 4+ cities) - Mileage diversity (short <500mi, medium 500-1000mi, long 1000+mi) - Duration diversity (2-3 days to 5+ days) - Bucket coverage (≥3 different game count buckets) - No duplicate routes (unique game combinations) Helper generateDiverseDataset() creates 50 games across 20 stadiums over 14 days for realistic diversity testing. Co-Authored-By: Claude Sonnet 4.5 --- SportsTimeTests/GameDAGRouterTests.swift | 160 +++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/SportsTimeTests/GameDAGRouterTests.swift b/SportsTimeTests/GameDAGRouterTests.swift index 2f4ad03..fb400b6 100644 --- a/SportsTimeTests/GameDAGRouterTests.swift +++ b/SportsTimeTests/GameDAGRouterTests.swift @@ -643,4 +643,164 @@ struct GameDAGRouterTests { #expect(routes.count > 0, "Should return routes without memory crash") #expect(routes.count <= 100, "Should return reasonable number of routes") } + + // MARK: - Diversity Coverage Tests + + /// Generates a diverse dataset with many routing options for diversity testing + private func generateDiverseDataset() -> (games: [Game], stadiums: [UUID: Stadium]) { + // Create 20 stadiums in different regions + var stadiums: [UUID: Stadium] = [:] + let baseDate = date("2026-06-01 19:00") + + for i in 0..<20 { + let lat = 32.0 + Double(i % 10) * 2.0 // 32-50 latitude + let lon = -120.0 + Double(i / 10) * 30.0 // Spread across US + let stadium = makeStadium(city: "City\(i)", lat: lat, lon: lon) + stadiums[stadium.id] = stadium + } + + // Create 50 games over 14 days with varied scheduling + var games: [Game] = [] + let stadiumIds = Array(stadiums.keys) + + for i in 0..<50 { + let dayOffset = Double(i / 4) // ~4 games per day + let hoursOffset = Double(i % 4) * 3.0 // Spread within day + let gameTime = baseDate.addingTimeInterval(dayOffset * 24 * 3600 + hoursOffset * 3600) + + let stadiumId = stadiumIds[i % stadiumIds.count] + let game = makeGame(stadiumId: stadiumId, startTime: gameTime) + games.append(game) + } + + return (games, stadiums) + } + + @Test("Diversity: routes include varied game counts") + func diversity_VariedGameCounts() { + let (games, stadiums) = generateDiverseDataset() + let routes = GameDAGRouter.findRoutes( + games: games, + stadiums: stadiums, + constraints: .default + ) + + let gameCounts = Set(routes.map { $0.count }) + + #expect(gameCounts.count >= 3, "Should have at least 3 different route lengths, got \(gameCounts.count)") + #expect(gameCounts.contains(where: { $0 <= 3 }), "Should include short routes (≤3 games)") + #expect(gameCounts.contains(where: { $0 >= 5 }), "Should include long routes (≥5 games)") + } + + @Test("Diversity: routes span different numbers of cities") + func diversity_VariedCityCounts() { + let (games, stadiums) = generateDiverseDataset() + let routes = GameDAGRouter.findRoutes( + games: games, + stadiums: stadiums, + constraints: .default + ) + + // Calculate city counts for each route + let cityCounts = routes.map { route in + Set(route.compactMap { stadiums[$0.stadiumId]?.city }).count + } + let uniqueCityCounts = Set(cityCounts) + + #expect(uniqueCityCounts.count >= 3, "Should have at least 3 different city count variations, got \(uniqueCityCounts.count)") + #expect(cityCounts.contains(where: { $0 <= 3 }), "Should include routes with ≤3 cities") + #expect(cityCounts.contains(where: { $0 >= 4 }), "Should include routes with ≥4 cities") + } + + @Test("Diversity: routes include varied mileage ranges") + func diversity_VariedMileage() { + let (games, stadiums) = generateDiverseDataset() + let routes = GameDAGRouter.findRoutes( + games: games, + stadiums: stadiums, + constraints: .default + ) + + // Calculate total mileage for each route + let mileages = routes.map { route -> Double in + var totalMiles: Double = 0 + for i in 0..<(route.count - 1) { + let from = stadiums[route[i].stadiumId]! + let to = stadiums[route[i + 1].stadiumId]! + let distance = TravelEstimator.haversineDistanceMiles( + from: CLLocationCoordinate2D(latitude: from.coordinate.latitude, longitude: from.coordinate.longitude), + to: CLLocationCoordinate2D(latitude: to.coordinate.latitude, longitude: to.coordinate.longitude) + ) * 1.3 + totalMiles += distance + } + return totalMiles + } + + let shortRoutes = mileages.filter { $0 < 500 } + let mediumRoutes = mileages.filter { $0 >= 500 && $0 < 1000 } + let longRoutes = mileages.filter { $0 >= 1000 } + + #expect(shortRoutes.count > 0, "Should include short routes (<500 miles)") + #expect(mediumRoutes.count > 0 || longRoutes.count > 0, "Should include medium (500-1000mi) or long (≥1000mi) routes") + } + + @Test("Diversity: routes include varied durations") + func diversity_VariedDurations() { + let (games, stadiums) = generateDiverseDataset() + let routes = GameDAGRouter.findRoutes( + games: games, + stadiums: stadiums, + constraints: .default + ) + + // Calculate duration for each route + let durations = routes.compactMap { route -> Int? in + guard let first = route.first, let last = route.last else { return nil } + let calendar = Calendar.current + let days = calendar.dateComponents([.day], from: first.startTime, to: last.startTime).day ?? 0 + return max(1, days + 1) + } + let uniqueDurations = Set(durations) + + #expect(uniqueDurations.count >= 3, "Should have at least 3 different duration variations, got \(uniqueDurations.count)") + #expect(durations.contains(where: { $0 <= 3 }), "Should include short duration routes (≤3 days)") + #expect(durations.contains(where: { $0 >= 5 }), "Should include long duration routes (≥5 days)") + } + + @Test("Diversity: at least 3 game count buckets represented") + func diversity_BucketCoverage() { + let (games, stadiums) = generateDiverseDataset() + let routes = GameDAGRouter.findRoutes( + games: games, + stadiums: stadiums, + constraints: .default + ) + + // Map game counts to buckets (1, 2, 3, 4, 5+) + let buckets = routes.map { route -> Int in + let count = route.count + return count >= 5 ? 5 : count // Bucket 5+ as 5 + } + let uniqueBuckets = Set(buckets) + + #expect(uniqueBuckets.count >= 3, "Should have at least 3 different game count buckets represented, got \(uniqueBuckets.count)") + } + + @Test("Diversity: no duplicate routes") + func diversity_NoDuplicates() { + let (games, stadiums) = generateDiverseDataset() + let routes = GameDAGRouter.findRoutes( + games: games, + stadiums: stadiums, + constraints: .default + ) + + // Create unique keys for each route (sorted game IDs) + let routeKeys = routes.map { route in + route.map { $0.id.uuidString }.sorted().joined(separator: "-") + } + let uniqueKeys = Set(routeKeys) + + #expect(routeKeys.count == uniqueKeys.count, "All routes should be unique, found \(routeKeys.count - uniqueKeys.count) duplicates") + } }