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