@@ -0,0 +1,221 @@
import XCTest
@ testable import FuelBoardShared
// F u e l B o a r d s h a r e d - l o g i c t e s t s . R u n f r o m F u e l B o a r d T e s t s / w i t h ` s w i f t t e s t ` .
// C o v e r s t h e p u r e l o g i c t h a t d r i v e s t h e a p p + w i d g e t : t i t l e s a n i t i z e r , p r i c e
// g u a r d , R A G , s o r t i n g b a s e l i n e s , d i s t a n c e , b r a n d n o r m a l i z a t i o n , f u e l l a b e l s .
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 1 AA " , " 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 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 s t a t i o n s e l l s O N E d i e s e l g r a d e — t e s t e a c h v a r i a n t i n d i v i d u a l l y .
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 {
// B a s e l i n e = c h e a p e s t w i t h i n t h e c h o s e n r a d i u s ( b o t h C h e a p e s t a n d C l o s e s t ) .
// T O P b a d g e g o e s t o t h e c h e a p e s t s t a t i o n i n t h e p o o l .
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 " ] )
}
}
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 ( ) {
// L o n d o n ( 5 1 . 5 0 7 4 , - 0 . 1 2 7 8 ) → M a n c h e s t e r ( 5 3 . 4 8 0 8 , - 2 . 2 4 2 6 ) ≈ 2 6 2 k m
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 m i l e s ≈ 8 . 0 5 k m ( a p p u s e s × 1 . 6 0 9 3 4 )
XCTAssertEqual ( 5 * 1.60934 , 8.0467 , accuracy : 0.001 )
// 2 6 2 k m ≈ 1 6 2 . 8 m i l e s ( d i s p l a y u s e s × 0 . 6 2 1 3 7 1 )
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 ( ) {
XCTAssertEqual ( FuelType . e10 . displayName , " Unleaded (E10) " )
XCTAssertEqual ( FuelType . e5 . displayName , " Premium (E5) " )
XCTAssertEqual ( FuelType . diesel . displayName , " Diesel " )
}
}
final class FavouriteRefreshTests : XCTestCase {
func testRefreshedFavouritesApplyFreshPrices ( ) {
let fav = FuelStation ( id : " s1 " , name : " OLD NAME " , brand : " X " , address : " " , postcode : " " , lat : 0 , lng : 0 , prices : [ . e10 : 140.0 ] , priceUpdated : nil )
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 ] . name , " Fresh Station " )
XCTAssertEqual ( updated [ 0 ] . prices [ . e10 ] , 132.9 )
}
func testRefreshedFavouritesKeepUnmatchedSnapshot ( ) {
let fav = FuelStation ( id : " s1 " , name : " Cached " , brand : " X " , address : " " , postcode : " " , lat : 0 , lng : 0 , prices : [ . e10 : 140.0 ] , priceUpdated : nil )
let updated = FuelStore . refreshedFavourites ( [ fav ] , from : [ ] )
XCTAssertEqual ( updated [ 0 ] . name , " Cached " , " unmatched favourite keeps its snapshot " )
XCTAssertEqual ( updated [ 0 ] . prices [ . e10 ] , 140.0 )
}
}