Files
Sportstime/SportsTimeTests/Services/EVChargingServiceTests.swift
Trey t 8162b4a029 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>
2026-01-16 14:07:41 -06:00

211 lines
7.1 KiB
Swift

//
// EVChargingServiceTests.swift
// SportsTimeTests
//
// TDD specification tests for EVChargingService types.
//
import Testing
import Foundation
@testable import SportsTime
// MARK: - ChargerType Detection Tests
@Suite("ChargerType Detection")
struct ChargerTypeDetectionTests {
// MARK: - Test Helper
// Note: detectChargerType and estimateChargeTime are private in EVChargingService
// These tests document expected behavior for the ChargerType enum
// MARK: - Specification Tests: ChargerType Cases
/// - Expected Behavior: ChargerType has three cases
@Test("ChargerType: has supercharger case")
func chargerType_supercharger() {
let type = ChargerType.supercharger
#expect(type == .supercharger)
}
@Test("ChargerType: has dcFast case")
func chargerType_dcFast() {
let type = ChargerType.dcFast
#expect(type == .dcFast)
}
@Test("ChargerType: has level2 case")
func chargerType_level2() {
let type = ChargerType.level2
#expect(type == .level2)
}
// MARK: - Invariant Tests
/// - Invariant: All charger types are distinct
@Test("Invariant: all charger types are distinct")
func invariant_distinctTypes() {
let types: [ChargerType] = [.supercharger, .dcFast, .level2]
let uniqueTypes = Set(types)
#expect(types.count == uniqueTypes.count)
}
}
// MARK: - EVChargingStop Tests
@Suite("EVChargingStop")
struct EVChargingStopTests {
// MARK: - Test Data
private func makeStop(
name: String = "Tesla Supercharger",
chargerType: ChargerType = .supercharger,
estimatedChargeTime: TimeInterval = 25 * 60
) -> EVChargingStop {
EVChargingStop(
name: name,
location: LocationInput(name: name, coordinate: nil, address: nil),
chargerType: chargerType,
estimatedChargeTime: estimatedChargeTime
)
}
// MARK: - Specification Tests: Properties
@Test("EVChargingStop: stores name")
func evChargingStop_name() {
let stop = makeStop(name: "Test Charger")
#expect(stop.name == "Test Charger")
}
@Test("EVChargingStop: stores chargerType")
func evChargingStop_chargerType() {
let stop = makeStop(chargerType: .dcFast)
#expect(stop.chargerType == .dcFast)
}
@Test("EVChargingStop: stores estimatedChargeTime")
func evChargingStop_estimatedChargeTime() {
let stop = makeStop(estimatedChargeTime: 30 * 60)
#expect(stop.estimatedChargeTime == 30 * 60)
}
@Test("EVChargingStop: stores location")
func evChargingStop_location() {
let stop = makeStop(name: "Test Location")
#expect(stop.location.name == "Test Location")
}
// MARK: - Specification Tests: Expected Charge Times
/// - Expected Behavior: Supercharger takes ~25 minutes
@Test("Expected charge time: supercharger ~25 minutes")
func expectedChargeTime_supercharger() {
// Based on EVChargingService.estimateChargeTime implementation
let expectedTime: TimeInterval = 25 * 60
let stop = makeStop(chargerType: .supercharger, estimatedChargeTime: expectedTime)
#expect(stop.estimatedChargeTime == 25 * 60)
}
/// - Expected Behavior: DC Fast takes ~30 minutes
@Test("Expected charge time: dcFast ~30 minutes")
func expectedChargeTime_dcFast() {
let expectedTime: TimeInterval = 30 * 60
let stop = makeStop(chargerType: .dcFast, estimatedChargeTime: expectedTime)
#expect(stop.estimatedChargeTime == 30 * 60)
}
/// - Expected Behavior: Level 2 takes ~2 hours
@Test("Expected charge time: level2 ~2 hours")
func expectedChargeTime_level2() {
let expectedTime: TimeInterval = 120 * 60
let stop = makeStop(chargerType: .level2, estimatedChargeTime: expectedTime)
#expect(stop.estimatedChargeTime == 120 * 60)
}
// MARK: - Invariant Tests
/// - Invariant: Each stop has unique id
@Test("Invariant: each stop has unique id")
func invariant_uniqueId() {
let stop1 = makeStop()
let stop2 = makeStop()
#expect(stop1.id != stop2.id)
}
}
// MARK: - Charger Detection Behavior Tests
@Suite("Charger Detection Behavior")
struct ChargerDetectionBehaviorTests {
// These tests document the expected name charger type mapping
// based on the detectChargerType implementation
// MARK: - Specification Tests: Name Detection Rules
/// - Expected Behavior: "Tesla" or "Supercharger" .supercharger
@Test("Detection rule: Tesla names should map to supercharger")
func detectionRule_tesla() {
// Expected names that should return .supercharger:
// - "Tesla Supercharger"
// - "Supercharger"
// - "Tesla Station"
let teslaNames = ["Tesla Supercharger", "Supercharger", "Tesla Station"]
for name in teslaNames {
let lowercased = name.lowercased()
let isTesla = lowercased.contains("tesla") || lowercased.contains("supercharger")
#expect(isTesla, "'\(name)' should be detected as Tesla/Supercharger")
}
}
/// - Expected Behavior: DC Fast keywords .dcFast
@Test("Detection rule: DC Fast keywords should map to dcFast")
func detectionRule_dcFast() {
// Expected names that should return .dcFast:
// - "DC Fast Charging"
// - "DCFC Station"
// - "CCS Charger"
// - "CHAdeMO"
// - "Electrify America"
// - "EVgo"
let dcFastKeywords = ["dc fast", "dcfc", "ccs", "chademo", "electrify america", "evgo"]
let testNames = ["DC Fast Charging", "DCFC Station", "CCS Charger", "CHAdeMO", "Electrify America", "EVgo Station"]
for name in testNames {
let lowercased = name.lowercased()
let isDCFast = dcFastKeywords.contains { lowercased.contains($0) }
#expect(isDCFast, "'\(name)' should be detected as DC Fast")
}
}
/// - Expected Behavior: Other names .level2 (default)
@Test("Detection rule: Unknown names default to level2")
func detectionRule_level2Default() {
// Names without specific keywords should default to Level 2
let genericNames = ["ChargePoint", "Blink", "Generic EV Charger", "Unknown Station"]
for name in genericNames {
let lowercased = name.lowercased()
let isTesla = lowercased.contains("tesla") || lowercased.contains("supercharger")
let isDCFast = ["dc fast", "dcfc", "ccs", "chademo", "electrify america", "evgo"]
.contains { lowercased.contains($0) }
#expect(!isTesla && !isDCFast, "'\(name)' should not match Tesla or DC Fast")
}
}
// MARK: - Invariant Tests
/// - Invariant: Detection is case-insensitive
@Test("Invariant: detection is case-insensitive")
func invariant_caseInsensitive() {
let variations = ["TESLA", "Tesla", "tesla", "TeSLa"]
for name in variations {
let isTesla = name.lowercased().contains("tesla")
#expect(isTesla, "'\(name)' should match 'tesla' case-insensitively")
}
}
}