2 * Copyright (c) 2010-2022 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.bmwconnecteddrive.internal.handler;
15 import static org.junit.jupiter.api.Assertions.*;
16 import static org.mockito.Mockito.*;
18 import java.util.HashMap;
19 import java.util.List;
21 import java.util.Optional;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.junit.jupiter.api.Test;
26 import org.mockito.ArgumentCaptor;
27 import org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants;
28 import org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants.VehicleType;
29 import org.openhab.binding.bmwconnecteddrive.internal.dto.StatusWrapper;
30 import org.openhab.binding.bmwconnecteddrive.internal.dto.compat.VehicleAttributesContainer;
31 import org.openhab.binding.bmwconnecteddrive.internal.util.FileReader;
32 import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
33 import org.openhab.binding.bmwconnecteddrive.internal.utils.Converter;
34 import org.openhab.core.library.types.StringType;
35 import org.openhab.core.thing.ChannelUID;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.thing.ThingUID;
38 import org.openhab.core.thing.binding.ThingHandlerCallback;
39 import org.openhab.core.types.State;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
44 * The {@link VehicleTests} is responsible for handling commands, which are
45 * sent to one of the channels.
47 * @author Bernd Weymann - Initial contribution
50 @SuppressWarnings("null")
51 public class VehicleTests {
52 private final Logger logger = LoggerFactory.getLogger(VehicleHandler.class);
54 private static final int STATUS_ELECTRIC = 12;
55 private static final int STATUS_CONV = 8;
56 private static final int RANGE_HYBRID = 12;
57 private static final int RANGE_CONV = 4;
58 private static final int RANGE_ELECTRIC = 5;
59 private static final int DOORS = 12;
60 private static final int CHECK_EMPTY = 3;
61 private static final int CHECK_AVAILABLE = 3;
62 private static final int SERVICE_AVAILABLE = 4;
63 private static final int SERVICE_EMPTY = 4;
64 private static final int POSITION = 2;
67 ArgumentCaptor<ChannelUID> channelCaptor;
69 ArgumentCaptor<State> stateCaptor;
71 ThingHandlerCallback tc;
75 List<ChannelUID> allChannels;
77 List<State> allStates;
78 String driveTrain = Constants.EMPTY;
82 * Prepare environment for Vehicle Status Updates
84 public void setup(String type, boolean imperial) {
86 this.imperial = imperial;
87 Thing thing = mock(Thing.class);
88 when(thing.getUID()).thenReturn(new ThingUID("testbinding", "test"));
89 BMWConnectedDriveOptionProvider op = mock(BMWConnectedDriveOptionProvider.class);
90 cch = new VehicleHandler(thing, op, type, imperial);
91 tc = mock(ThingHandlerCallback.class);
93 channelCaptor = ArgumentCaptor.forClass(ChannelUID.class);
94 stateCaptor = ArgumentCaptor.forClass(State.class);
97 private boolean testVehicle(String statusContent, int callbacksExpected,
98 Optional<Map<String, State>> concreteChecks) {
99 assertNotNull(statusContent);
100 cch.vehicleStatusCallback.onResponse(statusContent);
101 verify(tc, times(callbacksExpected)).stateUpdated(channelCaptor.capture(), stateCaptor.capture());
102 allChannels = channelCaptor.getAllValues();
103 allStates = stateCaptor.getAllValues();
105 assertNotNull(driveTrain);
106 StatusWrapper checker = new StatusWrapper(driveTrain, imperial, statusContent);
108 if (concreteChecks.isPresent()) {
109 return checker.append(concreteChecks.get()).checkResults(allChannels, allStates);
111 return checker.checkResults(allChannels, allStates);
115 private void trace() {
116 for (int i = 0; i < allChannels.size(); i++) {
117 logger.info("Channel {} {}", allChannels.get(i), allStates.get(i));
122 * Test various Vehicles from users which delivered their fingerprint.
123 * The tests are checking the chain from "JSON to Channel update".
124 * Checks are done in an automated way cross checking the data from JSON and data delivered via Channel.
125 * Also important the updates are counted in order to check if code changes are affecting Channel Updates.
127 * With the given output the updated Channels are visible.
131 * [main] INFO org.eclipse.jetty.util.log - Logging initialized @1731ms
132 * Channel testbinding::test:status#lock Secured
133 * Channel testbinding::test:status#service-date 2021-11-01T13:00:00.000+0100
134 * Channel testbinding::test:status#service-mileage -1.0 km
135 * Channel testbinding::test:status#check-control Not Active
136 * Channel testbinding::test:status#last-update 2020-08-24T17:55:32.000+0200
137 * Channel testbinding::test:status#doors CLOSED
138 * Channel testbinding::test:status#windows CLOSED
139 * Channel testbinding::test:doors#driver-front CLOSED
140 * Channel testbinding::test:doors#driver-rear CLOSED
141 * Channel testbinding::test:doors#passenger-front CLOSED
142 * Channel testbinding::test:doors#passenger-rear CLOSED
143 * Channel testbinding::test:doors#trunk CLOSED
144 * Channel testbinding::test:doors#hood CLOSED
145 * Channel testbinding::test:doors#window-driver-front CLOSED
146 * Channel testbinding::test:doors#window-driver-rear CLOSED
147 * Channel testbinding::test:doors#window-passenger-front CLOSED
148 * Channel testbinding::test:doors#window-passenger-rear CLOSED
149 * Channel testbinding::test:doors#window-rear INVALID
150 * Channel testbinding::test:doors#sunroof CLOSED
151 * Channel testbinding::test:range#mileage 17273.0 km
152 * Channel testbinding::test:range#electric 148.0 km
153 * Channel testbinding::test:range#radius-electric 118.4 km
154 * Channel testbinding::test:range#fuel 70.0 km
155 * Channel testbinding::test:range#radius-fuel 56.0 km
156 * Channel testbinding::test:range#hybrid 218.0 km
157 * Channel testbinding::test:range#radius-hybrid 174.4 km
158 * Channel testbinding::test:range#soc 71.0 %
159 * Channel testbinding::test:range#remaining-fuel 4.0 l
160 * Channel testbinding::test:status#charge Charging Goal Reached
161 * Channel testbinding::test:check#size 0
162 * Channel testbinding::test:check#name INVALID
163 * Channel testbinding::test:check#mileage -1.0 km
164 * Channel testbinding::test:check#index -1
165 * Channel testbinding::test:service#size 4
166 * Channel testbinding::test:service#name Brake Fluid
167 * Channel testbinding::test:service#date 2021-11-01T13:00:00.000+0100
168 * Channel testbinding::test:service#mileage 15345.0 km
169 * Channel testbinding::test:service#index 0
170 * Channel testbinding::test:location#latitude 50.55604934692383
171 * Channel testbinding::test:location#longitude 8.4956693649292
172 * Channel testbinding::test:location#heading 219.0 °
177 public void testi3Rex() {
178 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
179 setup(VehicleType.ELECTRIC_REX.toString(), false);
180 String content = FileReader.readFileInString("src/test/resources/webapi/vehicle-status.json");
181 assertTrue(testVehicle(content,
182 STATUS_ELECTRIC + RANGE_HYBRID + DOORS + CHECK_EMPTY + SERVICE_AVAILABLE + POSITION, Optional.empty()));
186 public void testi3RexMiles() {
187 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
188 setup(VehicleType.ELECTRIC_REX.toString(), true);
189 String content = FileReader.readFileInString("src/test/resources/webapi/vehicle-status.json");
190 // assertTrue(testVehicle(content, HYBRID_CALL_TIMES + LIST_UPDATES, Optional.empty()));
191 assertTrue(testVehicle(content,
192 STATUS_ELECTRIC + RANGE_HYBRID + DOORS + CHECK_EMPTY + SERVICE_AVAILABLE + POSITION, Optional.empty()));
196 public void testF15() {
197 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
198 setup(VehicleType.CONVENTIONAL.toString(), false);
199 String content = FileReader.readFileInString("src/test/resources/responses/F15/status.json");
200 // Check earliest Service by hard
201 Map<String, State> m = new HashMap<String, State>();
202 // Don>'t test on concrete timestamp - it's is different on each machine
203 // Check for cbsType which is "Oil" instead
204 // m.put(ConnectedDriveConstants.SERVICE_DATE, DateTimeType.valueOf("2018-06-01T14:00:00.000+0200"));
205 m.put(ConnectedDriveConstants.NAME, StringType.valueOf("Oil"));
206 assertTrue(testVehicle(content, STATUS_CONV + DOORS + RANGE_CONV + POSITION + SERVICE_AVAILABLE + CHECK_EMPTY,
211 public void testF15Miles() {
212 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
213 setup(VehicleType.CONVENTIONAL.toString(), true);
214 String content = FileReader.readFileInString("src/test/resources/responses/F15/status.json");
215 // Check earliest Service by hard
216 Map<String, State> m = new HashMap<String, State>();
217 // Don>'t test on concrete timestamp - it's idfferent on each machine
218 // Check for cbsType which is "Oil" instead
219 // m.put(ConnectedDriveConstants.SERVICE_DATE, DateTimeType.valueOf("2018-06-01T14:00:00.000+0200"));
220 m.put(ConnectedDriveConstants.NAME, StringType.valueOf("Oil"));
221 assertTrue(testVehicle(content, STATUS_CONV + DOORS + RANGE_CONV + POSITION + SERVICE_AVAILABLE + CHECK_EMPTY,
226 public void testF31() {
227 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
228 setup(VehicleType.CONVENTIONAL.toString(), false);
229 String content = FileReader.readFileInString("src/test/resources/responses/F31/status.json");
230 assertTrue(testVehicle(content, STATUS_CONV + DOORS + RANGE_CONV + POSITION + SERVICE_AVAILABLE + CHECK_EMPTY,
235 public void testF31Miles() {
236 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
237 setup(VehicleType.CONVENTIONAL.toString(), true);
238 String content = FileReader.readFileInString("src/test/resources/responses/F31/status.json");
239 assertTrue(testVehicle(content, STATUS_CONV + DOORS + RANGE_CONV + POSITION + SERVICE_AVAILABLE + CHECK_EMPTY,
244 public void testF35() {
245 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
246 setup(VehicleType.CONVENTIONAL.toString(), false);
247 String content = FileReader.readFileInString("src/test/resources/responses/F35/status.json");
248 assertTrue(testVehicle(content, STATUS_CONV + DOORS + RANGE_CONV + POSITION + SERVICE_EMPTY + CHECK_EMPTY,
253 public void testF35Miles() {
254 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
255 setup(VehicleType.CONVENTIONAL.toString(), true);
256 String content = FileReader.readFileInString("src/test/resources/responses/F35/status.json");
257 assertTrue(testVehicle(content, STATUS_CONV + DOORS + RANGE_CONV + POSITION + SERVICE_EMPTY + CHECK_EMPTY,
262 public void testF45() {
263 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
264 setup(VehicleType.CONVENTIONAL.toString(), false);
265 String content = FileReader.readFileInString("src/test/resources/responses/F45/status.json");
266 // assertTrue(testVehicle(content, 27, Optional.empty()));
267 assertTrue(testVehicle(content, STATUS_CONV + DOORS + RANGE_CONV + SERVICE_EMPTY + CHECK_EMPTY + POSITION,
272 public void testF45Miles() {
273 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
274 setup(VehicleType.CONVENTIONAL.toString(), true);
275 String content = FileReader.readFileInString("src/test/resources/responses/F45/status.json");
276 assertTrue(testVehicle(content, STATUS_CONV + DOORS + RANGE_CONV + SERVICE_EMPTY + CHECK_EMPTY + POSITION,
281 public void testF48() {
282 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
283 setup(VehicleType.CONVENTIONAL.toString(), false);
284 String content = FileReader.readFileInString("src/test/resources/responses/F48/status.json");
285 assertTrue(testVehicle(content,
286 STATUS_CONV + DOORS + RANGE_CONV + SERVICE_AVAILABLE + CHECK_AVAILABLE + POSITION, Optional.empty()));
290 public void testF48Miles() {
291 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
292 setup(VehicleType.CONVENTIONAL.toString(), true);
293 String content = FileReader.readFileInString("src/test/resources/responses/F48/status.json");
294 assertTrue(testVehicle(content,
295 STATUS_CONV + DOORS + RANGE_CONV + SERVICE_AVAILABLE + CHECK_AVAILABLE + POSITION, Optional.empty()));
299 public void testG31NBTEvo() {
300 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
301 setup(VehicleType.CONVENTIONAL.toString(), false);
302 String content = FileReader.readFileInString("src/test/resources/responses/G31_NBTevo/status.json");
303 // assertTrue(testVehicle(content, 27, Optional.empty()));
304 assertTrue(testVehicle(content, STATUS_CONV + DOORS + RANGE_CONV + SERVICE_AVAILABLE + CHECK_EMPTY + POSITION,
309 public void testG31NBTEvoMiles() {
310 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
311 setup(VehicleType.CONVENTIONAL.toString(), true);
312 String content = FileReader.readFileInString("src/test/resources/responses/G31_NBTevo/status.json");
313 assertTrue(testVehicle(content, STATUS_CONV + DOORS + RANGE_CONV + SERVICE_AVAILABLE + CHECK_EMPTY + POSITION,
318 public void testI01NoRex() {
319 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
320 setup(VehicleType.ELECTRIC.toString(), false);
321 String content = FileReader.readFileInString("src/test/resources/responses/I01_NOREX/status.json");
322 assertTrue(testVehicle(content,
323 STATUS_ELECTRIC + DOORS + RANGE_ELECTRIC + SERVICE_AVAILABLE + CHECK_EMPTY + POSITION,
328 public void testI01NoRexMiles() {
329 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
330 setup(VehicleType.ELECTRIC.toString(), true);
331 String content = FileReader.readFileInString("src/test/resources/responses/I01_NOREX/status.json");
332 assertTrue(testVehicle(content,
333 STATUS_ELECTRIC + DOORS + RANGE_ELECTRIC + SERVICE_AVAILABLE + CHECK_EMPTY + POSITION,
338 public void testI01Rex() {
339 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
340 setup(VehicleType.ELECTRIC_REX.toString(), false);
341 String content = FileReader.readFileInString("src/test/resources/responses/I01_REX/status.json");
342 assertTrue(testVehicle(content,
343 STATUS_ELECTRIC + DOORS + RANGE_HYBRID + SERVICE_AVAILABLE + CHECK_EMPTY + POSITION, Optional.empty()));
347 public void testI01RexMiles() {
348 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
349 setup(VehicleType.ELECTRIC_REX.toString(), true);
350 String content = FileReader.readFileInString("src/test/resources/responses/I01_REX/status.json");
351 assertTrue(testVehicle(content,
352 STATUS_ELECTRIC + DOORS + RANGE_HYBRID + SERVICE_AVAILABLE + CHECK_EMPTY + POSITION, Optional.empty()));
356 public void test318iF31() {
357 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
358 setup(VehicleType.CONVENTIONAL.toString(), false);
359 String content = FileReader.readFileInString("src/test/resources/responses/F31/status-318i.json");
360 Map<String, State> m = new HashMap<String, State>();
361 m.put(ConnectedDriveConstants.WINDOWS, StringType.valueOf(Constants.INTERMEDIATE));
362 assertTrue(testVehicle(content, STATUS_CONV + DOORS + RANGE_CONV + SERVICE_AVAILABLE + CHECK_EMPTY + POSITION,
367 public void test318iF31Miles() {
368 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
369 setup(VehicleType.CONVENTIONAL.toString(), true);
370 String content = FileReader.readFileInString("src/test/resources/responses/F31/status-318i.json");
371 Map<String, State> m = new HashMap<String, State>();
372 m.put(ConnectedDriveConstants.WINDOWS, StringType.valueOf(Constants.INTERMEDIATE));
373 assertTrue(testVehicle(content, STATUS_CONV + DOORS + RANGE_CONV + SERVICE_AVAILABLE + CHECK_EMPTY + POSITION,
378 public void testI01RexCompat() {
379 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
380 setup(VehicleType.ELECTRIC_REX.toString(), false);
381 String content = FileReader.readFileInString("src/test/resources/api/vehicle/vehicle-ccm.json");
382 VehicleAttributesContainer vac = Converter.getGson().fromJson(content, VehicleAttributesContainer.class);
383 assertTrue(testVehicle(Converter.transformLegacyStatus(vac),
384 STATUS_ELECTRIC + DOORS + RANGE_HYBRID + SERVICE_AVAILABLE + CHECK_AVAILABLE + POSITION,
389 public void testI01RexMilesCompat() {
390 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
391 setup(VehicleType.ELECTRIC_REX.toString(), true);
392 String content = FileReader.readFileInString("src/test/resources/api/vehicle/vehicle-ccm.json");
393 VehicleAttributesContainer vac = Converter.getGson().fromJson(content, VehicleAttributesContainer.class);
394 assertTrue(testVehicle(Converter.transformLegacyStatus(vac),
395 STATUS_ELECTRIC + DOORS + RANGE_HYBRID + SERVICE_AVAILABLE + CHECK_AVAILABLE + POSITION,
400 public void testF11Compat() {
401 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
402 setup(VehicleType.CONVENTIONAL.toString(), false);
403 String content = FileReader.readFileInString("src/test/resources/responses/F11/vehicle-status.json");
404 VehicleAttributesContainer vac = Converter.getGson().fromJson(content, VehicleAttributesContainer.class);
405 assertTrue(testVehicle(Converter.transformLegacyStatus(vac),
406 STATUS_CONV + DOORS + RANGE_CONV + SERVICE_AVAILABLE + CHECK_EMPTY + POSITION, Optional.empty()));
410 public void testF11MilesCompat() {
411 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
412 setup(VehicleType.CONVENTIONAL.toString(), true);
413 String content = FileReader.readFileInString("src/test/resources/responses/F11/vehicle-status.json");
414 VehicleAttributesContainer vac = Converter.getGson().fromJson(content, VehicleAttributesContainer.class);
415 assertTrue(testVehicle(Converter.transformLegacyStatus(vac),
416 STATUS_CONV + DOORS + RANGE_CONV + SERVICE_AVAILABLE + CHECK_EMPTY + POSITION, Optional.empty()));