// // StadiumProximityMatcherTests.swift // SportsTimeTests // // TDD specification tests for StadiumProximityMatcher and related types. // import Testing import Foundation import CoreLocation @testable import SportsTime // MARK: - MatchConfidence Tests @Suite("MatchConfidence") struct MatchConfidenceTests { // MARK: - Specification Tests: description @Test("description: high has description") func description_high() { #expect(!MatchConfidence.high.description.isEmpty) } @Test("description: medium has description") func description_medium() { #expect(!MatchConfidence.medium.description.isEmpty) } @Test("description: low has description") func description_low() { #expect(!MatchConfidence.low.description.isEmpty) } @Test("description: none has description") func description_none() { #expect(!MatchConfidence.none.description.isEmpty) } // MARK: - Specification Tests: shouldAutoSelect @Test("shouldAutoSelect: true for high confidence") func shouldAutoSelect_high() { #expect(MatchConfidence.high.shouldAutoSelect == true) } @Test("shouldAutoSelect: false for medium confidence") func shouldAutoSelect_medium() { #expect(MatchConfidence.medium.shouldAutoSelect == false) } @Test("shouldAutoSelect: false for low confidence") func shouldAutoSelect_low() { #expect(MatchConfidence.low.shouldAutoSelect == false) } @Test("shouldAutoSelect: false for none confidence") func shouldAutoSelect_none() { #expect(MatchConfidence.none.shouldAutoSelect == false) } // MARK: - Specification Tests: Comparable @Test("Comparable: high > medium > low > none") func comparable_ordering() { #expect(MatchConfidence.high > MatchConfidence.medium) #expect(MatchConfidence.medium > MatchConfidence.low) #expect(MatchConfidence.low > MatchConfidence.none) } // MARK: - Invariant Tests @Test("Invariant: all cases have non-empty description") func invariant_allHaveDescription() { let cases: [MatchConfidence] = [.high, .medium, .low, .none] for confidence in cases { #expect(!confidence.description.isEmpty) } } } // MARK: - TemporalConfidence Tests @Suite("TemporalConfidence") struct TemporalConfidenceTests { // MARK: - Specification Tests: description @Test("description: exactDay has description") func description_exactDay() { #expect(!TemporalConfidence.exactDay.description.isEmpty) } @Test("description: adjacentDay has description") func description_adjacentDay() { #expect(!TemporalConfidence.adjacentDay.description.isEmpty) } @Test("description: outOfRange has description") func description_outOfRange() { #expect(!TemporalConfidence.outOfRange.description.isEmpty) } // MARK: - Specification Tests: Comparable @Test("Comparable: exactDay > adjacentDay > outOfRange") func comparable_ordering() { #expect(TemporalConfidence.exactDay > TemporalConfidence.adjacentDay) #expect(TemporalConfidence.adjacentDay > TemporalConfidence.outOfRange) } // MARK: - Invariant Tests @Test("Invariant: all cases have non-empty description") func invariant_allHaveDescription() { let cases: [TemporalConfidence] = [.exactDay, .adjacentDay, .outOfRange] for temporal in cases { #expect(!temporal.description.isEmpty) } } } // MARK: - CombinedConfidence Tests @Suite("CombinedConfidence") struct CombinedConfidenceTests { // MARK: - Specification Tests: combine @Test("combine: high + exactDay = autoSelect") func combine_highExactDay() { let result = CombinedConfidence.combine(spatial: .high, temporal: .exactDay) #expect(result == .autoSelect) } @Test("combine: high + adjacentDay = userConfirm") func combine_highAdjacentDay() { let result = CombinedConfidence.combine(spatial: .high, temporal: .adjacentDay) #expect(result == .userConfirm) } @Test("combine: medium + exactDay = userConfirm") func combine_mediumExactDay() { let result = CombinedConfidence.combine(spatial: .medium, temporal: .exactDay) #expect(result == .userConfirm) } @Test("combine: medium + adjacentDay = userConfirm") func combine_mediumAdjacentDay() { let result = CombinedConfidence.combine(spatial: .medium, temporal: .adjacentDay) #expect(result == .userConfirm) } @Test("combine: low spatial = manualOnly regardless of temporal") func combine_lowSpatial() { #expect(CombinedConfidence.combine(spatial: .low, temporal: .exactDay) == .manualOnly) #expect(CombinedConfidence.combine(spatial: .low, temporal: .adjacentDay) == .manualOnly) #expect(CombinedConfidence.combine(spatial: .low, temporal: .outOfRange) == .manualOnly) } @Test("combine: none spatial = manualOnly regardless of temporal") func combine_noneSpatial() { #expect(CombinedConfidence.combine(spatial: .none, temporal: .exactDay) == .manualOnly) #expect(CombinedConfidence.combine(spatial: .none, temporal: .adjacentDay) == .manualOnly) #expect(CombinedConfidence.combine(spatial: .none, temporal: .outOfRange) == .manualOnly) } @Test("combine: outOfRange temporal with high/medium spatial = manualOnly") func combine_outOfRangeTemporal() { #expect(CombinedConfidence.combine(spatial: .high, temporal: .outOfRange) == .manualOnly) #expect(CombinedConfidence.combine(spatial: .medium, temporal: .outOfRange) == .manualOnly) } // MARK: - Specification Tests: description @Test("description: autoSelect has description") func description_autoSelect() { #expect(!CombinedConfidence.autoSelect.description.isEmpty) } @Test("description: userConfirm has description") func description_userConfirm() { #expect(!CombinedConfidence.userConfirm.description.isEmpty) } @Test("description: manualOnly has description") func description_manualOnly() { #expect(!CombinedConfidence.manualOnly.description.isEmpty) } // MARK: - Specification Tests: Comparable @Test("Comparable: autoSelect > userConfirm > manualOnly") func comparable_ordering() { #expect(CombinedConfidence.autoSelect > CombinedConfidence.userConfirm) #expect(CombinedConfidence.userConfirm > CombinedConfidence.manualOnly) } } // MARK: - StadiumMatch Tests @Suite("StadiumMatch") struct StadiumMatchTests { private func makeStadium() -> Stadium { Stadium( id: "stadium_1", name: "Test Stadium", city: "Test City", state: "TS", latitude: 40.7580, longitude: -73.9855, capacity: 40000, sport: .mlb ) } // MARK: - Specification Tests: confidence @Test("confidence: high for distance < 500m") func confidence_high() { let match = StadiumMatch(stadium: makeStadium(), distance: 300) #expect(match.confidence == .high) } @Test("confidence: medium for distance 500m - 2km") func confidence_medium() { let match = StadiumMatch(stadium: makeStadium(), distance: 1000) #expect(match.confidence == .medium) } @Test("confidence: low for distance 2km - 5km") func confidence_low() { let match = StadiumMatch(stadium: makeStadium(), distance: 3000) #expect(match.confidence == .low) } @Test("confidence: none for distance > 5km") func confidence_none() { let match = StadiumMatch(stadium: makeStadium(), distance: 6000) #expect(match.confidence == .none) } // MARK: - Specification Tests: formattedDistance @Test("formattedDistance: meters for < 1km") func formattedDistance_meters() { let match = StadiumMatch(stadium: makeStadium(), distance: 500) #expect(match.formattedDistance.contains("m")) #expect(!match.formattedDistance.contains("km")) } @Test("formattedDistance: kilometers for >= 1km") func formattedDistance_kilometers() { let match = StadiumMatch(stadium: makeStadium(), distance: 2500) #expect(match.formattedDistance.contains("km")) } // MARK: - Specification Tests: Identifiable @Test("id: matches stadium id") func id_matchesStadiumId() { let stadium = makeStadium() let match = StadiumMatch(stadium: stadium, distance: 100) #expect(match.id == stadium.id) } // MARK: - Invariant Tests @Test("Invariant: confidence boundaries") func invariant_confidenceBoundaries() { let stadium = makeStadium() // Boundary at 500m #expect(StadiumMatch(stadium: stadium, distance: 499).confidence == .high) #expect(StadiumMatch(stadium: stadium, distance: 500).confidence == .medium) // Boundary at 2000m #expect(StadiumMatch(stadium: stadium, distance: 1999).confidence == .medium) #expect(StadiumMatch(stadium: stadium, distance: 2000).confidence == .low) // Boundary at 5000m #expect(StadiumMatch(stadium: stadium, distance: 4999).confidence == .low) #expect(StadiumMatch(stadium: stadium, distance: 5000).confidence == .none) } } // MARK: - PhotoMatchConfidence Composition Tests @Suite("PhotoMatchConfidence Composition") struct PhotoMatchConfidenceCompositionTests { @Test("combined: derived from spatial and temporal") func combined_derived() { let confidence = PhotoMatchConfidence(spatial: .high, temporal: .exactDay) #expect(confidence.combined == .autoSelect) #expect(confidence.spatial == .high) #expect(confidence.temporal == .exactDay) } @Test("combined: matches CombinedConfidence.combine result") func combined_matchesCombine() { let spatials: [MatchConfidence] = [.high, .medium, .low, .none] let temporals: [TemporalConfidence] = [.exactDay, .adjacentDay, .outOfRange] for spatial in spatials { for temporal in temporals { let confidence = PhotoMatchConfidence(spatial: spatial, temporal: temporal) let expected = CombinedConfidence.combine(spatial: spatial, temporal: temporal) #expect(confidence.combined == expected) } } } } // MARK: - ProximityConstants Tests @Suite("ProximityConstants") struct ProximityConstantsTests { @Test("highConfidenceRadius: 500m") func highConfidenceRadius() { #expect(ProximityConstants.highConfidenceRadius == 500) } @Test("mediumConfidenceRadius: 2km") func mediumConfidenceRadius() { #expect(ProximityConstants.mediumConfidenceRadius == 2000) } @Test("searchRadius: 5km") func searchRadius() { #expect(ProximityConstants.searchRadius == 5000) } @Test("dateToleranceDays: 1") func dateToleranceDays() { #expect(ProximityConstants.dateToleranceDays == 1) } }