Files
fuelboard/FuelBoardTests/Tests/FuelBoardSharedTests/LiveChainTests.swift

126 lines
5.5 KiB
Swift

import XCTest
#if SWIFT_PACKAGE
@testable import FuelBoardShared
#endif
/// P0 live chain: public GitHub mirror first everywhere →
/// (app: bundled dump in ContentView's catch, widget: cache → placeholder).
/// No LAN relay fallback is compiled into the app binary.
final class LiveChainTests: XCTestCase {
// MARK: Fixtures
private final class StubProvider: FuelPriceProviding {
var result: [FuelStation]
var error: Error?
private(set) var callCount = 0
init(result: [FuelStation]? = nil, error: Error? = nil) {
self.result = result ?? []
self.error = error
}
func fetchStations(near lat: Double?, lng: Double?, fuel: FuelType, radiusKM: Double?) async throws -> [FuelStation] {
callCount += 1
if let error { throw error }
return result
}
}
private func fixtureStation(_ id: String = "s1", lat: Double = 53.7, lng: Double = -1.8) -> FuelStation {
FuelStation(id: id, name: "Station \(id)", brand: "Test", address: "1 High St",
postcode: "SW1A 1AA", lat: lat, lng: lng,
prices: [.e10: 137.9, .e5: 144.9, .diesel: 144.9], priceUpdated: nil)
}
// MARK: Full path (app refresh): GitHub mirror only
func testFullPathUsesMirror() async throws {
let mirror = StubProvider(result: [fixtureStation()])
let chain = LiveChainProvider(mirror: mirror)
let stations = try await chain.fetchStations(near: nil, lng: nil, fuel: .e10, radiusKM: nil)
XCTAssertEqual(stations.count, 1)
XCTAssertEqual(mirror.callCount, 1, "full path must use the public GitHub mirror")
XCTAssertEqual(LiveChainProvider.lastSource, "github")
}
func testFullPathPropagatesMirrorFailure() async {
let mirror = StubProvider(error: FuelProviderError.mirrorUnavailable)
let chain = LiveChainProvider(mirror: mirror)
do {
_ = try await chain.fetchStations(near: nil, lng: nil, fuel: .e10, radiusKM: nil)
XCTFail("chain must throw when the public mirror fails; callers decide cache/bundled fallback")
} catch {
XCTAssertEqual(mirror.callCount, 1)
}
}
// MARK: Focused path (alert checks + widget): GitHub mirror only
func testFocusedPathUsesMirror() async throws {
let mirror = StubProvider(result: [fixtureStation("mirror")])
let chain = LiveChainProvider(mirror: mirror)
let stations = try await chain.fetchStations(near: 53.7, lng: -1.8, fuel: .e10, radiusKM: 5)
XCTAssertEqual(stations.first?.id, "mirror", "focused path must use the public GitHub mirror")
XCTAssertEqual(mirror.callCount, 1)
XCTAssertEqual(LiveChainProvider.lastSource, "github")
}
func testFocusedPathPropagatesMirrorFailure() async {
let mirror = StubProvider(error: FuelProviderError.mirrorUnavailable)
let chain = LiveChainProvider(mirror: mirror)
do {
_ = try await chain.fetchStations(near: 53.7, lng: -1.8, fuel: .e10, radiusKM: 5)
XCTFail("focused path must throw when the public mirror fails")
} catch {
XCTAssertEqual(mirror.callCount, 1)
}
}
// MARK: Mirror focused projection (replaces the relay's server-side radius)
func testMirrorFocusedFiltersByFuelRadiusDistanceAndLimit() {
let here = fixtureStation("here", lat: 53.7, lng: -1.8) // e10+others, at origin
let far = fixtureStation("far", lat: 54.0, lng: -1.8) // ~33 km away, e10
let noFuel = FuelStation(id: "noFuel", name: "Diesel Only", brand: "Test",
address: "2 High St", postcode: "SW1A 1AA",
lat: 53.7, lng: -1.8, prices: [.diesel: 144.9], priceUpdated: nil)
let d1 = fixtureStation("d1", lat: 53.701, lng: -1.8)
let d2 = fixtureStation("d2", lat: 53.702, lng: -1.8)
let out = MirrorFuelProvider.focused([far, noFuel, d2, d1, here],
near: 53.7, lng: -1.8, fuel: .e10, radiusKM: 10)
XCTAssertEqual(out.map(\.id), ["here", "d1", "d2"], "nearest-first, fuel-only, within radius")
XCTAssertFalse(out.contains { $0.id == "far" }, "outside radius must drop")
XCTAssertFalse(out.contains { $0.id == "noFuel" }, "missing fuel must drop")
}
func testMirrorFocusedPassesThroughWithoutRadius() {
let stations = [fixtureStation("a"), fixtureStation("b")]
XCTAssertEqual(MirrorFuelProvider.focused(stations, near: nil, lng: nil, fuel: .e10, radiusKM: nil).count, 2)
}
func testMirrorFocusedLimit() {
let stations = (0..<600).map { fixtureStation("s\($0)", lat: 53.7 + Double($0) * 0.0001, lng: -1.8) }
let out = MirrorFuelProvider.focused(stations, near: 53.7, lng: -1.8, fuel: .e10, radiusKM: 100)
XCTAssertEqual(out.count, 500, "focused projection must stay bounded")
}
// MARK: Mirror day-cache decision
func testDumpCacheReuseDecision() {
XCTAssertTrue(MirrorFuelProvider.canReuseCache(cachedDay: "2026-08-15", latestDay: "2026-08-15"),
"same day → reuse the cached dump, no ~2.8 MB re-download")
XCTAssertFalse(MirrorFuelProvider.canReuseCache(cachedDay: "2026-08-14", latestDay: "2026-08-15"))
XCTAssertFalse(MirrorFuelProvider.canReuseCache(cachedDay: nil, latestDay: "2026-08-15"))
XCTAssertFalse(MirrorFuelProvider.canReuseCache(cachedDay: "2026-08-15", latestDay: nil))
}
}