153 lines
8.2 KiB
Swift
153 lines
8.2 KiB
Swift
import XCTest
|
|
#if SWIFT_PACKAGE
|
|
@testable import FuelBoardShared
|
|
#endif
|
|
|
|
final class FuelSavingsCalculatorTests: XCTestCase {
|
|
func testUKMPGConversionUsesImperialGallons() {
|
|
XCTAssertEqual(FuelSavingsCalculator.fuelUsedLitres(distanceMiles: 10, mpg: 45), 1.0102, accuracy: 0.0001)
|
|
}
|
|
|
|
func testPoundsBudgetConvertsToLitresUsingGivenPrice() throws {
|
|
let amount = FuelSavingsFillAmount(value: 40, unit: .pounds)
|
|
let litres = try FuelSavingsCalculator.fillLitres(amount: amount, pricePencePerLitre: 160)
|
|
XCTAssertEqual(litres, 25.0, accuracy: 0.0001)
|
|
}
|
|
|
|
func testLitresAmountPassesThrough() throws {
|
|
let amount = FuelSavingsFillAmount(value: 35, unit: .litres)
|
|
let litres = try FuelSavingsCalculator.fillLitres(amount: amount, pricePencePerLitre: 160)
|
|
XCTAssertEqual(litres, 35, accuracy: 0.0001)
|
|
}
|
|
|
|
func testNetSavingSubtractsAdditionalDrivingFuelCost() throws {
|
|
let result = try FuelSavingsCalculator.calculate(
|
|
mpg: 45,
|
|
amount: FuelSavingsFillAmount(value: 40, unit: .pounds),
|
|
baselinePricePencePerLitre: 160,
|
|
candidatePricePencePerLitre: 150,
|
|
baselineJourneyMiles: 2,
|
|
candidateJourneyMiles: 8
|
|
)
|
|
XCTAssertEqual(result.fillLitres, 26.6667, accuracy: 0.0001)
|
|
XCTAssertEqual(result.grossSavingGBP, 2.6667, accuracy: 0.0001)
|
|
XCTAssertEqual(result.extraDistanceMiles, 6, accuracy: 0.0001)
|
|
XCTAssertEqual(result.extraDrivingCostGBP, 0.9092, accuracy: 0.0001)
|
|
XCTAssertEqual(result.netSavingGBP, 1.7574, accuracy: 0.0001)
|
|
XCTAssertEqual(result.status, .worthTheDrive)
|
|
}
|
|
|
|
func testNegativeAndBreakEvenOutcomes() throws {
|
|
let negative = try FuelSavingsCalculator.calculate(
|
|
mpg: 45,
|
|
amount: FuelSavingsFillAmount(value: 10, unit: .litres),
|
|
baselinePricePencePerLitre: 150,
|
|
candidatePricePencePerLitre: 149,
|
|
baselineJourneyMiles: 1,
|
|
candidateJourneyMiles: 20
|
|
)
|
|
XCTAssertEqual(negative.status, .notWorthTheDrive)
|
|
XCTAssertLessThan(negative.netSavingGBP, 0)
|
|
|
|
let even = try FuelSavingsCalculator.calculate(
|
|
mpg: 45,
|
|
amount: FuelSavingsFillAmount(value: 10, unit: .litres),
|
|
baselinePricePencePerLitre: 150,
|
|
candidatePricePencePerLitre: 150,
|
|
baselineJourneyMiles: 2,
|
|
candidateJourneyMiles: 2
|
|
)
|
|
XCTAssertEqual(even.status, .breakEven)
|
|
}
|
|
|
|
func testInvalidInputsRejected() {
|
|
XCTAssertThrowsError(try FuelSavingsCalculator.fillLitres(amount: FuelSavingsFillAmount(value: 40, unit: .pounds), pricePencePerLitre: 0))
|
|
XCTAssertThrowsError(try FuelSavingsCalculator.calculate(mpg: 0, amount: FuelSavingsFillAmount(value: 40, unit: .pounds), baselinePricePencePerLitre: 160, candidatePricePencePerLitre: 150, baselineJourneyMiles: 1, candidateJourneyMiles: 2))
|
|
XCTAssertThrowsError(try FuelSavingsCalculator.calculate(mpg: 500, amount: FuelSavingsFillAmount(value: 40, unit: .pounds), baselinePricePencePerLitre: 160, candidatePricePencePerLitre: 150, baselineJourneyMiles: 1, candidateJourneyMiles: 2))
|
|
}
|
|
|
|
func testSwitchingPoundsToLitresPreservesPhysicalQuantity() throws {
|
|
let pounds = FuelSavingsFillAmount(value: 40, unit: .pounds)
|
|
let litres = try FuelSavingsCalculator.convert(amount: pounds, to: .litres, referencePricePencePerLitre: 160)
|
|
XCTAssertEqual(litres.unit, .litres)
|
|
XCTAssertEqual(litres.value, 25, accuracy: 0.0001)
|
|
let back = try FuelSavingsCalculator.convert(amount: litres, to: .pounds, referencePricePencePerLitre: 160)
|
|
XCTAssertEqual(back.value, 40, accuracy: 0.0001)
|
|
}
|
|
|
|
func testSortingPlacesUnavailableAfterSavingsAndBreaksTies() {
|
|
let a = FuelSavingsRankedStation(stationID: "a", result: .available(.init(fillLitres: 25, grossSavingGBP: 2, extraDrivingCostGBP: 1, extraDistanceMiles: 3, netSavingGBP: 1, status: .worthTheDrive)), distanceMiles: 5, pricePencePerLitre: 150)
|
|
let b = FuelSavingsRankedStation(stationID: "b", result: .available(.init(fillLitres: 25, grossSavingGBP: 3, extraDrivingCostGBP: 1, extraDistanceMiles: 2, netSavingGBP: 2, status: .worthTheDrive)), distanceMiles: 8, pricePencePerLitre: 149)
|
|
let c = FuelSavingsRankedStation(stationID: "c", result: .unavailable("No route"), distanceMiles: 1, pricePencePerLitre: 140)
|
|
XCTAssertEqual(FuelSavingsCalculator.sortedForWorthTheDrive([a, c, b]).map(\.stationID), ["b", "a", "c"])
|
|
}
|
|
|
|
func testBestValueAlertCandidateRequiresConfiguredSavingsAndThreshold() {
|
|
let stations = [
|
|
station("near", lat: 0, lng: 0, price: 160),
|
|
station("cheap", lat: 0, lng: 0, price: 150),
|
|
]
|
|
let journeys = ["near": 2.0, "cheap": 4.0]
|
|
let off = FuelSavingsSettings(enabled: false, mpg: 45, preferredAmount: .init(value: 40, unit: .pounds))
|
|
XCTAssertNil(FuelSavingsCalculator.bestValueAlertCandidate(
|
|
stations: stations,
|
|
fuel: .e10,
|
|
settings: off,
|
|
amount: off.preferredAmount,
|
|
minimumNetSavingGBP: 1.0,
|
|
journeyMiles: { journeys[$0.id].map { FuelSavingsJourney(miles: $0, basis: .roadRoute) } }
|
|
))
|
|
|
|
let on = FuelSavingsSettings(enabled: true, mpg: 45, preferredAmount: .init(value: 40, unit: .pounds))
|
|
XCTAssertNil(FuelSavingsCalculator.bestValueAlertCandidate(
|
|
stations: stations,
|
|
fuel: .e10,
|
|
settings: on,
|
|
amount: on.preferredAmount,
|
|
minimumNetSavingGBP: 5.0,
|
|
journeyMiles: { journeys[$0.id].map { FuelSavingsJourney(miles: $0, basis: .roadRoute) } }
|
|
))
|
|
}
|
|
|
|
func testBestValueAlertCandidatePicksHighestNetSavingAboveThreshold() throws {
|
|
let stations = [
|
|
station("baseline", lat: 0, lng: 0, price: 160),
|
|
station("small", lat: 0, lng: 0, price: 154),
|
|
station("best", lat: 0, lng: 0, price: 148),
|
|
]
|
|
let journeys = ["baseline": 2.0, "small": 4.0, "best": 6.0]
|
|
let settings = FuelSavingsSettings(enabled: true, mpg: 45, preferredAmount: .init(value: 40, unit: .pounds))
|
|
let candidate = FuelSavingsCalculator.bestValueAlertCandidate(
|
|
stations: stations,
|
|
fuel: .e10,
|
|
settings: settings,
|
|
amount: settings.preferredAmount,
|
|
minimumNetSavingGBP: 1.0,
|
|
journeyMiles: { journeys[$0.id].map { FuelSavingsJourney(miles: $0, basis: .roadRoute) } }
|
|
)
|
|
XCTAssertEqual(candidate?.station.id, "best")
|
|
XCTAssertEqual(candidate?.result.status, .worthTheDrive)
|
|
XCTAssertGreaterThan(candidate?.result.netSavingGBP ?? 0, 1.0)
|
|
}
|
|
|
|
func testBestValueAlertCandidateBreaksTiesByDistanceThenPrice() throws {
|
|
let first = FuelSavingsResult(fillLitres: 25, grossSavingGBP: 3, extraDrivingCostGBP: 1, extraDistanceMiles: 2, netSavingGBP: 2, status: .worthTheDrive)
|
|
let nearer = FuelSavingsRankedStation(stationID: "nearer", result: .available(first), distanceMiles: 4, pricePencePerLitre: 149)
|
|
let farther = FuelSavingsRankedStation(stationID: "farther", result: .available(first), distanceMiles: 8, pricePencePerLitre: 145)
|
|
XCTAssertEqual(FuelSavingsCalculator.sortedForWorthTheDrive([farther, nearer]).first?.stationID, "nearer")
|
|
}
|
|
|
|
private func station(_ id: String, lat: Double, lng: Double, price: Double) -> FuelStation {
|
|
FuelStation(id: id, name: id, brand: id, address: "", postcode: "", lat: lat, lng: lng, prices: [.e10: price], priceUpdated: nil)
|
|
}
|
|
}
|
|
|
|
final class FuelSavingsSettingsTests: XCTestCase {
|
|
func testSettingsValidationRequiresEnabledMPGAndFillAmount() {
|
|
XCTAssertFalse(FuelSavingsSettings(enabled: false, mpg: 45, preferredAmount: .init(value: 40, unit: .pounds)).isConfigured)
|
|
XCTAssertFalse(FuelSavingsSettings(enabled: true, mpg: 0, preferredAmount: .init(value: 40, unit: .pounds)).isConfigured)
|
|
XCTAssertFalse(FuelSavingsSettings(enabled: true, mpg: 45, preferredAmount: .init(value: 0, unit: .pounds)).isConfigured)
|
|
XCTAssertTrue(FuelSavingsSettings(enabled: true, mpg: 45, preferredAmount: .init(value: 40, unit: .pounds)).isConfigured)
|
|
}
|
|
}
|