Files
FuelBoard Contributor d7a0ecb79a Add Worth the Drive fuel savings with road-routed estimates
Optional Fuel Savings feature (off by default): vehicle MPG in UK imperial gallons, fill amount in pounds or litres, persisted through the existing keychain/app-group store. Ranks the WHOLE station pool by estimated net saving (fuel-price saving at each pump minus the extra return-trip driving cost) using MapKit road distances with a straight-line fallback marked (est.).

The closest station with a valid price for the selected fuel is the comparison baseline and is labelled "Closest station"; other rows read "Save £X.XX after travel" / "£X.XX worse off after travel" / "Break even". Labels live in the shared model so they are unit-tested. The TOP badge still marks the cheapest pump price and reads "CHEAPEST" when the list is ranked by net saving.

Road routing now prioritises the Worth-the-Drive candidates plus the baseline and merges cache entries instead of replacing them, so far-out stations get real routes rather than permanent straight-line estimates. Numeric fields gain a Done button above the keypad (decimalPad has no Return key); the fill-up control is hidden unless the feature is configured; Favourites shares one session fill amount with the station list. 131 tests.
2026-10-11 14:34:53 +01:00

70 lines
3.0 KiB
Swift

import XCTest
@testable import FuelBoardShared
/// Shared payload storage must survive an install whose provisioning profile
/// has NO `com.apple.security.application-groups` entitlement (free/personal
/// team and SideStore installs) — the exact situation this test runner is in.
///
/// Regression guard: `appGroupFileURL` used to return nil without a group
/// container, so every save silently failed and every load returned empty. That
/// left `loadStations()` permanently empty (letting the freshness gate skip the
/// fetch for ever, i.e. a blank station list) and re-downloaded the ~2.8 MB
/// dump on every refresh. Storage now falls back to the app's own container.
final class FuelStoreFileCacheTests: XCTestCase {
func testSharedFileURLExistsWithoutAppGroupContainer() {
XCTAssertNotNil(
FuelStore.appGroupFileURL("fuelboard.test.probe.json"),
"storage must resolve to a writable location even when the App Group container is unprovisioned"
)
}
func testSharedFileRoundTripsWithoutAppGroupContainer() throws {
let name = "fuelboard.test.roundtrip.json"
let payload = Data("hello-cache".utf8)
XCTAssertTrue(
FuelStore.saveAppGroupFile(payload, name: name),
"save must succeed without an App Group container"
)
XCTAssertEqual(FuelStore.loadAppGroupFile(name), payload)
if let url = FuelStore.appGroupFileURL(name) {
try? FileManager.default.removeItem(at: url)
}
}
/// The dump cache is what stops a ~2.8 MB re-download on every refresh, so
/// it must round-trip too — not just the station list.
func testDumpCacheRoundTripsWithoutAppGroupContainer() {
let day = "2026-01-01"
let data = Data(repeating: 7, count: 4096)
MirrorFuelProvider.saveDumpCache(day: day, data: data)
let cache = MirrorFuelProvider.loadDumpCache()
XCTAssertEqual(cache?.day, day, "cached dump day must survive a round-trip")
XCTAssertEqual(cache?.data, data, "cached dump payload must survive a round-trip")
if let url = FuelStore.appGroupFileURL(MirrorFuelProvider.liveDumpCacheFileName) {
try? FileManager.default.removeItem(at: url)
}
}
/// The road-distance cache is memoized in memory because re-reading it per
/// station (it lives in the keychain) stalled the main thread past the iOS
/// launch watchdog. A save must therefore invalidate the memo — a stale
/// in-memory cache would show wrong distances after a route refresh.
func testRoadDistanceMemoRefreshesAfterSave() {
FuelStore.saveRoadDistances(sourceLat: 51.0, sourceLng: -1.0, entries: [:])
XCTAssertEqual(FuelStore.loadRoadDistances()?.sourceLat, 51.0)
FuelStore.saveRoadDistances(sourceLat: 52.0, sourceLng: -2.0, entries: [:])
XCTAssertEqual(
FuelStore.loadRoadDistances()?.sourceLat, 52.0,
"load must reflect the newest save, not a stale memoized cache"
)
}
}