@@ -43,7 +43,8 @@ void getPriser_shouldReturnParsedPrices_whenMockDataIsProvided() {
4343 [{"SEK_per_kWh":0.12229,"EUR_per_kWh":0.01112,"EXR":10.997148,"time_start":"2025-09-04T00:00:00+02:00","time_end":"2025-09-04T01:00:00+02:00"},{"SEK_per_kWh":0.09886,"EUR_per_kWh":0.00899,"EXR":10.997148,"time_start":"2025-09-04T01:00:00+02:00","time_end":"2025-09-04T02:00:00+02:00"},{"SEK_per_kWh":0.09095,"EUR_per_kWh":0.00827,"EXR":10.997148,"time_start":"2025-09-04T02:00:00+02:00","time_end":"2025-09-04T03:00:00+02:00"},{"SEK_per_kWh":0.04201,"EUR_per_kWh":0.00382,"EXR":10.997148,"time_start":"2025-09-04T03:00:00+02:00","time_end":"2025-09-04T04:00:00+02:00"},{"SEK_per_kWh":0.04146,"EUR_per_kWh":0.00377,"EXR":10.997148,"time_start":"2025-09-04T04:00:00+02:00","time_end":"2025-09-04T05:00:00+02:00"},{"SEK_per_kWh":0.04465,"EUR_per_kWh":0.00406,"EXR":10.997148,"time_start":"2025-09-04T05:00:00+02:00","time_end":"2025-09-04T06:00:00+02:00"},{"SEK_per_kWh":0.32991,"EUR_per_kWh":0.03,"EXR":10.997148,"time_start":"2025-09-04T06:00:00+02:00","time_end":"2025-09-04T07:00:00+02:00"},{"SEK_per_kWh":0.47123,"EUR_per_kWh":0.04285,"EXR":10.997148,"time_start":"2025-09-04T07:00:00+02:00","time_end":"2025-09-04T08:00:00+02:00"},{"SEK_per_kWh":0.68182,"EUR_per_kWh":0.062,"EXR":10.997148,"time_start":"2025-09-04T08:00:00+02:00","time_end":"2025-09-04T09:00:00+02:00"},{"SEK_per_kWh":0.4125,"EUR_per_kWh":0.03751,"EXR":10.997148,"time_start":"2025-09-04T09:00:00+02:00","time_end":"2025-09-04T10:00:00+02:00"},{"SEK_per_kWh":0.29571,"EUR_per_kWh":0.02689,"EXR":10.997148,"time_start":"2025-09-04T10:00:00+02:00","time_end":"2025-09-04T11:00:00+02:00"},{"SEK_per_kWh":0.06136,"EUR_per_kWh":0.00558,"EXR":10.997148,"time_start":"2025-09-04T11:00:00+02:00","time_end":"2025-09-04T12:00:00+02:00"},{"SEK_per_kWh":0.03662,"EUR_per_kWh":0.00333,"EXR":10.997148,"time_start":"2025-09-04T12:00:00+02:00","time_end":"2025-09-04T13:00:00+02:00"},{"SEK_per_kWh":0.0375,"EUR_per_kWh":0.00341,"EXR":10.997148,"time_start":"2025-09-04T13:00:00+02:00","time_end":"2025-09-04T14:00:00+02:00"},{"SEK_per_kWh":0.26822,"EUR_per_kWh":0.02439,"EXR":10.997148,"time_start":"2025-09-04T14:00:00+02:00","time_end":"2025-09-04T15:00:00+02:00"},{"SEK_per_kWh":0.30429,"EUR_per_kWh":0.02767,"EXR":10.997148,"time_start":"2025-09-04T15:00:00+02:00","time_end":"2025-09-04T16:00:00+02:00"},{"SEK_per_kWh":0.36675,"EUR_per_kWh":0.03335,"EXR":10.997148,"time_start":"2025-09-04T16:00:00+02:00","time_end":"2025-09-04T17:00:00+02:00"},{"SEK_per_kWh":0.58296,"EUR_per_kWh":0.05301,"EXR":10.997148,"time_start":"2025-09-04T17:00:00+02:00","time_end":"2025-09-04T18:00:00+02:00"},{"SEK_per_kWh":0.92145,"EUR_per_kWh":0.08379,"EXR":10.997148,"time_start":"2025-09-04T18:00:00+02:00","time_end":"2025-09-04T19:00:00+02:00"},{"SEK_per_kWh":1.5054,"EUR_per_kWh":0.13689,"EXR":10.997148,"time_start":"2025-09-04T19:00:00+02:00","time_end":"2025-09-04T20:00:00+02:00"},{"SEK_per_kWh":1.00888,"EUR_per_kWh":0.09174,"EXR":10.997148,"time_start":"2025-09-04T20:00:00+02:00","time_end":"2025-09-04T21:00:00+02:00"},{"SEK_per_kWh":0.63179,"EUR_per_kWh":0.05745,"EXR":10.997148,"time_start":"2025-09-04T21:00:00+02:00","time_end":"2025-09-04T22:00:00+02:00"},{"SEK_per_kWh":0.56382,"EUR_per_kWh":0.05127,"EXR":10.997148,"time_start":"2025-09-04T22:00:00+02:00","time_end":"2025-09-04T23:00:00+02:00"},{"SEK_per_kWh":0.52951,"EUR_per_kWh":0.04815,"EXR":10.997148,"time_start":"2025-09-04T23:00:00+02:00","time_end":"2025-09-05T00:00:00+02:00"}]""" ;
4444
4545 // 2. Set the mock response using the static method.
46- ElpriserAPI .setMockResponse (fakeJson );
46+ LocalDate today = LocalDate .of (2025 , 9 , 4 );
47+ ElpriserAPI .setMockResponseForDate (today ,fakeJson );
4748
4849 // 3. Create an instance of the class as a student would.
4950 ElpriserAPI api = new ElpriserAPI (false ); // Disable caching for predictable tests
@@ -109,7 +110,8 @@ void displayMeanPrice_withValidData() {
109110 {"SEK_per_kWh":0.30,"EUR_per_kWh":0.03,"EXR":10.0,"time_start":"2025-09-04T02:00:00+02:00","time_end":"2025-09-04T03:00:00+02:00"},
110111 {"SEK_per_kWh":0.40,"EUR_per_kWh":0.04,"EXR":10.0,"time_start":"2025-09-04T03:00:00+02:00","time_end":"2025-09-04T04:00:00+02:00"}]""" ;
111112
112- ElpriserAPI .setMockResponse (mockJson );
113+ LocalDate today = LocalDate .of (2025 , 9 , 4 );
114+ ElpriserAPI .setMockResponseForDate (today ,mockJson );
113115
114116 Main .main (new String []{"--zone" , "SE3" , "--date" , "2025-09-04" });
115117
@@ -127,7 +129,8 @@ void displayMinMaxPrices_withValidData() {
127129 {"SEK_per_kWh":0.80,"EUR_per_kWh":0.08,"EXR":10.0,"time_start":"2025-09-04T02:00:00+02:00","time_end":"2025-09-04T03:00:00+02:00"},
128130 {"SEK_per_kWh":0.30,"EUR_per_kWh":0.03,"EXR":10.0,"time_start":"2025-09-04T03:00:00+02:00","time_end":"2025-09-04T04:00:00+02:00"}]""" ;
129131
130- ElpriserAPI .setMockResponse (mockJson );
132+ LocalDate today = LocalDate .of (2025 , 9 , 4 );
133+ ElpriserAPI .setMockResponseForDate (today ,mockJson );
131134
132135 Main .main (new String []{"--zone" , "SE1" , "--date" , "2025-09-04" });
133136
@@ -144,24 +147,36 @@ void displayMinMaxPrices_withValidData() {
144147
145148 @ Test
146149 void displaySortedPrices_whenRequested () {
147- String mockJson = """
148- [{"SEK_per_kWh":0.30,"EUR_per_kWh":0.03,"EXR":10.0,"time_start":"2025-09-04T00:00:00+02:00","time_end":"2025-09-04T01:00:00+02:00"},
149- {"SEK_per_kWh":0.10,"EUR_per_kWh":0.01,"EXR":10.0,"time_start":"2025-09-04T01:00:00+02:00","time_end":"2025-09-04T02:00:00+02:00"},
150- {"SEK_per_kWh":0.20,"EUR_per_kWh":0.02,"EXR":10.0,"time_start":"2025-09-04T02:00:00+02:00","time_end":"2025-09-04T03:00:00+02:00"},
151- {"SEK_per_kWh":0.10,"EUR_per_kWh":0.01,"EXR":10.0,"time_start":"2025-09-04T03:00:00+02:00","time_end":"2025-09-04T04:00:00+02:00"}]""" ;
150+ // This test ensures charging window can span days when next day data exists
151+ LocalDate today = LocalDate .of (2025 , 9 , 4 );
152+ LocalDate tomorrow = today .plusDays (1 );
152153
153- ElpriserAPI .setMockResponse (mockJson );
154+ String mockJsonToday = """
155+ [{"SEK_per_kWh":0.30,"EUR_per_kWh":0.03,"EXR":10.0,"time_start":"2025-09-04T20:00:00+02:00","time_end":"2025-09-04T21:00:00+02:00"},
156+ {"SEK_per_kWh":0.10,"EUR_per_kWh":0.01,"EXR":10.0,"time_start":"2025-09-04T21:00:00+02:00","time_end":"2025-09-04T22:00:00+02:00"},
157+ {"SEK_per_kWh":0.20,"EUR_per_kWh":0.02,"EXR":10.0,"time_start":"2025-09-04T22:00:00+02:00","time_end":"2025-09-04T23:00:00+02:00"},
158+ {"SEK_per_kWh":0.10,"EUR_per_kWh":0.01,"EXR":10.0,"time_start":"2025-09-04T23:00:00+02:00","time_end":"2025-09-04T00:00:00+02:00"}]""" ;
159+ String mockJsonTomorrow = """
160+ [{"SEK_per_kWh":0.10,"EUR_per_kWh":0.01,"EXR":10.0,"time_start":"2025-09-05T00:00:00+02:00","time_end":"2025-09-05T01:00:00+02:00"},
161+ {"SEK_per_kWh":0.15,"EUR_per_kWh":0.015,"EXR":10.0,"time_start":"2025-09-05T01:00:00+02:00","time_end":"2025-09-05T02:00:00+02:00"},
162+ {"SEK_per_kWh":0.15,"EUR_per_kWh":0.015,"EXR":10.0,"time_start":"2025-09-05T02:00:00+02:00","time_end":"2025-09-05T03:00:00+02:00"}]""" ;
163+
164+ ElpriserAPI .setMockResponseForDate (today , mockJsonToday );
165+ ElpriserAPI .setMockResponseForDate (tomorrow , mockJsonTomorrow );
154166
155167 Main .main (new String []{"--zone" , "SE2" , "--date" , "2025-09-04" , "--sorted" });
156168
157169 String output = bos .toString ();
158170
159171 // Expected sorted output (ascending by price)
160172 List <String > expectedOrder = List .of (
161- "01-02 10,00 öre" ,
162- "03-04 10,00 öre" ,
163- "02-03 20,00 öre" ,
164- "00-01 30,00 öre"
173+ "21-22 10,00 öre" ,
174+ "23-00 10,00 öre" ,
175+ "00-01 10,00 öre" ,
176+ "01-02 15,00 öre" ,
177+ "02-03 15,00 öre" ,
178+ "22-23 20,00 öre" ,
179+ "20-21 30,00 öre"
165180 );
166181
167182 // Extract actual lines that match the pattern
@@ -183,7 +198,9 @@ void findOptimalCharging2Hours() {
183198 {"SEK_per_kWh":0.15,"EUR_per_kWh":0.015,"EXR":10.0,"time_start":"2025-09-04T03:00:00+02:00","time_end":"2025-09-04T04:00:00+02:00"},
184199 {"SEK_per_kWh":0.30,"EUR_per_kWh":0.03,"EXR":10.0,"time_start":"2025-09-04T04:00:00+02:00","time_end":"2025-09-04T05:00:00+02:00"}]""" ;
185200
186- ElpriserAPI .setMockResponse (mockJson );
201+ LocalDate today = LocalDate .of (2025 , 9 , 4 );
202+
203+ ElpriserAPI .setMockResponseForDate (today , mockJson );
187204
188205 Main .main (new String []{"--zone" , "SE3" , "--date" , "2025-09-04" , "--charging" , "2h" });
189206
@@ -204,7 +221,9 @@ void findOptimalCharging4Hours() {
204221 {"SEK_per_kWh":0.20,"EUR_per_kWh":0.02,"EXR":10.0,"time_start":"2025-09-04T04:00:00+02:00","time_end":"2025-09-04T05:00:00+02:00"},
205222 {"SEK_per_kWh":0.30,"EUR_per_kWh":0.03,"EXR":10.0,"time_start":"2025-09-04T05:00:00+02:00","time_end":"2025-09-04T06:00:00+02:00"}]""" ;
206223
207- ElpriserAPI .setMockResponse (mockJson );
224+ LocalDate today = LocalDate .of (2025 , 9 , 4 );
225+
226+ ElpriserAPI .setMockResponseForDate (today , mockJson );
208227
209228 Main .main (new String []{"--zone" , "SE1" , "--date" , "2025-09-04" , "--charging" , "4h" });
210229
@@ -223,7 +242,9 @@ void chargingWindowDoesNotUseNextDay_whenNextDayUnavailable() {
223242 [{"SEK_per_kWh":0.20,"EUR_per_kWh":0.02,"EXR":10.0,"time_start":"2025-09-04T00:00:00+02:00","time_end":"2025-09-04T01:00:00+02:00"},
224243 {"SEK_per_kWh":0.10,"EUR_per_kWh":0.01,"EXR":10.0,"time_start":"2025-09-04T01:00:00+02:00","time_end":"2025-09-04T02:00:00+02:00"},
225244 {"SEK_per_kWh":0.15,"EUR_per_kWh":0.015,"EXR":10.0,"time_start":"2025-09-04T02:00:00+02:00","time_end":"2025-09-04T03:00:00+02:00"}]""" ;
226- ElpriserAPI .setMockResponse (mockJsonToday );
245+ LocalDate today = LocalDate .of (2025 , 9 , 4 );
246+ ElpriserAPI .setMockResponseForDate (today ,mockJsonToday );
247+
227248 Main .main (new String []{"--zone" , "SE3" , "--date" , "2025-09-04" , "--charging" , "2h" });
228249 String output = bos .toString ();
229250 // Best 2h window should be 01-03 (0.10 + 0.15)
@@ -235,20 +256,28 @@ void chargingWindowDoesNotUseNextDay_whenNextDayUnavailable() {
235256 void findOptimalCharging8Hours () {
236257 // Create mock data with 12 hours to allow for 8-hour window
237258 StringBuilder jsonBuilder = new StringBuilder ("[" );
238- double [] prices = {0.50 , 0.10 , 0.05 , 0.15 , 0.08 , 0.12 , 0.06 , 0.09 , 0.25 , 0.30 , 0.35 , 0.40 };
259+ double [] prices = {0.50 , 0.10 , 0.05 , 0.15 , 0.08 , 0.12 , 0.06 , 0.09 , 0.25 , 0.30 , 0.35 , 0.40 , 0.50 , 0.10 , 0.05 , 0.15 , 0.08 , 0.12 , 0.06 , 0.09 , 0.25 , 0.30 , 0.35 , 0.40 };
239260
240261 for (int i = 0 ; i < prices .length ; i ++) {
241262 if (i > 0 ) jsonBuilder .append ("," );
242263 jsonBuilder .append (String .format (
243264 Locale .US ,
244265 """
245266 {"SEK_per_kWh":%.2f,"EUR_per_kWh":%.3f,"EXR":10.0,"time_start":"2025-09-04T%02d:00:00+02:00","time_end":"2025-09-04T%02d:00:00+02:00"}""" ,
246- prices [i ], prices [i ] / 10 , i , i + 1
267+ prices [i ], prices [i ] / 10 , i , ( i + 1 ) % 24
247268 ));
248269 }
249270 jsonBuilder .append ("]" );
250271
251- ElpriserAPI .setMockResponse (jsonBuilder .toString ());
272+ LocalDate today = LocalDate .of (2025 , 9 , 4 );
273+ ElpriserAPI .setMockResponseForDate (today , jsonBuilder .toString ());
274+
275+ LocalDate tomorrow = today .plusDays (1 );
276+ String mockJsonTomorrow = """
277+ [{"SEK_per_kWh":0.1,"EUR_per_kWh":0.01,"EXR":10.0,"time_start":"2025-09-05T00:00:00+02:00","time_end":"2025-09-05T01:00:00+02:00"},
278+ {"SEK_per_kWh":0.15,"EUR_per_kWh":0.015,"EXR":10.0,"time_start":"2025-09-05T01:00:00+02:00","time_end":"2025-09-05T02:00:00+02:00"},
279+ {"SEK_per_kWh":0.15,"EUR_per_kWh":0.015,"EXR":10.0,"time_start":"2025-09-05T02:00:00+02:00","time_end":"2025-09-05T03:00:00+02:00"}]""" ;
280+ ElpriserAPI .setMockResponseForDate (tomorrow , mockJsonTomorrow );
252281
253282 Main .main (new String []{"--zone" , "SE4" , "--date" , "2025-09-04" , "--charging" , "8h" });
254283
@@ -359,66 +388,63 @@ void chargingWindowSpansToNextDay_whenCheapestCrossesMidnight() {
359388 }
360389
361390 @ Test
362- public void testHourlyMinMaxPrices () {
363- List <Double > quarterHourPrices = new ArrayList <>();
364-
365- // Simulate 96 prices: 24 hours, each with 4 quarter-hour prices
366- for (int i = 0 ; i < 96 ; i ++) {
367- quarterHourPrices .add ((double ) (i % 24 )); // repeating hourly pattern
368- }
391+ void testHourlyMinMaxPrices_with96Entries () {
392+ // --- ARRANGE ---
393+ LocalDate today = LocalDate .of (2025 , 9 , 4 );
394+ StringBuilder jsonBuilder = new StringBuilder ("[" );
369395
370- // Expected hourly averages
371- List <Double > hourlyAverages = new ArrayList <>();
372- for (int i = 0 ; i < 24 ; i ++) {
373- double sum = 0 ;
374- for (int j = 0 ; j < 4 ; j ++) {
375- sum += quarterHourPrices .get (i * 4 + j );
396+ for (int hour = 0 ; hour < 24 ; hour ++) {
397+ for (int quarter = 0 ; quarter < 4 ; quarter ++) {
398+ if (hour > 0 || quarter > 0 ) {
399+ jsonBuilder .append ("," );
400+ }
401+ double price = (hour * 0.1 ) + (quarter * 0.01 ) + 0.10 ;
402+ String time_start = String .format ("2025-09-04T%02d:%02d:00+02:00" , hour , quarter * 15 );
403+ String time_end = String .format ("2025-09-04T%02d:%02d:00+02:00" , hour , (quarter + 1 ) * 15 );
404+ if (quarter == 3 ) { // Handle end of hour
405+ time_end = String .format ("2025-09-04T%02d:00:00+02:00" , (hour + 1 ) % 24 );
406+ }
407+
408+ jsonBuilder .append (String .format (Locale .US ,
409+ """
410+ {"SEK_per_kWh":%.4f,"EUR_per_kWh":0.01,"EXR":10.0,"time_start":"%s","time_end":"%s"}""" ,
411+ price , time_start , time_end ));
376412 }
377- hourlyAverages .add (sum / 4.0 );
378413 }
414+ jsonBuilder .append ("]" );
415+ ElpriserAPI .setMockResponseForDate (today , jsonBuilder .toString ());
379416
380- double expectedMin = Collections . min ( hourlyAverages );
381- double expectedMax = Collections . max ( hourlyAverages );
417+ // --- ACT ---
418+ Main . main ( new String []{ "--zone" , "SE3" , "--date" , "2025-09-04" } );
382419
383- // Call your method under test
384- PriceRange result = PriceCalculator .calculateHourlyMinMax (quarterHourPrices );
420+ // --- ASSERT ---
421+ String output = bos .toString ();
422+ assertThat (output ).containsIgnoringCase ("lägsta pris" );
423+ assertThat (output ).containsIgnoringCase ("högsta pris" );
424+ assertThat (output ).containsIgnoringCase ("medelpris" );
385425
386- assertThat (result .getMin ()).isCloseTo (expectedMin , within (0.001 ));
387- assertThat (result .getMax ()).isCloseTo (expectedMax , within (0.001 ));
426+ // Expected Min: Hour 0 -> avg(0.10, 0.11, 0.12, 0.13) = 0.115 SEK/kWh = 11,50 öre
427+ // Expected Max: Hour 23 -> avg(2.40, 2.41, 2.42, 2.43) = 2.415 SEK/kWh = 241,50 öre
428+ assertThat (output ).contains ("00-01" ); // Cheapest hour
429+ assertThat (output ).contains ("23-00" ); // Most expensive hour
430+ assertThat (output ).contains (formatOre (0.115 ));
431+ assertThat (output ).contains (formatOre (2.415 ));
432+
433+ // Calculate overall average for the day
434+ double totalSum = 0 ;
435+ for (int hour = 0 ; hour < 24 ; hour ++) {
436+ for (int quarter = 0 ; quarter < 4 ; quarter ++) {
437+ totalSum += (hour * 0.1 ) + (quarter * 0.01 ) + 0.10 ;
438+ }
439+ }
440+ double expectedMean = totalSum / 96 ;
441+ assertThat (output ).contains ("Medelpris: " + formatOre (expectedMean ) + " öre" );
388442 }
389443
390444 private String formatOre (double sekPerKWh ) {
391445 double ore = sekPerKWh * 100.0 ;
392- DecimalFormatSymbols symbols = DecimalFormatSymbols . getInstance ( Locale . of ("sv" , "SE" ));
446+ DecimalFormatSymbols symbols = new DecimalFormatSymbols ( new Locale ("sv" , "SE" ));
393447 DecimalFormat df = new DecimalFormat ("0.00" , symbols );
394448 return df .format (ore );
395449 }
396- }
397- class PriceRange {
398- private final double min ;
399- private final double max ;
400-
401- public PriceRange (double min , double max ) {
402- this .min = min ;
403- this .max = max ;
404- }
405-
406- public double getMin () { return min ; }
407- public double getMax () { return max ; }
408- }
409-
410- class PriceCalculator {
411- public static PriceRange calculateHourlyMinMax (List <Double > quarterHourPrices ) {
412- List <Double > hourlyAverages = new ArrayList <>();
413- for (int i = 0 ; i < 24 ; i ++) {
414- double sum = 0 ;
415- for (int j = 0 ; j < 4 ; j ++) {
416- sum += quarterHourPrices .get (i * 4 + j );
417- }
418- hourlyAverages .add (sum / 4.0 );
419- }
420- double min = Collections .min (hourlyAverages );
421- double max = Collections .max (hourlyAverages );
422- return new PriceRange (min , max );
423- }
424450}
0 commit comments