]> git.basschouten.com Git - openhab-addons.git/blob
6e03ddcacee349514a44dba3f78183c9c245bd3a
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.mielecloud.internal.handler;
14
15 import static org.junit.jupiter.api.Assertions.*;
16 import static org.mockito.ArgumentMatchers.*;
17 import static org.mockito.Mockito.*;
18 import static org.openhab.binding.mielecloud.internal.MieleCloudBindingConstants.Channels.*;
19 import static org.openhab.binding.mielecloud.internal.util.MieleCloudBindingIntegrationTestConstants.*;
20 import static org.openhab.binding.mielecloud.internal.util.ReflectionUtil.setPrivate;
21
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.Objects;
26 import java.util.Optional;
27
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.junit.jupiter.api.BeforeEach;
31 import org.junit.jupiter.api.Test;
32 import org.openhab.binding.mielecloud.internal.MieleCloudBindingConstants;
33 import org.openhab.binding.mielecloud.internal.auth.OAuthTokenRefresher;
34 import org.openhab.binding.mielecloud.internal.auth.OpenHabOAuthTokenRefresher;
35 import org.openhab.binding.mielecloud.internal.util.MieleCloudBindingIntegrationTestConstants;
36 import org.openhab.binding.mielecloud.internal.util.ReflectionUtil;
37 import org.openhab.binding.mielecloud.internal.webservice.MieleWebservice;
38 import org.openhab.binding.mielecloud.internal.webservice.MieleWebserviceFactory;
39 import org.openhab.binding.mielecloud.internal.webservice.api.DeviceState;
40 import org.openhab.binding.mielecloud.internal.webservice.api.ProgramStatus;
41 import org.openhab.binding.mielecloud.internal.webservice.api.json.DeviceType;
42 import org.openhab.binding.mielecloud.internal.webservice.api.json.ProcessAction;
43 import org.openhab.binding.mielecloud.internal.webservice.api.json.StateType;
44 import org.openhab.binding.mielecloud.internal.webservice.exception.MieleWebserviceException;
45 import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
46 import org.openhab.core.auth.client.oauth2.OAuthClientService;
47 import org.openhab.core.auth.client.oauth2.OAuthFactory;
48 import org.openhab.core.config.core.Configuration;
49 import org.openhab.core.items.Item;
50 import org.openhab.core.items.ItemBuilder;
51 import org.openhab.core.items.ItemBuilderFactory;
52 import org.openhab.core.items.ItemRegistry;
53 import org.openhab.core.library.types.DecimalType;
54 import org.openhab.core.library.types.OnOffType;
55 import org.openhab.core.library.types.StringType;
56 import org.openhab.core.test.java.JavaOSGiTest;
57 import org.openhab.core.thing.Bridge;
58 import org.openhab.core.thing.Channel;
59 import org.openhab.core.thing.ChannelUID;
60 import org.openhab.core.thing.Thing;
61 import org.openhab.core.thing.ThingRegistry;
62 import org.openhab.core.thing.ThingStatus;
63 import org.openhab.core.thing.ThingStatusDetail;
64 import org.openhab.core.thing.ThingTypeUID;
65 import org.openhab.core.thing.ThingUID;
66 import org.openhab.core.thing.binding.ThingHandler;
67 import org.openhab.core.thing.binding.ThingHandlerFactory;
68 import org.openhab.core.thing.binding.builder.BridgeBuilder;
69 import org.openhab.core.thing.binding.builder.ChannelBuilder;
70 import org.openhab.core.thing.binding.builder.ThingBuilder;
71 import org.openhab.core.thing.link.ItemChannelLink;
72 import org.openhab.core.thing.link.ItemChannelLinkRegistry;
73 import org.openhab.core.thing.type.ChannelDefinition;
74 import org.openhab.core.thing.type.ChannelType;
75 import org.openhab.core.thing.type.ChannelTypeRegistry;
76 import org.openhab.core.thing.type.ChannelTypeUID;
77 import org.openhab.core.thing.type.ThingType;
78 import org.openhab.core.thing.type.ThingTypeRegistry;
79 import org.openhab.core.types.State;
80 import org.openhab.core.types.UnDefType;
81
82 /**
83  * @author Björn Lange - Initial contribution
84  */
85 @NonNullByDefault
86 public abstract class AbstractMieleThingHandlerTest extends JavaOSGiTest {
87     protected static final State NULL_VALUE_STATE = UnDefType.UNDEF;
88
89     @Nullable
90     private Bridge bridge;
91     @Nullable
92     private MieleBridgeHandler bridgeHandler;
93     @Nullable
94     private ThingRegistry thingRegistry;
95     @Nullable
96     private MieleWebservice webserviceMock;
97     @Nullable
98     private AbstractMieleThingHandler thingHandler;
99
100     @Nullable
101     private ItemRegistry itemRegistry;
102
103     protected Bridge getBridge() {
104         assertNotNull(bridge);
105         return Objects.requireNonNull(bridge);
106     }
107
108     protected MieleBridgeHandler getBridgeHandler() {
109         assertNotNull(bridgeHandler);
110         return Objects.requireNonNull(bridgeHandler);
111     }
112
113     protected ThingRegistry getThingRegistry() {
114         assertNotNull(thingRegistry);
115         return Objects.requireNonNull(thingRegistry);
116     }
117
118     protected MieleWebservice getWebserviceMock() {
119         assertNotNull(webserviceMock);
120         return Objects.requireNonNull(webserviceMock);
121     }
122
123     protected AbstractMieleThingHandler getThingHandler() {
124         assertNotNull(thingHandler);
125         return Objects.requireNonNull(thingHandler);
126     }
127
128     protected ItemRegistry getItemRegistry() {
129         assertNotNull(itemRegistry);
130         return Objects.requireNonNull(itemRegistry);
131     }
132
133     @Override
134     protected void waitForAssert(Runnable assertion) {
135         // Use a larger timeout to avoid test failures in the CI build.
136         waitForAssert(assertion, 2 * DFL_TIMEOUT, 2 * DFL_SLEEP_TIME);
137     }
138
139     private void setUpThingRegistry() {
140         thingRegistry = getService(ThingRegistry.class, ThingRegistry.class);
141         assertNotNull(thingRegistry, "Thing registry is missing");
142     }
143
144     private void setUpItemRegistry() {
145         itemRegistry = getService(ItemRegistry.class, ItemRegistry.class);
146         assertNotNull(itemRegistry);
147     }
148
149     private void setUpWebservice()
150             throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
151         webserviceMock = mock(MieleWebservice.class);
152         when(getWebserviceMock().hasAccessToken()).thenReturn(true);
153
154         MieleWebserviceFactory webserviceFactory = mock(MieleWebserviceFactory.class);
155         when(webserviceFactory.create(any())).thenReturn(getWebserviceMock());
156
157         MieleHandlerFactory factory = getService(ThingHandlerFactory.class, MieleHandlerFactory.class);
158         assertNotNull(factory);
159         setPrivate(Objects.requireNonNull(factory), "webserviceFactory", webserviceFactory);
160     }
161
162     private void setUpBridge() throws Exception {
163         AccessTokenResponse accessTokenResponse = new AccessTokenResponse();
164         accessTokenResponse.setAccessToken(ACCESS_TOKEN);
165
166         OAuthClientService oAuthClientService = mock(OAuthClientService.class);
167         when(oAuthClientService.getAccessTokenResponse()).thenReturn(accessTokenResponse);
168
169         OAuthFactory oAuthFactory = mock(OAuthFactory.class);
170         when(oAuthFactory
171                 .getOAuthClientService(MieleCloudBindingIntegrationTestConstants.BRIDGE_THING_UID.getAsString()))
172                         .thenReturn(oAuthClientService);
173
174         OpenHabOAuthTokenRefresher tokenRefresher = getService(OAuthTokenRefresher.class,
175                 OpenHabOAuthTokenRefresher.class);
176         assertNotNull(tokenRefresher);
177         setPrivate(Objects.requireNonNull(tokenRefresher), "oauthFactory", oAuthFactory);
178
179         bridge = BridgeBuilder
180                 .create(MieleCloudBindingConstants.THING_TYPE_BRIDGE,
181                         MieleCloudBindingIntegrationTestConstants.BRIDGE_THING_UID)
182                 .withLabel("Miele@home Account")
183                 .withConfiguration(
184                         new Configuration(Collections.singletonMap(MieleCloudBindingConstants.CONFIG_PARAM_EMAIL,
185                                 MieleCloudBindingIntegrationTestConstants.EMAIL)))
186                 .build();
187         assertNotNull(bridge);
188
189         getThingRegistry().add(getBridge());
190
191         // then:
192         waitForAssert(() -> {
193             assertNotNull(getBridge().getHandler());
194             assertTrue(getBridge().getHandler() instanceof MieleBridgeHandler, "Handler type is wrong");
195         });
196
197         MieleBridgeHandler bridgeHandler = (MieleBridgeHandler) getBridge().getHandler();
198         assertNotNull(bridgeHandler);
199
200         waitForAssert(() -> {
201             assertNotNull(bridgeHandler.getThing());
202         });
203
204         bridgeHandler.initialize();
205         bridgeHandler.onConnectionAlive();
206         setPrivate(bridgeHandler, "discoveryService", null);
207         this.bridgeHandler = bridgeHandler;
208     }
209
210     protected AbstractMieleThingHandler createThingHandler(ThingTypeUID thingTypeUid, ThingUID thingUid,
211             Class<? extends AbstractMieleThingHandler> expectedHandlerClass, String deviceIdentifier) {
212         ThingRegistry registry = getThingRegistry();
213
214         List<Channel> channels = createChannelsForThingHandler(thingTypeUid, thingUid);
215
216         Thing thing = ThingBuilder.create(thingTypeUid, thingUid)
217                 .withConfiguration(new Configuration(Collections
218                         .singletonMap(MieleCloudBindingConstants.CONFIG_PARAM_DEVICE_IDENTIFIER, deviceIdentifier)))
219                 .withBridge(getBridge().getUID()).withChannels(channels).withLabel("DA-6996").build();
220         assertNotNull(thing);
221
222         registry.add(thing);
223
224         waitForAssert(() -> {
225             ThingHandler handler = thing.getHandler();
226             assertNotNull(handler);
227             assertTrue(expectedHandlerClass.isAssignableFrom(handler.getClass()), "Handler type is wrong");
228         });
229
230         createItemsForChannels(thing);
231         linkChannelsToItems(thing);
232
233         ThingHandler handler = thing.getHandler();
234         assertNotNull(handler);
235         AbstractMieleThingHandler mieleThingHandler = (AbstractMieleThingHandler) Objects.requireNonNull(handler);
236
237         waitForAssert(() -> {
238             try {
239                 assertNotNull(ReflectionUtil.invokePrivate(mieleThingHandler, "getBridge"));
240             } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException e) {
241                 throw new RuntimeException(e);
242             }
243             assertNotNull(getBridge().getThing(thingUid));
244         });
245
246         return mieleThingHandler;
247     }
248
249     private List<Channel> createChannelsForThingHandler(ThingTypeUID thingTypeUid, ThingUID thingUid) {
250         ChannelTypeRegistry channelTypeRegistry = getService(ChannelTypeRegistry.class, ChannelTypeRegistry.class);
251         assertNotNull(channelTypeRegistry);
252
253         ThingTypeRegistry thingTypeRegistry = getService(ThingTypeRegistry.class, ThingTypeRegistry.class);
254         assertNotNull(thingTypeRegistry);
255
256         ThingType thingType = thingTypeRegistry.getThingType(thingTypeUid);
257         assertNotNull(thingType);
258
259         List<ChannelDefinition> channelDefinitions = thingType.getChannelDefinitions();
260         assertNotNull(channelDefinitions);
261
262         List<Channel> channels = new ArrayList<Channel>();
263         for (ChannelDefinition channelDefinition : channelDefinitions) {
264             ChannelTypeUID channelTypeUid = channelDefinition.getChannelTypeUID();
265             assertNotNull(channelTypeUid);
266
267             ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeUid);
268             assertNotNull(channelType);
269
270             String acceptedItemType = channelType.getItemType();
271             assertNotNull(acceptedItemType);
272
273             String channelId = channelDefinition.getId();
274             assertNotNull(channelId);
275
276             ChannelUID channelUid = new ChannelUID(thingUid, channelId);
277             assertNotNull(channelUid);
278
279             Channel channel = ChannelBuilder.create(channelUid, acceptedItemType).build();
280             assertNotNull(channel);
281
282             channels.add(channel);
283         }
284
285         return channels;
286     }
287
288     private void createItemsForChannels(Thing thing) {
289         ItemBuilderFactory itemBuilderFactory = getService(ItemBuilderFactory.class);
290         assertNotNull(itemBuilderFactory);
291
292         for (Channel channel : thing.getChannels()) {
293             String acceptedItemType = channel.getAcceptedItemType();
294             assertNotNull(acceptedItemType);
295
296             ItemBuilder itemBuilder = itemBuilderFactory.newItemBuilder(Objects.requireNonNull(acceptedItemType),
297                     channel.getUID().getId());
298             assertNotNull(itemBuilder);
299
300             Item item = itemBuilder.build();
301             assertNotNull(item);
302
303             getItemRegistry().add(item);
304         }
305     }
306
307     private void linkChannelsToItems(Thing thing) {
308         ItemChannelLinkRegistry itemChannelLinkRegistry = getService(ItemChannelLinkRegistry.class,
309                 ItemChannelLinkRegistry.class);
310         assertNotNull(itemChannelLinkRegistry);
311
312         for (Channel channel : thing.getChannels()) {
313             String itemName = channel.getUID().getId();
314             assertNotNull(itemName);
315
316             ChannelUID channelUid = channel.getUID();
317             assertNotNull(channelUid);
318
319             ItemChannelLink link = itemChannelLinkRegistry.add(new ItemChannelLink(itemName, channelUid));
320             assertNotNull(link);
321         }
322     }
323
324     protected ChannelUID channel(String id) {
325         return new ChannelUID(getThingHandler().getThing().getUID(), id);
326     }
327
328     @BeforeEach
329     public void setUpAbstractMieleThingHandlerTest() throws Exception {
330         registerVolatileStorageService();
331         setUpThingRegistry();
332         setUpItemRegistry();
333         setUpWebservice();
334     }
335
336     protected void setUpBridgeAndThing() throws Exception {
337         setUpBridge();
338         thingHandler = setUpThingHandler();
339     }
340
341     private void assertThingStatusIs(Thing thing, ThingStatus expectedStatus, ThingStatusDetail expectedStatusDetail) {
342         assertThingStatusIs(thing, expectedStatus, expectedStatusDetail, null);
343     }
344
345     private void assertThingStatusIs(Thing thing, ThingStatus expectedStatus, ThingStatusDetail expectedStatusDetail,
346             @Nullable String expectedDescription) {
347         assertEquals(expectedStatus, thing.getStatus());
348         assertEquals(expectedStatusDetail, thing.getStatusInfo().getStatusDetail());
349         if (expectedDescription == null) {
350             assertNull(thing.getStatusInfo().getDescription());
351         } else {
352             assertEquals(expectedDescription, thing.getStatusInfo().getDescription());
353         }
354     }
355
356     protected State getChannelState(String channelUid) {
357         Item item = getItemRegistry().get(channelUid);
358         assertNotNull(item, "Item for channel UID " + channelUid + " is null.");
359         return item.getState();
360     }
361
362     /**
363      * Sets up the {@link ThingHandler} under test.
364      *
365      * @return The created {@link ThingHandler}.
366      */
367     protected abstract AbstractMieleThingHandler setUpThingHandler();
368
369     @Test
370     public void testCachedStateIsQueriedOnInitialize() throws Exception {
371         // given:
372         setUpBridgeAndThing();
373
374         // then:
375         verify(getWebserviceMock()).dispatchDeviceState(SERIAL_NUMBER);
376     }
377
378     @Test
379     public void testThingStatusIsOfflineWithDetailGoneAndDetailMessageWhenDeviceIsRemoved() throws Exception {
380         // given:
381         setUpBridgeAndThing();
382
383         // when:
384         getBridgeHandler().onDeviceRemoved(SERIAL_NUMBER);
385
386         // then:
387         Thing thing = getThingHandler().getThing();
388         assertThingStatusIs(thing, ThingStatus.OFFLINE, ThingStatusDetail.GONE,
389                 "@text/mielecloud.thing.status.removed");
390     }
391
392     private DeviceState createDeviceStateMock(StateType stateType, String localizedState) {
393         DeviceState deviceState = mock(DeviceState.class);
394         when(deviceState.getDeviceIdentifier()).thenReturn(getThingHandler().getThing().getUID().getId());
395         when(deviceState.getRawType()).thenReturn(DeviceType.UNKNOWN);
396         when(deviceState.getStateType()).thenReturn(Optional.of(stateType));
397         when(deviceState.isInState(any())).thenCallRealMethod();
398         when(deviceState.getStatus()).thenReturn(Optional.of(localizedState));
399         return deviceState;
400     }
401
402     @Test
403     public void testStatusIsSetToOnlineWhenDeviceStateIsValid() throws Exception {
404         // given:
405         setUpBridgeAndThing();
406
407         DeviceState deviceState = createDeviceStateMock(StateType.ON, "On");
408
409         // when:
410         getBridgeHandler().onDeviceStateUpdated(deviceState);
411
412         // then:
413         assertThingStatusIs(getThingHandler().getThing(), ThingStatus.ONLINE, ThingStatusDetail.NONE);
414     }
415
416     @Test
417     public void testStatusIsSetToOfflineWhenDeviceIsNotConnected() throws Exception {
418         // given:
419         setUpBridgeAndThing();
420
421         DeviceState deviceState = createDeviceStateMock(StateType.NOT_CONNECTED, "Not connected");
422
423         // when:
424         getBridgeHandler().onDeviceStateUpdated(deviceState);
425
426         // then:
427         assertThingStatusIs(getThingHandler().getThing(), ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
428                 "@text/mielecloud.thing.status.disconnected");
429     }
430
431     @Test
432     public void testFailingPutProcessActionDoesNotSetTheDeviceToOffline() throws Exception {
433         // given:
434         doThrow(MieleWebserviceException.class).when(getWebserviceMock()).putProcessAction(any(),
435                 eq(ProcessAction.STOP));
436
437         setUpBridgeAndThing();
438
439         DeviceState deviceState = createDeviceStateMock(StateType.ON, "On");
440         getBridgeHandler().onDeviceStateUpdated(deviceState);
441         assertThingStatusIs(getThingHandler().getThing(), ThingStatus.ONLINE, ThingStatusDetail.NONE);
442
443         // when:
444         getThingHandler().triggerProcessAction(ProcessAction.STOP);
445
446         // then:
447         assertThingStatusIs(getThingHandler().getThing(), ThingStatus.ONLINE, ThingStatusDetail.NONE);
448     }
449
450     @Test
451     public void testHandleCommandProgramStartToStartStopChannel() throws Exception {
452         // given:
453         setUpBridgeAndThing();
454
455         // when:
456         getThingHandler().handleCommand(channel(PROGRAM_START_STOP),
457                 new StringType(ProgramStatus.PROGRAM_STARTED.getState()));
458
459         // then:
460         waitForAssert(() -> {
461             verify(getWebserviceMock()).putProcessAction(getThingHandler().getDeviceId(), ProcessAction.START);
462         });
463     }
464
465     @Test
466     public void testHandleCommandProgramStopToStartStopChannel() throws Exception {
467         // given:
468         setUpBridgeAndThing();
469
470         // when:
471         getThingHandler().handleCommand(channel(PROGRAM_START_STOP),
472                 new StringType(ProgramStatus.PROGRAM_STOPPED.getState()));
473
474         // then:
475         waitForAssert(() -> {
476             verify(getWebserviceMock()).putProcessAction(getThingHandler().getDeviceId(), ProcessAction.STOP);
477         });
478     }
479
480     @Test
481     public void testHandleCommandProgramStartToStartStopPauseChannel() throws Exception {
482         // given:
483         setUpBridgeAndThing();
484
485         // when:
486         getThingHandler().handleCommand(channel(PROGRAM_START_STOP_PAUSE),
487                 new StringType(ProgramStatus.PROGRAM_STARTED.getState()));
488
489         // then:
490         waitForAssert(() -> {
491             verify(getWebserviceMock()).putProcessAction(getThingHandler().getDeviceId(), ProcessAction.START);
492         });
493     }
494
495     @Test
496     public void testHandleCommandProgramStopToStartStopPauseChannel() throws Exception {
497         // given:
498         setUpBridgeAndThing();
499
500         // when:
501         getThingHandler().handleCommand(channel(PROGRAM_START_STOP_PAUSE),
502                 new StringType(ProgramStatus.PROGRAM_STOPPED.getState()));
503
504         // then:
505         waitForAssert(() -> {
506             verify(getWebserviceMock()).putProcessAction(getThingHandler().getDeviceId(), ProcessAction.STOP);
507         });
508     }
509
510     @Test
511     public void testHandleCommandProgramPauseToStartStopPauseChannel() throws Exception {
512         // given:
513         setUpBridgeAndThing();
514
515         // when:
516         getThingHandler().handleCommand(channel(PROGRAM_START_STOP_PAUSE),
517                 new StringType(ProgramStatus.PROGRAM_PAUSED.getState()));
518
519         // then:
520         waitForAssert(() -> {
521             verify(getWebserviceMock()).putProcessAction(getThingHandler().getDeviceId(), ProcessAction.PAUSE);
522         });
523     }
524
525     @Test
526     public void testFailingPutLightDoesNotSetTheDeviceToOffline() throws Exception {
527         // given:
528         doThrow(MieleWebserviceException.class).when(getWebserviceMock()).putLight(any(), eq(true));
529
530         setUpBridgeAndThing();
531
532         DeviceState deviceState = createDeviceStateMock(StateType.ON, "On");
533         getBridgeHandler().onDeviceStateUpdated(deviceState);
534         assertThingStatusIs(getThingHandler().getThing(), ThingStatus.ONLINE, ThingStatusDetail.NONE);
535
536         // when:
537         getThingHandler().triggerLight(true);
538
539         // then:
540         assertThingStatusIs(getThingHandler().getThing(), ThingStatus.ONLINE, ThingStatusDetail.NONE);
541     }
542
543     @Test
544     public void testHandleCommandLightOff() throws Exception {
545         // given:
546         setUpBridgeAndThing();
547
548         // when:
549         getThingHandler().handleCommand(channel(LIGHT_SWITCH), OnOffType.OFF);
550
551         // then:
552         waitForAssert(() -> {
553             verify(getWebserviceMock()).putLight(getThingHandler().getDeviceId(), false);
554         });
555     }
556
557     @Test
558     public void testHandleCommandLightOn() throws Exception {
559         // given:
560         setUpBridgeAndThing();
561
562         // when:
563         getThingHandler().handleCommand(channel(LIGHT_SWITCH), OnOffType.ON);
564
565         // then:
566         waitForAssert(() -> {
567             verify(getWebserviceMock()).putLight(getThingHandler().getDeviceId(), true);
568         });
569     }
570
571     @Test
572     public void testHandleCommandDoesNothingWhenCommandIsNotOfOnOffType() throws Exception {
573         // given:
574         setUpBridgeAndThing();
575
576         // when:
577         getThingHandler().handleCommand(channel(LIGHT_SWITCH), new DecimalType(0));
578
579         // then:
580         verify(getWebserviceMock(), never()).putLight(anyString(), anyBoolean());
581     }
582
583     @Test
584     public void testHandleCommandPowerOn() throws Exception {
585         // given:
586         setUpBridgeAndThing();
587
588         // when:
589         getThingHandler().handleCommand(channel(POWER_ON_OFF), OnOffType.ON);
590
591         // then:
592         waitForAssert(() -> {
593             verify(getWebserviceMock()).putPowerState(getThingHandler().getDeviceId(), true);
594         });
595     }
596
597     @Test
598     public void testHandleCommandPowerOff() throws Exception {
599         // given:
600         setUpBridgeAndThing();
601
602         // when:
603         getThingHandler().handleCommand(channel(POWER_ON_OFF), OnOffType.OFF);
604
605         // then:
606         waitForAssert(() -> {
607             verify(getWebserviceMock()).putPowerState(getThingHandler().getDeviceId(), false);
608         });
609     }
610
611     @Test
612     public void testHandleCommandDoesNothingWhenPowerCommandIsNotOfOnOffType() throws Exception {
613         // given:
614         setUpBridgeAndThing();
615
616         // when:
617         getThingHandler().handleCommand(channel(POWER_ON_OFF), new DecimalType(0));
618
619         // then:
620         verify(getWebserviceMock(), never()).putPowerState(anyString(), anyBoolean());
621     }
622
623     @Test
624     public void testMissingPropertiesAreSetWhenAStateUpdateIsReceivedFromTheCloud() throws Exception {
625         // given:
626         setUpBridgeAndThing();
627
628         assertFalse(getThingHandler().getThing().getProperties().containsKey(Thing.PROPERTY_SERIAL_NUMBER));
629         assertFalse(getThingHandler().getThing().getProperties().containsKey(Thing.PROPERTY_MODEL_ID));
630
631         var deviceState = mock(DeviceState.class);
632         when(deviceState.getRawType()).thenReturn(DeviceType.UNKNOWN);
633         when(deviceState.getDeviceIdentifier()).thenReturn(MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER);
634         when(deviceState.getFabNumber())
635                 .thenReturn(Optional.of(MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER));
636         when(deviceState.getType()).thenReturn(Optional.of("Unknown device type"));
637         when(deviceState.getTechType()).thenReturn(Optional.of("UK-4567"));
638
639         // when:
640         getThingHandler().onDeviceStateUpdated(deviceState);
641
642         // then:
643         assertEquals(MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER,
644                 getThingHandler().getThing().getProperties().get(Thing.PROPERTY_SERIAL_NUMBER));
645         assertEquals("Unknown device type UK-4567",
646                 getThingHandler().getThing().getProperties().get(Thing.PROPERTY_MODEL_ID));
647     }
648 }