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