2 * Copyright (c) 2010-2023 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.mybmw.internal.handler;
15 import static org.junit.jupiter.api.Assertions.*;
16 import static org.mockito.Mockito.*;
18 import java.util.List;
20 import java.util.Optional;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.junit.jupiter.api.Test;
25 import org.mockito.ArgumentCaptor;
26 import org.openhab.binding.mybmw.internal.MyBMWConstants.VehicleType;
27 import org.openhab.binding.mybmw.internal.VehicleConfiguration;
28 import org.openhab.binding.mybmw.internal.dto.StatusWrapper;
29 import org.openhab.binding.mybmw.internal.util.FileReader;
30 import org.openhab.binding.mybmw.internal.utils.Constants;
31 import org.openhab.core.i18n.LocationProvider;
32 import org.openhab.core.library.types.PointType;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingUID;
36 import org.openhab.core.thing.binding.ThingHandlerCallback;
37 import org.openhab.core.types.State;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
42 * The {@link VehicleTests} is responsible for handling commands, which are
43 * sent to one of the channels.
45 * @author Bernd Weymann - Initial contribution
48 @SuppressWarnings("null")
49 public class VehicleTests {
50 private final Logger logger = LoggerFactory.getLogger(VehicleHandler.class);
52 private static final int STATUS_ELECTRIC = 12;
53 private static final int STATUS_CONV = 9;
54 private static final int RANGE_HYBRID = 9;
55 private static final int RANGE_CONV = 4;
56 private static final int RANGE_ELECTRIC = 4;
57 private static final int DOORS = 11;
58 private static final int CHECK_EMPTY = 3;
59 private static final int CHECK_AVAILABLE = 3;
60 private static final int SERVICE_AVAILABLE = 3;
61 private static final int SERVICE_EMPTY = 3;
62 private static final int LOCATION = 4;
63 private static final int CHARGE_PROFILE = 44;
64 private static final int TIRES = 8;
65 public static final PointType HOME_LOCATION = new PointType("54.321,9.876");
67 ArgumentCaptor<ChannelUID> channelCaptor;
69 ArgumentCaptor<State> stateCaptor;
71 ThingHandlerCallback tc;
75 List<ChannelUID> allChannels;
77 List<State> allStates;
78 String driveTrain = Constants.EMPTY;
81 * Prepare environment for Vehicle Status Updates
83 public void setup(String type, String vin) {
85 Thing thing = mock(Thing.class);
86 when(thing.getUID()).thenReturn(new ThingUID("testbinding", "test"));
87 MyBMWCommandOptionProvider cop = mock(MyBMWCommandOptionProvider.class);
88 LocationProvider locationProvider = mock(LocationProvider.class);
89 when(locationProvider.getLocation()).thenReturn(HOME_LOCATION);
90 cch = new VehicleHandler(thing, cop, locationProvider, type);
91 VehicleConfiguration vc = new VehicleConfiguration();
93 Optional<VehicleConfiguration> ovc = Optional.of(vc);
94 cch.configuration = ovc;
95 tc = mock(ThingHandlerCallback.class);
97 channelCaptor = ArgumentCaptor.forClass(ChannelUID.class);
98 stateCaptor = ArgumentCaptor.forClass(State.class);
101 private boolean testVehicle(String statusContent, int callbacksExpected,
102 Optional<Map<String, State>> concreteChecks) {
103 assertNotNull(statusContent);
104 cch.vehicleStatusCallback.onResponse(statusContent);
105 verify(tc, times(callbacksExpected)).stateUpdated(channelCaptor.capture(), stateCaptor.capture());
106 allChannels = channelCaptor.getAllValues();
107 allStates = stateCaptor.getAllValues();
109 assertNotNull(driveTrain);
110 StatusWrapper checker = new StatusWrapper(driveTrain, statusContent);
112 if (concreteChecks.isPresent()) {
113 return checker.append(concreteChecks.get()).checkResults(allChannels, allStates);
115 return checker.checkResults(allChannels, allStates);
119 private void trace() {
120 for (int i = 0; i < allChannels.size(); i++) {
121 // change to info for debugging channel updates
122 logger.debug("Channel {} {}", allChannels.get(i), allStates.get(i));
127 * Test various Vehicles from users which delivered their fingerprint.
128 * The tests are checking the chain from "JSON to Channel update".
129 * Checks are done in an automated way cross checking the data from JSON and data delivered via Channel.
130 * Also important the updates are counted in order to check if code changes are affecting Channel Updates.
132 * With the given output the updated Channels are visible.
136 * Channel testbinding::test:status#lock Locked
137 * Channel testbinding::test:status#service-date 2023-11-01T00:00:00.000+0100
138 * Channel testbinding::test:status#check-control No Issues
139 * Channel testbinding::test:status#last-update 2021-12-21T16:46:02.000+0100
140 * Channel testbinding::test:status#doors Closed
141 * Channel testbinding::test:status#windows Closed
142 * Channel testbinding::test:status#plug-connection Not connected
143 * Channel testbinding::test:status#charge Not Charging
144 * Channel testbinding::test:status#charge-type Not Available
145 * Channel testbinding::test:range#electric 76 km
146 * Channel testbinding::test:range#radius-electric 60.800000000000004 km
147 * Channel testbinding::test:range#fuel 31 km
148 * Channel testbinding::test:range#radius-fuel 24.8 km
149 * Channel testbinding::test:range#hybrid 31 km
150 * Channel testbinding::test:range#radius-hybrid 24.8 km
151 * Channel testbinding::test:range#mileage 31537 km
152 * Channel testbinding::test:range#soc 74 %
153 * Channel testbinding::test:range#remaining-fuel 4 l
154 * Channel testbinding::test:doors#driver-front Closed
155 * Channel testbinding::test:doors#driver-rear Closed
156 * Channel testbinding::test:doors#passenger-front Closed
157 * Channel testbinding::test:doors#passenger-rear Closed
158 * Channel testbinding::test:doors#trunk Closed
159 * Channel testbinding::test:doors#hood Closed
160 * Channel testbinding::test:doors#win-driver-front Closed
161 * Channel testbinding::test:doors#win-driver-rear Undef
162 * Channel testbinding::test:doors#win-passenger-front Closed
163 * Channel testbinding::test:doors#win-passenger-rear Undef
164 * Channel testbinding::test:doors#sunroof Closed
165 * Channel testbinding::test:location#gps 1.2345,6.789
166 * Channel testbinding::test:location#heading 222 °
167 * Channel testbinding::test:service#name Brake Fluid
168 * Channel testbinding::test:service#date 2023-11-01T00:00:00.000+0100
169 * Channel testbinding::test:profile#prefs Chargingwindow
170 * Channel testbinding::test:profile#mode Immediatecharging
171 * Channel testbinding::test:profile#control Weeklyplanner
172 * Channel testbinding::test:profile#target 100
173 * Channel testbinding::test:profile#limit OFF
174 * Channel testbinding::test:profile#climate OFF
175 * Channel testbinding::test:profile#window-start 1970-01-01T11:00:00.000+0100
176 * Channel testbinding::test:profile#window-end 1970-01-01T14:30:00.000+0100
177 * Channel testbinding::test:profile#timer1-departure 1970-01-01T16:00:00.000+0100
178 * Channel testbinding::test:profile#timer1-enabled OFF
179 * Channel testbinding::test:profile#timer1-day-mon ON
180 * Channel testbinding::test:profile#timer1-day-tue ON
181 * Channel testbinding::test:profile#timer1-day-wed ON
182 * Channel testbinding::test:profile#timer1-day-thu ON
183 * Channel testbinding::test:profile#timer1-day-fri ON
184 * Channel testbinding::test:profile#timer1-day-sat ON
185 * Channel testbinding::test:profile#timer1-day-sun ON
186 * Channel testbinding::test:profile#timer2-departure 1970-01-01T12:02:00.000+0100
187 * Channel testbinding::test:profile#timer2-enabled ON
188 * Channel testbinding::test:profile#timer2-day-mon OFF
189 * Channel testbinding::test:profile#timer2-day-tue OFF
190 * Channel testbinding::test:profile#timer2-day-wed OFF
191 * Channel testbinding::test:profile#timer2-day-thu OFF
192 * Channel testbinding::test:profile#timer2-day-fri OFF
193 * Channel testbinding::test:profile#timer2-day-sat OFF
194 * Channel testbinding::test:profile#timer2-day-sun ON
195 * Channel testbinding::test:profile#timer3-departure 1970-01-01T13:03:00.000+0100
196 * Channel testbinding::test:profile#timer3-enabled OFF
197 * Channel testbinding::test:profile#timer3-day-mon OFF
198 * Channel testbinding::test:profile#timer3-day-tue OFF
199 * Channel testbinding::test:profile#timer3-day-wed OFF
200 * Channel testbinding::test:profile#timer3-day-thu OFF
201 * Channel testbinding::test:profile#timer3-day-fri OFF
202 * Channel testbinding::test:profile#timer3-day-sat ON
203 * Channel testbinding::test:profile#timer3-day-sun OFF
204 * Channel testbinding::test:profile#timer4-departure 1970-01-01T12:02:00.000+0100
205 * Channel testbinding::test:profile#timer4-enabled OFF
206 * Channel testbinding::test:profile#timer4-day-mon OFF
207 * Channel testbinding::test:profile#timer4-day-tue OFF
208 * Channel testbinding::test:profile#timer4-day-wed OFF
209 * Channel testbinding::test:profile#timer4-day-thu OFF
210 * Channel testbinding::test:profile#timer4-day-fri OFF
211 * Channel testbinding::test:profile#timer4-day-sat OFF
212 * Channel testbinding::test:profile#timer4-day-sun ON
215 public void testI01Rex() {
216 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
217 setup(VehicleType.ELECTRIC_REX.toString(), Constants.ANONYMOUS);
218 String content = FileReader.readFileInString("src/test/resources/responses/I01_REX/vehicles.json");
219 assertTrue(testVehicle(content, STATUS_ELECTRIC + RANGE_HYBRID + DOORS + LOCATION + SERVICE_AVAILABLE
220 + CHECK_EMPTY + CHARGE_PROFILE + TIRES, Optional.empty()));
224 public void testF11() {
225 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
226 setup(VehicleType.CONVENTIONAL.toString(), "some_vin_F11");
227 String content = FileReader.readFileInString("src/test/resources/responses/F11/vehicles_v2_bmw_0.json");
228 assertTrue(testVehicle(content,
229 STATUS_CONV + DOORS + RANGE_CONV + SERVICE_AVAILABLE + CHECK_EMPTY + LOCATION + TIRES,
234 public void testF31() {
235 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
236 setup(VehicleType.CONVENTIONAL.toString(), "some_vin_F31");
237 String content = FileReader.readFileInString("src/test/resources/responses/F31/vehicles_v2_bmw_0.json");
238 assertTrue(testVehicle(content,
239 STATUS_CONV + DOORS + RANGE_CONV + SERVICE_AVAILABLE + CHECK_EMPTY + LOCATION + TIRES,
244 public void testF44() {
245 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
246 setup(VehicleType.CONVENTIONAL.toString(), "some_vin_F44");
247 String content = FileReader.readFileInString("src/test/resources/responses/F44/vehicles_v2_bmw_0.json");
248 assertTrue(testVehicle(content,
249 STATUS_CONV + DOORS + RANGE_CONV + LOCATION + SERVICE_EMPTY + CHECK_EMPTY + TIRES, Optional.empty()));
253 public void testF45() {
254 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
255 setup(VehicleType.PLUGIN_HYBRID.toString(), "some_vin_F45");
256 String content = FileReader.readFileInString("src/test/resources/responses/F45/vehicles_v2_bmw_0.json");
257 assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_HYBRID + SERVICE_AVAILABLE + CHECK_EMPTY
258 + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty()));
262 public void testF48() {
263 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
264 setup(VehicleType.CONVENTIONAL.toString(), "some_vin_F48");
265 String content = FileReader.readFileInString("src/test/resources/responses/F48/vehicles_v2_bmw_0.json");
266 assertTrue(testVehicle(content,
267 STATUS_CONV + DOORS + RANGE_CONV + SERVICE_AVAILABLE + CHECK_AVAILABLE + LOCATION + TIRES,
272 public void testG01() {
273 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
274 setup(VehicleType.PLUGIN_HYBRID.toString(), "some_vin_G01");
275 String content = FileReader.readFileInString("src/test/resources/responses/G01/vehicles_v2_bmw_0.json");
276 assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_HYBRID + SERVICE_AVAILABLE + CHECK_EMPTY
277 + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty()));
281 public void testG05() {
282 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
283 setup(VehicleType.PLUGIN_HYBRID.toString(), "some_vin_G05");
284 String content = FileReader.readFileInString("src/test/resources/responses/G05/vehicles_v2_bmw_0.json");
285 assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_HYBRID + SERVICE_AVAILABLE + CHECK_EMPTY
286 + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty()));
290 public void testG08() {
291 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
292 setup(VehicleType.ELECTRIC.toString(), "some_vin_G08");
293 String content = FileReader.readFileInString("src/test/resources/responses/G08/vehicles_v2_bmw_0.json");
294 assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_ELECTRIC + SERVICE_AVAILABLE + CHECK_EMPTY
295 + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty()));
299 public void testG21() {
300 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
301 setup(VehicleType.PLUGIN_HYBRID.toString(), "some_vin_G21");
302 String content = FileReader.readFileInString("src/test/resources/responses/G21/vehicles_v2_bmw_0.json");
303 assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_HYBRID + SERVICE_AVAILABLE + CHECK_EMPTY
304 + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty()));
308 public void testG30() {
309 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
310 setup(VehicleType.PLUGIN_HYBRID.toString(), "some_vin_G30");
311 String content = FileReader.readFileInString("src/test/resources/responses/G30/vehicles_v2_bmw_0.json");
312 assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_HYBRID + SERVICE_AVAILABLE + CHECK_EMPTY
313 + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty()));
317 public void testI01NoRex() {
318 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
319 setup(VehicleType.ELECTRIC.toString(), "some_vin_I01_NOREX");
320 String content = FileReader.readFileInString("src/test/resources/responses/I01_NOREX/vehicles_v2_bmw_0.json");
321 assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_ELECTRIC + SERVICE_AVAILABLE + CHECK_EMPTY
322 + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty()));
326 public void test530e() {
327 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
328 setup(VehicleType.PLUGIN_HYBRID.toString(), "anonymous");
329 String content = FileReader.readFileInString("src/test/resources/responses/530e/vehicles.json");
330 assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_HYBRID + SERVICE_AVAILABLE + CHECK_EMPTY
331 + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty()));
335 public void test340i() {
336 logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
337 setup(VehicleType.MILD_HYBRID.toString(), "anonymous");
338 String content = FileReader.readFileInString("src/test/resources/responses/G21/340i.json");
339 assertTrue(testVehicle(content,
340 STATUS_CONV + DOORS + RANGE_CONV + LOCATION + SERVICE_EMPTY + CHECK_EMPTY + TIRES, Optional.empty()));