import XCTest @testable import FuelBoardShared // FuelBoard shared-logic tests. Run from FuelBoardTests/ with `swift test`. // Covers the pure logic that drives the app + widget: title sanitizer, price // guard, RAG, sorting baselines, distance, brand normalization, fuel labels. final class TitleSanitizerTests: XCTestCase { func testAllCapsToTitleCase() { XCTAssertEqual("SHELL SALTERHEBBLE".sanitizedStationTitle, "Shell Salterhebble") XCTAssertEqual("TESCO EXPRESS YORK".sanitizedStationTitle, "Tesco Express York") } func testSainsburysApostrophe() { XCTAssertEqual("SAINSBURYS HALIFAX".sanitizedStationTitle, "Sainsbury's Halifax") XCTAssertEqual("SAINSBURYS".sanitizedStationTitle, "Sainsbury's") } func testAcronymsStayUppercase() { XCTAssertEqual("BP HESSLE".sanitizedStationTitle, "BP Hessle") XCTAssertEqual("MFG BRIGHOUSE".sanitizedStationTitle, "MFG Brighouse") XCTAssertEqual("ASDA WAKEFIELD".sanitizedStationTitle, "ASDA Wakefield") XCTAssertEqual("MOTO WETHERBY".sanitizedStationTitle, "MOTO Wetherby") } func testConnectorsLowercase() { XCTAssertEqual("GULF OIL OF MANCHESTER".sanitizedStationTitle, "Gulf Oil of Manchester") } func testLtd() { XCTAssertEqual("SMITHS GARAGES LTD".sanitizedStationTitle, "Smiths Garages Ltd") } func testHyphenParenChunks() { XCTAssertEqual("NEWCASTLE-UNDER-LYME SERVICES".sanitizedStationTitle, "Newcastle-Under-Lyme Services") XCTAssertEqual("SPAR (MEADOWHALL)".sanitizedStationTitle, "SPAR (Meadowhall)") } func testMixedCasePassthrough() { XCTAssertEqual("Shell Salterhebble".sanitizedStationTitle, "Shell Salterhebble") XCTAssertEqual("Esso M62 EASTBOUND".sanitizedStationTitle, "Esso M62 Eastbound") } func testNoLettersPassthrough() { XCTAssertEqual("M62 J25".sanitizedStationTitle, "M62 J25") } func testAmpersandChunkReset() { XCTAssertEqual("SHELL & BP SERVICES".sanitizedStationTitle, "Shell & BP Services") } } final class PriceGuardTests: XCTestCase { func testInBandPricesKept() throws { let json = """ {"stations":[{"id":"s1","name":"SHELL LEEDS","brand":"Shell","address":"A1","postcode":"LS1 1AA","lat":53.8,"lng":-1.5,"prices":{"E10":137.9,"E5":144.9,"DIESEL":142.9}}]} """ let stations = try FuelPriceProvider.decodeStations(from: Data(json.utf8)) XCTAssertEqual(stations.count, 1) XCTAssertEqual(stations[0].prices[.e10], 137.9) XCTAssertEqual(stations[0].prices[.e5], 144.9) XCTAssertEqual(stations[0].prices[.diesel], 142.9) } func testRelayMetaDecoded() throws { let json = """ {"source":"api","stations_count":8010,"data_updated":"2026-08-12T10:23:00.000Z","stations":[{"id":"s1","name":"SHELL LEEDS","brand":"Shell","address":"A1","postcode":"LS1 1AA","lat":53.8,"lng":-1.5,"prices":{"E10":137.9}}]} """ let meta = FuelPriceProvider.decodeRelayMeta(from: Data(json.utf8)) XCTAssertEqual(meta?.source, "api") XCTAssertEqual(meta?.stationCount, 8010) XCTAssertEqual(meta?.dataUpdated, "2026-08-12T10:23:00.000Z") } func testRelayMetaAbsentIsNil() throws { // Older relay without envelope metadata must not fail decode — nil meta. let json = """ {"stations":[{"id":"s1","name":"SHELL LEEDS","brand":"Shell","address":"A1","postcode":"LS1 1AA","lat":53.8,"lng":-1.5,"prices":{"E10":137.9}}]} """ let meta = FuelPriceProvider.decodeRelayMeta(from: Data(json.utf8)) XCTAssertNil(meta?.source) XCTAssertNil(meta?.stationCount) XCTAssertNil(meta?.dataUpdated) } func testOutOfBandPricesDropped() throws { let json = """ {"stations":[{"id":"s1","name":"GARBAGE","brand":"X","address":"A","postcode":"L","lat":0,"lng":0,"prices":{"E10":1.3,"E5":1589.0,"DIESEL":137.9}}]} """ let stations = try FuelPriceProvider.decodeStations(from: Data(json.utf8)) XCTAssertEqual(stations.count, 1) XCTAssertNil(stations[0].prices[.e10], "1.3p garbage must be dropped") XCTAssertNil(stations[0].prices[.e5], "1589p garbage must be dropped") XCTAssertEqual(stations[0].prices[.diesel], 137.9, "in-band price kept") } func testBoundaryValues() throws { let json = """ {"stations":[{"id":"s1","name":"BOUNDARY","brand":"X","address":"A","postcode":"L","lat":0,"lng":0,"prices":{"E10":50.0,"E5":500.0,"DIESEL":49.9}}]} """ let stations = try FuelPriceProvider.decodeStations(from: Data(json.utf8)) XCTAssertEqual(stations[0].prices[.e10], 50.0, "lower bound inclusive") XCTAssertEqual(stations[0].prices[.e5], 500.0, "upper bound inclusive") XCTAssertNil(stations[0].prices[.diesel], "49.9 below band dropped") } func testB7VariantsMapToDiesel() throws { // A station sells ONE diesel grade — test each variant individually. for (grade, price) in [("B7S", 141.9), ("B7P", 143.9), ("B10", 140.9)] { let json = """ {"stations":[{"id":"s1","name":"DIESEL MAP","brand":"X","address":"A","postcode":"L","lat":0,"lng":0,"prices":{"\(grade)":\(price)}}]} """ let stations = try FuelPriceProvider.decodeStations(from: Data(json.utf8)) XCTAssertEqual(stations[0].prices[.diesel], price, "\(grade) maps to diesel") } } func testLegacyPriceFallback() throws { let json = """ {"stations":[{"id":"s1","name":"LEGACY","brand":"X","address":"A","postcode":"L","lat":0,"lng":0,"price":136.5}]} """ let stations = try FuelPriceProvider.decodeStations(from: Data(json.utf8)) XCTAssertEqual(stations[0].prices[.e10], 136.5, "legacy price maps to E10") } func testLegacyGarbagePriceDropped() throws { let json = """ {"stations":[{"id":"s1","name":"LEGACY BAD","brand":"X","address":"A","postcode":"L","lat":0,"lng":0,"price":1589.0}]} """ let stations = try FuelPriceProvider.decodeStations(from: Data(json.utf8)) XCTAssertTrue(stations[0].prices.isEmpty, "garbage legacy price dropped") } func testRelayNameSanitizedAtDecode() throws { let json = """ {"stations":[{"id":"s1","name":"TESCO EXPRESS LEEDS","brand":"Tesco","address":"A","postcode":"L","lat":0,"lng":0,"prices":{"E10":137.9}}]} """ let stations = try FuelPriceProvider.decodeStations(from: Data(json.utf8)) XCTAssertEqual(stations[0].name, "Tesco Express Leeds") } } final class RAGTests: XCTestCase { func testGreenWithinOneAndHalf() { XCTAssertEqual(RAGRating.rating(price: 138.0, cheapest: 137.0), .green) XCTAssertEqual(RAGRating.rating(price: 138.5, cheapest: 137.0), .green, "exactly 1.5p is green") } func testAmberWithinFour() { XCTAssertEqual(RAGRating.rating(price: 141.0, cheapest: 137.0), .amber) XCTAssertEqual(RAGRating.rating(price: 141.0, cheapest: 137.0), .amber) XCTAssertEqual(RAGRating.rating(price: 141.0, cheapest: 137.0), .amber, "exactly 4p is amber") } func testRedBeyondFour() { XCTAssertEqual(RAGRating.rating(price: 141.1, cheapest: 137.0), .red) } } final class SortBaselineTests: XCTestCase { // Baseline = cheapest within the chosen radius (both Cheapest and Closest). // TOP badge goes to the cheapest station in the pool. func testCheapestFirstSorting() { let a = FuelStation(id: "a", name: "A", brand: "X", address: "", postcode: "", lat: 53.7, lng: -1.8, prices: [.e10: 140.0], priceUpdated: nil) let b = FuelStation(id: "b", name: "B", brand: "X", address: "", postcode: "", lat: 53.71, lng: -1.81, prices: [.e10: 137.0], priceUpdated: nil) let c = FuelStation(id: "c", name: "C", brand: "X", address: "", postcode: "", lat: 53.72, lng: -1.82, prices: [.e10: 139.0], priceUpdated: nil) let sorted = [a, b, c].sorted { $0.prices[.e10]! < $1.prices[.e10]! } XCTAssertEqual(sorted.map(\.id), ["b", "c", "a"]) } // Ties: stations at the same cheapest price are found (within 0.01p) and // sorted nearest-first from the reference location. func testTiedStations() { func station(_ id: String, _ lat: Double, _ lng: Double, _ price: Double) -> FuelStation { FuelStation(id: id, name: id, brand: "X", address: "", postcode: "", lat: lat, lng: lng, prices: [.e10: price], priceUpdated: nil) } let far = station("far", 53.72, -1.82, 137.0) let near = station("near", 53.71, -1.81, 137.0) let other = station("other", 53.70, -1.80, 141.0) let tied = FuelStore.tiedStations(in: [far, other, near], fuel: .e10, price: 137.0, fromLat: 53.7, lng: -1.8) XCTAssertEqual(tied.map(\.id), ["near", "far"], "tied stations sorted nearest-first") } // A price 0.005p away still counts as a tie; 0.05p away does not. func testTiedStationsTolerance() { func station(_ id: String, _ price: Double) -> FuelStation { FuelStation(id: id, name: id, brand: "X", address: "", postcode: "", lat: 53.7, lng: -1.8, prices: [.e10: price], priceUpdated: nil) } let a = station("a", 137.005) let b = station("b", 137.05) XCTAssertEqual(FuelStore.tiedStations(in: [a, b], fuel: .e10, price: 137.0, fromLat: 53.7, lng: -1.8).map(\.id), ["a"]) } } final class DistanceTests: XCTestCase { func testZeroDistance() { let s = FuelStation(id: "a", name: "A", brand: "X", address: "", postcode: "", lat: 53.7, lng: -1.8, prices: [:], priceUpdated: nil) XCTAssertEqual(s.distanceKM(to: 53.7, lng2: -1.8), 0, accuracy: 0.001) } func testKnownDistance() { // London (51.5074, -0.1278) → Manchester (53.4808, -2.2426) ≈ 262 km let s = FuelStation(id: "a", name: "A", brand: "X", address: "", postcode: "", lat: 51.5074, lng: -0.1278, prices: [:], priceUpdated: nil) XCTAssertEqual(s.distanceKM(to: 53.4808, lng2: -2.2426), 262, accuracy: 5) } func testMilesConversion() { // 5 miles ≈ 8.05 km (app uses × 1.60934) XCTAssertEqual(5 * 1.60934, 8.0467, accuracy: 0.001) // 262 km ≈ 162.8 miles (display uses × 0.621371) XCTAssertEqual(262 * 0.621371, 162.8, accuracy: 0.1) } } final class BrandTests: XCTestCase { private func station(brand: String) -> FuelStation { FuelStation(id: UUID().uuidString, name: "N", brand: brand, address: "", postcode: "", lat: 0, lng: 0, prices: [:], priceUpdated: nil) } func testKnownBrands() { XCTAssertEqual(station(brand: "SHELL LEEDS ROAD").brandImageName, "brand_shell") XCTAssertEqual(station(brand: "SAINSBURYS").brandImageName, "brand_sainsburys") XCTAssertEqual(station(brand: "MORRISONS").brandImageName, "brand_morrisons") XCTAssertEqual(station(brand: "TESCO EXPRESS").brandImageName, "brand_tesco") XCTAssertEqual(station(brand: "BP").brandImageName, "brand_bp") XCTAssertEqual(station(brand: "ESSO").brandImageName, "brand_esso") XCTAssertEqual(station(brand: "ASDA").brandImageName, "brand_asda") XCTAssertEqual(station(brand: "GULF").brandImageName, "brand_gulf") XCTAssertEqual(station(brand: "JET").brandImageName, "brand_jet") XCTAssertEqual(station(brand: "TEXACO").brandImageName, "brand_texaco") XCTAssertEqual(station(brand: "APPLEGREEN").brandImageName, "brand_applegreen") } func testUnknownBrand() { XCTAssertNil(station(brand: "WELCOME BREAK").brandImageName) XCTAssertNil(station(brand: "VALERO").brandImageName) XCTAssertNil(station(brand: "").brandImageName) } } final class FuelTypeLabelTests: XCTestCase { func testDisplayNames() { // E10/E5 grades are intentionally not part of user-facing labels. XCTAssertEqual(FuelType.e10.displayName, "Unleaded") XCTAssertEqual(FuelType.e5.displayName, "Premium") XCTAssertEqual(FuelType.diesel.displayName, "Diesel") } func testDistanceUnitConversion() { XCTAssertEqual(DistanceUnit.miles.toKM(1), 1.60934, accuracy: 0.00001) XCTAssertEqual(DistanceUnit.kilometers.toKM(5), 5) XCTAssertEqual(DistanceUnit.miles.fromKM(1.60934), 1, accuracy: 0.00001) XCTAssertEqual(DistanceUnit.kilometers.fromKM(2.5), 2.5) } func testDistanceUnitFormat() { XCTAssertEqual(DistanceUnit.miles.format(1.60934), "1.0 mi") XCTAssertEqual(DistanceUnit.kilometers.format(3.4), "3.4 km") } } final class FavouriteRefreshTests: XCTestCase { func testRefreshedFavouritesApplyFreshPrices() { let fav = FavouriteEntry(station: FuelStation(id: "s1", name: "OLD NAME", brand: "X", address: "", postcode: "", lat: 0, lng: 0, prices: [.e10: 140.0], priceUpdated: nil), fuel: .e10) let fresh = FuelStation(id: "s1", name: "Fresh Station", brand: "X", address: "", postcode: "", lat: 0, lng: 0, prices: [.e10: 132.9], priceUpdated: nil) let updated = FuelStore.refreshedFavourites([fav], from: [fresh]) XCTAssertEqual(updated.count, 1) XCTAssertEqual(updated[0].station.name, "Fresh Station") XCTAssertEqual(updated[0].station.prices[.e10], 132.9) XCTAssertEqual(updated[0].fuel, .e10, "fuel scoping survives refresh") } func testRefreshedFavouritesKeepUnmatchedSnapshot() { let fav = FavouriteEntry(station: FuelStation(id: "s1", name: "Cached", brand: "X", address: "", postcode: "", lat: 0, lng: 0, prices: [.e10: 140.0], priceUpdated: nil), fuel: .diesel) let updated = FuelStore.refreshedFavourites([fav], from: []) XCTAssertEqual(updated[0].station.name, "Cached", "unmatched favourite keeps its snapshot") XCTAssertEqual(updated[0].station.prices[.e10], 140.0) XCTAssertEqual(updated[0].fuel, .diesel, "fuel scoping survives unmatched refresh") } func testFavouriteEntryIDIsFuelScoped() { let station = FuelStation(id: "s1", name: "X", brand: "X", address: "", postcode: "", lat: 0, lng: 0, prices: [.e10: 140.0, .diesel: 150.0], priceUpdated: nil) let unleaded = FavouriteEntry(station: station, fuel: .e10) let diesel = FavouriteEntry(station: station, fuel: .diesel) XCTAssertNotEqual(unleaded.id, diesel.id, "same station favourited for two fuels is two distinct favourites") XCTAssertTrue(unleaded.id.hasSuffix("|s1")) XCTAssertTrue(diesel.id.hasPrefix("diesel|")) } } final class FavouriteReorderTests: XCTestCase { private func entry(_ id: String, _ fuel: FuelType) -> FavouriteEntry { FavouriteEntry(station: FuelStation(id: id, name: id, brand: "X", address: "", postcode: "", lat: 0, lng: 0, prices: [fuel: 140.0], priceUpdated: nil), fuel: fuel) } func testReorderMovesWithinFuelAndKeepsOthersRelativeOrder() { // e10: a, b, c | diesel: d let list = [entry("a", .e10), entry("b", .e10), entry("c", .e10), entry("d", .diesel)] // Move c (index 2) to the front (offset 0) of the e10 list. let moved = FuelStore.reorderedFavourites(list, fuel: .e10, fromOffsets: [2], toOffset: 0) XCTAssertEqual(moved.map(\.station.id), ["c", "a", "b", "d"], "c moves to front; diesel stays last") } func testReorderPreservesFuelBlockPosition() { // diesel first block, then e10 block: d, e | a, b, c let list = [entry("d", .diesel), entry("e", .diesel), entry("a", .e10), entry("b", .e10), entry("c", .e10)] // Move a (e10 index 0) to the e10 block end (offset 3 within e10). let moved = FuelStore.reorderedFavourites(list, fuel: .e10, fromOffsets: [0], toOffset: 3) XCTAssertEqual(moved.map(\.station.id), ["d", "e", "b", "c", "a"], "e10 block stays in place after diesel block") } func testReorderFirstBecomesSingleWidgetFavourite() { let list = [entry("x", .e10), entry("y", .e10), entry("z", .e10)] let moved = FuelStore.reorderedFavourites(list, fuel: .e10, fromOffsets: [2], toOffset: 0) XCTAssertEqual(moved.first?.station.id, "z", "first stored favourite = single-widget favourite") } func testReorderMultipleFuelsUntouched() { let list = [entry("a", .e10), entry("b", .e10), entry("d", .diesel), entry("p", .e5)] let moved = FuelStore.reorderedFavourites(list, fuel: .e10, fromOffsets: [0], toOffset: 2) XCTAssertEqual(moved.map(\.station.id), ["b", "a", "d", "p"], "e10 reorder leaves other fuels' relative order intact") } } final class AlertsFuelTests: XCTestCase { func testAlertsFuelRoundTrips() { FuelStore.saveAlertsFuel(.diesel) XCTAssertEqual(FuelStore.loadAlertsFuel(), .diesel, "alerts fuel persists independently") } func testAlertsFuelDefaultsToUnleaded() { // Fresh state (test isolation) defaults to Unleaded like the app's // other fuel selections. FuelStore.saveAlertsFuel(.e5) FuelStore.saveAlertsFuel(.e10) XCTAssertEqual(FuelStore.loadAlertsFuel(), .e10) } } final class AlertRadiusFollowTests: XCTestCase { func testAlertRadiusOptionsAreMileFriendly() { // 1–2 city, 3 town, 5 default, 8 motorway — the geofence ceiling. XCTAssertEqual(FuelStore.alertRadiusOptions, [1, 2, 3, 5, 8]) } func testLegacyRadiusClampsToCap() { // Old options went to 20 km; stored values must not exceed the 8-mi cap. FuelStore.saveAlertsRadius(30) XCTAssertEqual(FuelStore.loadAlertsRadius(), FuelStore.alertFollowCapKM, accuracy: 0.001) FuelStore.saveAlertsRadius(5) // still inside the cap — untouched XCTAssertEqual(FuelStore.loadAlertsRadius(), 5, accuracy: 0.001) } func testFollowSearchRoundTrips() { FuelStore.saveAlertsFollowsSearch(true) XCTAssertTrue(FuelStore.loadAlertsFollowsSearch()) FuelStore.saveAlertsFollowsSearch(false) XCTAssertFalse(FuelStore.loadAlertsFollowsSearch()) FuelStore.saveLiveActivityFollowsSearch(true) XCTAssertTrue(FuelStore.loadLiveActivityFollowsSearch()) FuelStore.saveLiveActivityFollowsSearch(false) XCTAssertFalse(FuelStore.loadLiveActivityFollowsSearch()) } func testEffectiveRadiusManualWhenNotFollowing() { let eff = FuelStore.effectiveAlertsRadiusKM(followsSearch: false, manualKM: 3) XCTAssertEqual(eff, 3, "manual radius passes through when not following") } func testEffectiveRadiusFollowsSearchCapped() { // 15-mile search must cap at the 8-mile geofence ceiling. FuelStore.saveDistanceUnit(.miles) FuelStore.saveStationLimit(15) let eff = FuelStore.effectiveAlertsRadiusKM(followsSearch: true, manualKM: 3) XCTAssertEqual(eff, FuelStore.alertFollowCapKM, accuracy: 0.001) } func testEffectiveRadiusFollowsSmallSearch() { // 5-mile search follows exactly (8.05 km), under the cap. FuelStore.saveDistanceUnit(.miles) FuelStore.saveStationLimit(5) let eff = FuelStore.effectiveAlertsRadiusKM(followsSearch: true, manualKM: 3) XCTAssertEqual(eff, 5 * 1.60934, accuracy: 0.001) } } final class DistanceLabelTests: XCTestCase { func testUnitLabelPluralizesMiles() { XCTAssertEqual(DistanceUnit.miles.label(for: 1), "mile") XCTAssertEqual(DistanceUnit.miles.label(for: 2), "miles") XCTAssertEqual(DistanceUnit.miles.label(for: 5), "miles") XCTAssertEqual(DistanceUnit.kilometers.label(for: 1), "km") XCTAssertEqual(DistanceUnit.kilometers.label(for: 8), "km") } func testDisplayMilesShowsTrueDistance() { // Picker labels must match what the option really means. XCTAssertEqual(DistanceUnit.miles.displayMiles(5), 5) XCTAssertEqual(DistanceUnit.miles.displayMiles(15), 15) XCTAssertEqual(DistanceUnit.kilometers.displayMiles(5), 8) // 5 mi = 8.05 km XCTAssertEqual(DistanceUnit.kilometers.displayMiles(10), 16) // 10 mi = 16.1 km XCTAssertEqual(DistanceUnit.kilometers.displayMiles(15), 24) // 15 mi = 24.1 km } } final class SiriCheapestLookupTests: XCTestCase { private func station(_ id: String, lat: Double, lng: Double, _ prices: [FuelType: Double]) -> FuelStation { FuelStation(id: id, name: id, brand: "X", address: "", postcode: "", lat: lat, lng: lng, prices: prices, priceUpdated: nil) } func testCheapestPicksLowestPriceNotNearest() { // Near station is pricier; far station is cheaper — price must win. let stations = [ station("near", lat: 53.0, lng: -1.0, [.e10: 145.9]), station("far", lat: 53.5, lng: -1.5, [.e10: 139.9]), ] let result = SiriCheapestLookup.cheapest(in: stations, fuel: .e10, fromLat: 53.01, lng: -1.01) XCTAssertEqual(result?.id, "far", "cheapest by price, not by distance") } func testCheapestTieBreaksByDistance() { let stations = [ station("near", lat: 53.0, lng: -1.0, [.e10: 140.0]), station("far", lat: 53.9, lng: -1.9, [.e10: 140.0]), ] let result = SiriCheapestLookup.cheapest(in: stations, fuel: .e10, fromLat: 53.01, lng: -1.01) XCTAssertEqual(result?.id, "near", "equal prices resolve to the nearest station") } func testCheapestSkipsStationsWithoutThatFuel() { let stations = [ station("noDiesel", lat: 53.0, lng: -1.0, [.e10: 139.9]), station("sellsDiesel", lat: 53.5, lng: -1.5, [.diesel: 149.9]), ] let result = SiriCheapestLookup.cheapest(in: stations, fuel: .diesel, fromLat: 53.01, lng: -1.01) XCTAssertEqual(result?.id, "sellsDiesel", "stations without the fuel are skipped") } func testCheapestReturnsNilWhenNoStationSellsFuel() { let stations = [station("a", lat: 53.0, lng: -1.0, [.e10: 139.9])] XCTAssertNil(SiriCheapestLookup.cheapest(in: stations, fuel: .diesel, fromLat: 53.0, lng: -1.0)) } func testCheapestReturnsNilForEmptyInput() { XCTAssertNil(SiriCheapestLookup.cheapest(in: [], fuel: .e10, fromLat: 53.0, lng: -1.0)) } func testFreshnessLabelUsesDataUpdatedStamp() { let label = SiriCheapestLookup.freshnessLabel( updated: "2026-08-14T10:31:36.000Z", lastRefresh: nil ) XCTAssertTrue(label.hasPrefix("as of "), "stamp must be prefixed for the dialog") XCTAssertFalse(label.contains("10:31"), "stamp is converted to the user's local time, not raw UTC") } func testFreshnessLabelFallsBackToLastRefresh() { let label = SiriCheapestLookup.freshnessLabel(updated: nil, lastRefresh: Date(timeIntervalSince1970: 0)) XCTAssertTrue(label.hasPrefix("as of "), "local refresh date is used when no GOV.UK stamp exists") } func testFreshnessLabelEmptyWithoutAnyDate() { XCTAssertEqual(SiriCheapestLookup.freshnessLabel(updated: nil, lastRefresh: nil), "") } func testFreshnessLabelToleratesPlainISODate() { // Some relays emit no fractional seconds. let label = SiriCheapestLookup.freshnessLabel( updated: "2026-08-14T10:31:36Z", lastRefresh: nil ) XCTAssertTrue(label.hasPrefix("as of "), "plain ISO 8601 still parses") } }