11package org .example ;
22
3+ import com .example .api .ElpriserAPI ;
4+ import org .junit .jupiter .api .AfterEach ;
5+ import org .junit .jupiter .api .BeforeEach ;
36import org .junit .jupiter .api .Test ;
47
8+ import java .io .ByteArrayOutputStream ;
9+ import java .io .PrintStream ;
10+ import java .time .LocalDate ;
11+ import java .util .List ;
12+
513import static org .assertj .core .api .Assertions .assertThat ;
14+ import static org .junit .jupiter .api .Assertions .assertNotNull ;
15+ import static org .junit .jupiter .api .Assertions .assertTrue ;
616
717class MainTest {
18+ PrintStream originalOut ;
19+ ByteArrayOutputStream bos ;
20+
21+ @ BeforeEach
22+ void setup () {
23+ originalOut = System .out ;
24+ bos = new ByteArrayOutputStream ();
25+ System .setOut (new PrintStream (bos ));
26+ }
27+
28+ @ AfterEach
29+ void tearDown () {
30+ System .setOut (originalOut );
31+ ElpriserAPI .clearMockResponse ();
32+ }
33+
34+ @ Test
35+ void getPriser_shouldReturnParsedPrices_whenMockDataIsProvided () {
36+ // --- ARRANGE ---
37+ // 1. Define your fake JSON data for this specific test
38+ String fakeJson = """
39+ [{"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"}]""" ;
40+
41+ // 2. Set the mock response using the static method.
42+ ElpriserAPI .setMockResponse (fakeJson );
43+
44+ // 3. Create an instance of the class as a student would.
45+ ElpriserAPI api = new ElpriserAPI (false ); // Disable caching for predictable tests
46+
47+ // --- ACT ---
48+ // This call will NOT make a network request. It will use `fakeJson`.
49+ List <ElpriserAPI .Elpris > priser = api .getPriser ("2025-09-04" , ElpriserAPI .Prisklass .SE3 );
50+
51+ // --- ASSERT ---
52+ assertThat (priser )
53+ .isNotNull ()
54+ .hasSize (24 )
55+ .satisfies (list -> {
56+ assertThat (list .get (0 ).sekPerKWh ()).isEqualTo (0.12229 );
57+ assertThat (list .get (1 ).sekPerKWh ()).isEqualTo (0.09886 );
58+ });
59+ }
60+
61+ @ Test
62+ void getPriser_shouldReturnEmptyList_whenMockDataIsNull () {
63+ ElpriserAPI .setMockResponse (null );
64+ ElpriserAPI api = new ElpriserAPI (false );
65+
66+ List <ElpriserAPI .Elpris > priser = api .getPriser (LocalDate .now (), ElpriserAPI .Prisklass .SE1 );
67+
68+ assertNotNull (priser );
69+ assertTrue (priser .isEmpty ());
70+ }
71+
72+ @ Test
73+ void showHelp_whenNoArguments () {
74+ Main .main (new String []{});
75+
76+ String output = bos .toString ();
77+ assertThat (output ).containsIgnoringCase ("usage" );
78+ assertThat (output ).containsIgnoringCase ("zone" );
79+ assertThat (output ).containsIgnoringCase ("date" );
80+ }
81+
82+ @ Test
83+ void showHelp_withHelpFlag () {
84+ Main .main (new String []{"--help" });
85+
86+ String output = bos .toString ();
87+ assertThat (output ).containsIgnoringCase ("electricity price optimizer" );
88+ assertThat (output ).containsIgnoringCase ("--zone" );
89+ assertThat (output ).containsIgnoringCase ("--date" );
90+ }
91+
92+ @ Test
93+ void displayMeanPrice_withValidData () {
94+ // Mock data with known values for predictable mean calculation
95+ String mockJson = """
96+ [{"SEK_per_kWh":0.10,"EUR_per_kWh":0.01,"EXR":10.0,"time_start":"2025-09-04T00:00:00+02:00","time_end":"2025-09-04T01:00:00+02:00"},
97+ {"SEK_per_kWh":0.20,"EUR_per_kWh":0.02,"EXR":10.0,"time_start":"2025-09-04T01:00:00+02:00","time_end":"2025-09-04T02:00:00+02:00"},
98+ {"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"},
99+ {"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"}]""" ;
100+
101+ ElpriserAPI .setMockResponse (mockJson );
102+
103+ Main .main (new String []{"--zone" , "SE3" , "--date" , "2025-09-04" });
104+
105+ String output = bos .toString ();
106+ assertThat (output ).containsIgnoringCase ("medelpris" );
107+ // Mean of 0.10, 0.20, 0.30, 0.40 = 0.25 = 25 öre
108+ assertThat (output ).contains ("25" );
109+ }
110+
111+ @ Test
112+ void displayMinMaxPrices_withValidData () {
113+ String mockJson = """
114+ [{"SEK_per_kWh":0.50,"EUR_per_kWh":0.05,"EXR":10.0,"time_start":"2025-09-04T00:00:00+02:00","time_end":"2025-09-04T01:00:00+02:00"},
115+ {"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"},
116+ {"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"},
117+ {"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"}]""" ;
118+
119+ ElpriserAPI .setMockResponse (mockJson );
120+
121+ Main .main (new String []{"--zone" , "SE1" , "--date" , "2025-09-04" });
122+
123+ String output = bos .toString ();
124+ assertThat (output ).containsIgnoringCase ("lägsta pris" );
125+ assertThat (output ).containsIgnoringCase ("högsta pris" );
126+ assertThat (output ).contains ("01-02" ); // Cheapest hour (0.10)
127+ assertThat (output ).contains ("02-03" ); // Most expensive hour (0.80)
128+ assertThat (output ).contains ("10" ); // 10 öre (cheapest)
129+ assertThat (output ).contains ("80" ); // 80 öre (most expensive)
130+ }
131+
132+ @ Test
133+ void displaySortedPrices_whenRequested () {
134+ String mockJson = """
135+ [{"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"},
136+ {"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"},
137+ {"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"}]""" ;
138+
139+ ElpriserAPI .setMockResponse (mockJson );
140+
141+ Main .main (new String []{"--zone" , "SE2" , "--date" , "2025-09-04" , "--sorted" });
142+
143+ String output = bos .toString ();
144+ // Should show prices in descending order
145+ assertThat (output ).contains ("00-01 30 öre" );
146+ assertThat (output ).contains ("02-03 20 öre" );
147+ assertThat (output ).contains ("01-02 10 öre" );
148+ }
149+
8150 @ Test
9- void test () {
10- assertThat (true ).isTrue ();
151+ void findOptimalCharging2Hours () {
152+ String mockJson = """
153+ [{"SEK_per_kWh":0.50,"EUR_per_kWh":0.05,"EXR":10.0,"time_start":"2025-09-04T00:00:00+02:00","time_end":"2025-09-04T01:00:00+02:00"},
154+ {"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"},
155+ {"SEK_per_kWh":0.05,"EUR_per_kWh":0.005,"EXR":10.0,"time_start":"2025-09-04T02:00:00+02:00","time_end":"2025-09-04T03:00:00+02:00"},
156+ {"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"},
157+ {"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"}]""" ;
158+
159+ ElpriserAPI .setMockResponse (mockJson );
160+
161+ Main .main (new String []{"--zone" , "SE3" , "--date" , "2025-09-04" , "--charging" , "2h" });
162+
163+ String output = bos .toString ();
164+ assertThat (output ).containsIgnoringCase ("påbörja laddning" );
165+ assertThat (output ).contains ("01" ); // Should start at 01:00 (cheapest 2h window: 01-02 + 02-03)
166+ assertThat (output ).containsIgnoringCase ("medelpris" );
167+ assertThat (output ).contains ("7,5" ); // Mean of 0.10 and 0.05 = 0.075 = 7.5 öre
168+ }
169+
170+ @ Test
171+ void findOptimalCharging4Hours () {
172+ String mockJson = """
173+ [{"SEK_per_kWh":0.40,"EUR_per_kWh":0.04,"EXR":10.0,"time_start":"2025-09-04T00:00:00+02:00","time_end":"2025-09-04T01:00:00+02:00"},
174+ {"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"},
175+ {"SEK_per_kWh":0.05,"EUR_per_kWh":0.005,"EXR":10.0,"time_start":"2025-09-04T02:00:00+02:00","time_end":"2025-09-04T03:00:00+02:00"},
176+ {"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"},
177+ {"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"},
178+ {"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"}]""" ;
179+
180+ ElpriserAPI .setMockResponse (mockJson );
181+
182+ Main .main (new String []{"--zone" , "SE1" , "--date" , "2025-09-04" , "--charging" , "4h" });
183+
184+ String output = bos .toString ();
185+ assertThat (output ).containsIgnoringCase ("påbörja laddning" );
186+ assertThat (output ).contains ("01" ); // Should start at 01:00 (cheapest 4h window)
187+ assertThat (output ).containsIgnoringCase ("medelpris" );
188+ // Mean of 0.10, 0.05, 0.15, 0.20 = 0.125 = 12.5 öre
189+ assertThat (output ).contains ("12,5" );
190+ }
191+
192+ @ Test
193+ void findOptimalCharging8Hours () {
194+ // Create mock data with 12 hours to allow for 8-hour window
195+ StringBuilder jsonBuilder = new StringBuilder ("[" );
196+ 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 };
197+
198+ for (int i = 0 ; i < prices .length ; i ++) {
199+ if (i > 0 ) jsonBuilder .append ("," );
200+ jsonBuilder .append (String .format (
201+ """
202+ {"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"}""" ,
203+ prices [i ], prices [i ] / 10 , i , i + 1
204+ ));
205+ }
206+ jsonBuilder .append ("]" );
207+
208+ ElpriserAPI .setMockResponse (jsonBuilder .toString ());
209+
210+ Main .main (new String []{"--zone" , "SE4" , "--date" , "2025-09-04" , "--charging" , "8h" });
211+
212+ String output = bos .toString ();
213+ assertThat (output ).containsIgnoringCase ("påbörja laddning" );
214+ assertThat (output ).containsIgnoringCase ("medelpris" );
215+ }
216+
217+ @ Test
218+ void handleInvalidZone () {
219+ Main .main (new String []{"--zone" , "SE5" , "--date" , "2025-09-04" });
220+
221+ String output = bos .toString ();
222+ assertThat (output ).containsAnyOf ("invalid zone" , "ogiltig zon" , "fel zon" );
223+ }
224+
225+ @ Test
226+ void handleInvalidDate () {
227+ Main .main (new String []{"--zone" , "SE3" , "--date" , "invalid-date" });
228+
229+ String output = bos .toString ();
230+ assertThat (output ).containsAnyOf ("invalid date" , "ogiltigt datum" , "fel datum" );
231+ }
232+
233+ @ Test
234+ void handleMissingZoneArgument () {
235+ Main .main (new String []{"--date" , "2025-09-04" });
236+
237+ String output = bos .toString ();
238+ assertThat (output ).containsAnyOf ("zone" , "required" );
239+ }
240+
241+ @ Test
242+ void useCurrentDateWhenNotSpecified () {
243+ ElpriserAPI .setMockResponse ("[]" ); // Empty response is fine for this test
244+
245+ Main .main (new String []{"--zone" , "SE3" });
246+
247+ String output = bos .toString ();
248+ // Should not show error about missing date
249+ assertThat (output ).doesNotContainIgnoringCase ("invalid date" );
250+ assertThat (output ).doesNotContainIgnoringCase ("missing date" );
251+ }
252+
253+ @ Test
254+ void handleNoDataAvailable () {
255+ ElpriserAPI .setMockResponse ("[]" ); // Empty response
256+
257+ Main .main (new String []{"--zone" , "SE3" , "--date" , "2025-09-04" });
258+
259+ String output = bos .toString ();
260+ assertThat (output ).containsAnyOf ("no data" , "ingen data" , "inga priser" );
261+ }
262+
263+ @ Test
264+ void handleMultipleDaysData () {
265+ // Mock response with data for two days
266+ String mockJson = """
267+ [{"SEK_per_kWh":0.30,"EUR_per_kWh":0.03,"EXR":10.0,"time_start":"2025-09-04T22:00:00+02:00","time_end":"2025-09-04T23:00:00+02:00"},
268+ {"SEK_per_kWh":0.25,"EUR_per_kWh":0.025,"EXR":10.0,"time_start":"2025-09-04T23:00:00+02:00","time_end":"2025-09-05T00:00:00+02:00"},
269+ {"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"},
270+ {"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"}]""" ;
271+
272+ ElpriserAPI .setMockResponse (mockJson );
273+
274+ Main .main (new String []{"--zone" , "SE3" , "--date" , "2025-09-04" , "--charging" , "4h" });
275+
276+ String output = bos .toString ();
277+ assertThat (output ).containsIgnoringCase ("påbörja laddning" );
278+ // Should be able to find optimal charging window across day boundary
11279 }
12- }
280+ }
0 commit comments