]> git.basschouten.com Git - openhab-addons.git/blob
2ae1fd607d0def6afb29c8fb6db09df35ca504dd
[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.discovery;
14
15 import static org.junit.jupiter.api.Assertions.*;
16 import static org.mockito.Mockito.*;
17 import static org.openhab.binding.mielecloud.internal.util.MieleCloudBindingIntegrationTestConstants.*;
18
19 import java.util.List;
20 import java.util.Objects;
21 import java.util.Optional;
22 import java.util.stream.Collectors;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.junit.jupiter.api.BeforeEach;
27 import org.junit.jupiter.api.Test;
28 import org.openhab.binding.mielecloud.internal.MieleCloudBindingConstants;
29 import org.openhab.binding.mielecloud.internal.util.MieleCloudBindingIntegrationTestConstants;
30 import org.openhab.binding.mielecloud.internal.util.OpenHabOsgiTest;
31 import org.openhab.binding.mielecloud.internal.webservice.api.DeviceState;
32 import org.openhab.binding.mielecloud.internal.webservice.api.json.Device;
33 import org.openhab.binding.mielecloud.internal.webservice.api.json.DeviceIdentLabel;
34 import org.openhab.binding.mielecloud.internal.webservice.api.json.DeviceType;
35 import org.openhab.binding.mielecloud.internal.webservice.api.json.Ident;
36 import org.openhab.binding.mielecloud.internal.webservice.api.json.Type;
37 import org.openhab.core.config.discovery.DiscoveryResult;
38 import org.openhab.core.config.discovery.DiscoveryService;
39 import org.openhab.core.config.discovery.inbox.InboxPredicates;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.thing.ThingTypeUID;
42 import org.openhab.core.thing.ThingUID;
43
44 /**
45  * @author Björn Lange - Initial contribution
46  */
47 @NonNullByDefault
48 public class ThingDiscoveryTest extends OpenHabOsgiTest {
49     private static final String DEVICE_TYPE_NAME_COFFEE_SYSTEM = "Coffee System";
50     private static final String DEVICE_TYPE_NAME_DISHWASHER = "Dishwasher";
51     private static final String DEVICE_TYPE_NAME_DISH_WARMER = "Dish Warmer";
52     private static final String DEVICE_TYPE_NAME_DRYER = "Dryer";
53     private static final String DEVICE_TYPE_NAME_FRIDGE_FREEZER = "Fridge Freezer";
54     private static final String DEVICE_TYPE_NAME_HOB = "Hob";
55     private static final String DEVICE_TYPE_NAME_HOOD = "Hood";
56     private static final String DEVICE_TYPE_NAME_OVEN = "Oven";
57     private static final String DEVICE_TYPE_NAME_ROBOTIC_VACUUM_CLEANER = "Robotic Vacuum Cleaner";
58     private static final String DEVICE_TYPE_NAME_WASHING_MACHINE = "Washing Machine";
59     private static final String DEVICE_TYPE_NAME_WINE_STORAGE = "Wine Storage";
60
61     private static final String TECH_TYPE = "WM1234";
62     private static final String TECH_TYPE_2 = "CM1234";
63     private static final String DEVICE_NAME = "My Device";
64     private static final String DEVICE_NAME_2 = "My Other Device";
65     private static final String SERIAL_NUMBER_2 = "900124430017";
66
67     private static final ThingUID DISHWASHER_DEVICE_THING_UID_WITH_SERIAL_NUMBER_2 = new ThingUID(
68             new ThingTypeUID(MieleCloudBindingConstants.BINDING_ID, "dishwasher"), BRIDGE_THING_UID, SERIAL_NUMBER_2);
69
70     @Nullable
71     private ThingDiscoveryService discoveryService;
72
73     private ThingDiscoveryService getDiscoveryService() {
74         assertNotNull(discoveryService);
75         return Objects.requireNonNull(discoveryService);
76     }
77
78     @BeforeEach
79     public void setUp()
80             throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
81         setUpBridge();
82         setUpDiscoveryService();
83     }
84
85     private void setUpDiscoveryService()
86             throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
87         waitForAssert(() -> {
88             discoveryService = getService(DiscoveryService.class, ThingDiscoveryService.class);
89             assertNotNull(discoveryService);
90         });
91
92         getDiscoveryService().activate();
93     }
94
95     private DeviceState createDeviceState(String fabNumber, String techType, String deviceName, DeviceType deviceType,
96             String deviceTypeText) {
97         // given:
98         DeviceIdentLabel deviceIdentLabel = mock(DeviceIdentLabel.class);
99         when(deviceIdentLabel.getFabNumber()).thenReturn(Optional.of(fabNumber));
100         when(deviceIdentLabel.getTechType()).thenReturn(Optional.of(techType));
101
102         Type type = mock(Type.class);
103         when(type.getValueRaw()).thenReturn(deviceType);
104         when(type.getValueLocalized()).thenReturn(Optional.of(deviceTypeText));
105
106         Ident ident = mock(Ident.class);
107         when(ident.getDeviceIdentLabel()).thenReturn(Optional.of(deviceIdentLabel));
108         when(ident.getType()).thenReturn(Optional.of(type));
109         when(ident.getDeviceName()).thenReturn(Optional.of(deviceName));
110
111         Device device = mock(Device.class);
112         when(device.getIdent()).thenReturn(Optional.of(ident));
113
114         return new DeviceState(fabNumber, device);
115     }
116
117     private void assertValidDiscoveryResult(ThingUID expectedThingUID, String expectedSerialNumber,
118             String expectedDeviceIdentifier, String expectedLabel, String expectedModelId) {
119         List<DiscoveryResult> results = getInbox().stream().filter(InboxPredicates.forThingUID(expectedThingUID))
120                 .collect(Collectors.toList());
121         assertEquals(1, results.size(), "Amount of things in inbox does not match expected number");
122
123         DiscoveryResult result = results.get(0);
124         assertEquals(MieleCloudBindingConstants.BINDING_ID, result.getBindingId(), "Invalid binding ID");
125         assertEquals(MieleCloudBindingIntegrationTestConstants.BRIDGE_THING_UID, result.getBridgeUID(),
126                 "Invalid bridge UID");
127         assertEquals(Thing.PROPERTY_SERIAL_NUMBER, result.getRepresentationProperty(),
128                 "Invalid representation property");
129         assertEquals(expectedModelId, result.getProperties().get(Thing.PROPERTY_MODEL_ID), "Invalid model ID");
130         assertEquals(expectedLabel, result.getLabel(), "Invalid label");
131         assertEquals(expectedSerialNumber, result.getProperties().get(Thing.PROPERTY_SERIAL_NUMBER),
132                 "Invalid serial number");
133         assertEquals(expectedDeviceIdentifier,
134                 result.getProperties().get(MieleCloudBindingConstants.CONFIG_PARAM_DEVICE_IDENTIFIER),
135                 "Invalid serial number");
136     }
137
138     private void testMieleDeviceInboxDiscoveryResult(DeviceType deviceType, ThingUID expectedThingUid,
139             String deviceTypeName) {
140         // given:
141         DeviceState deviceState = createDeviceState(SERIAL_NUMBER, TECH_TYPE, DEVICE_NAME, deviceType, deviceTypeName);
142
143         // when:
144         getDiscoveryService().onDeviceStateUpdated(deviceState);
145
146         // then:
147         assertValidDiscoveryResult(expectedThingUid, SERIAL_NUMBER, SERIAL_NUMBER, DEVICE_NAME,
148                 deviceTypeName + " " + TECH_TYPE);
149     }
150
151     @Test
152     public void testWashingDeviceInboxDiscoveryResult() {
153         testMieleDeviceInboxDiscoveryResult(DeviceType.WASHING_MACHINE, WASHING_MACHINE_THING_UID,
154                 DEVICE_TYPE_NAME_WASHING_MACHINE);
155     }
156
157     @Test
158     public void testOvenInboxDiscoveryResult() {
159         testMieleDeviceInboxDiscoveryResult(DeviceType.OVEN, OVEN_DEVICE_THING_UID, DEVICE_TYPE_NAME_OVEN);
160     }
161
162     @Test
163     public void testHobInboxDiscoveryResult() {
164         testMieleDeviceInboxDiscoveryResult(DeviceType.HOB_HIGHLIGHT, HOB_DEVICE_THING_UID, DEVICE_TYPE_NAME_HOB);
165     }
166
167     @Test
168     public void testCoolingDeviceInboxDiscoveryResult() {
169         testMieleDeviceInboxDiscoveryResult(DeviceType.FRIDGE_FREEZER_COMBINATION, FRIDGE_FREEZER_DEVICE_THING_UID,
170                 DEVICE_TYPE_NAME_FRIDGE_FREEZER);
171     }
172
173     @Test
174     public void testHoodInboxDiscoveryResult() {
175         testMieleDeviceInboxDiscoveryResult(DeviceType.HOOD, HOOD_DEVICE_THING_UID, DEVICE_TYPE_NAME_HOOD);
176     }
177
178     @Test
179     public void testCoffeeDeviceInboxDiscoveryResult() {
180         testMieleDeviceInboxDiscoveryResult(DeviceType.COFFEE_SYSTEM, COFFEE_SYSTEM_THING_UID,
181                 DEVICE_TYPE_NAME_COFFEE_SYSTEM);
182     }
183
184     @Test
185     public void testWineStorageDeviceInboxDiscoveryResult() {
186         testMieleDeviceInboxDiscoveryResult(DeviceType.WINE_CABINET, WINE_STORAGE_DEVICE_THING_UID,
187                 DEVICE_TYPE_NAME_WINE_STORAGE);
188     }
189
190     @Test
191     public void testDryerInboxDiscoveryResult() {
192         testMieleDeviceInboxDiscoveryResult(DeviceType.TUMBLE_DRYER, DRYER_DEVICE_THING_UID, DEVICE_TYPE_NAME_DRYER);
193     }
194
195     @Test
196     public void testDishwasherInboxDiscoveryResult() {
197         testMieleDeviceInboxDiscoveryResult(DeviceType.DISHWASHER, DISHWASHER_DEVICE_THING_UID,
198                 DEVICE_TYPE_NAME_DISHWASHER);
199     }
200
201     @Test
202     public void testDishWarmerInboxDiscoveryResult() {
203         testMieleDeviceInboxDiscoveryResult(DeviceType.DISH_WARMER, DISH_WARMER_DEVICE_THING_UID,
204                 DEVICE_TYPE_NAME_DISH_WARMER);
205     }
206
207     @Test
208     public void testRoboticVacuumCleanerInboxDiscoveryResult() {
209         testMieleDeviceInboxDiscoveryResult(DeviceType.VACUUM_CLEANER, ROBOTIC_VACUUM_CLEANER_THING_UID,
210                 DEVICE_TYPE_NAME_ROBOTIC_VACUUM_CLEANER);
211     }
212
213     @Test
214     public void testUnknownDeviceCreatesNoInboxDiscoveryResult() {
215         // given:
216         DeviceState deviceState = createDeviceState(SERIAL_NUMBER, TECH_TYPE, DEVICE_NAME, DeviceType.VACUUM_DRAWER,
217                 "Vacuum Drawer");
218
219         // when:
220         getDiscoveryService().onDeviceStateUpdated(deviceState);
221
222         // then:
223         waitForAssert(() -> {
224             List<DiscoveryResult> results = getInbox().stream().collect(Collectors.toList());
225             assertEquals(0, results.size(), "Amount of things in inbox does not match expected number");
226         });
227     }
228
229     @Test
230     public void testDeviceDiscoveryResultOfDeviceRemovedInTheCloudIsRemovedFromTheInbox() throws InterruptedException {
231         // given:
232         testMieleDeviceInboxDiscoveryResult(DeviceType.HOOD, HOOD_DEVICE_THING_UID, DEVICE_TYPE_NAME_HOOD);
233
234         Thread.sleep(10);
235
236         // when:
237         getDiscoveryService().onDeviceRemoved(SERIAL_NUMBER);
238
239         // then:
240         waitForAssert(() -> {
241             List<DiscoveryResult> results = getInbox().stream().collect(Collectors.toList());
242             assertEquals(0, results.size(), "Amount of things in inbox does not match expected number");
243         });
244     }
245
246     @Test
247     public void testDiscoveryResultsForTwoDevices() {
248         // given:
249         DeviceState hoodDevice = createDeviceState(SERIAL_NUMBER, TECH_TYPE, DEVICE_NAME, DeviceType.HOOD,
250                 DEVICE_TYPE_NAME_HOOD);
251         DeviceState dishwasherDevice = createDeviceState(SERIAL_NUMBER_2, TECH_TYPE_2, DEVICE_NAME_2,
252                 DeviceType.DISHWASHER, DEVICE_TYPE_NAME_DISHWASHER);
253
254         // when:
255         getDiscoveryService().onDeviceStateUpdated(hoodDevice);
256         getDiscoveryService().onDeviceStateUpdated(dishwasherDevice);
257
258         // then:
259         waitForAssert(() -> {
260             List<DiscoveryResult> results = getInbox().stream().collect(Collectors.toList());
261             assertEquals(2, results.size(), "Amount of things in inbox does not match expected number");
262
263             assertValidDiscoveryResult(HOOD_DEVICE_THING_UID, SERIAL_NUMBER, SERIAL_NUMBER, DEVICE_NAME,
264                     "Hood " + TECH_TYPE);
265             assertValidDiscoveryResult(DISHWASHER_DEVICE_THING_UID_WITH_SERIAL_NUMBER_2, SERIAL_NUMBER_2,
266                     SERIAL_NUMBER_2, DEVICE_NAME_2, DEVICE_TYPE_NAME_DISHWASHER + " " + TECH_TYPE_2);
267         });
268     }
269
270     @Test
271     public void testOnlyDeviceDiscoveryResultsOfDevicesRemovedInTheCloudAreRemovedFromTheInbox()
272             throws InterruptedException {
273         // given:
274         DeviceState hoodDevice = createDeviceState(SERIAL_NUMBER, TECH_TYPE, DEVICE_NAME, DeviceType.HOOD,
275                 DEVICE_TYPE_NAME_HOOD);
276         DeviceState dishwasherDevice = createDeviceState(SERIAL_NUMBER_2, TECH_TYPE_2, DEVICE_NAME_2,
277                 DeviceType.DISHWASHER, DEVICE_TYPE_NAME_DISHWASHER);
278         getDiscoveryService().onDeviceStateUpdated(hoodDevice);
279         getDiscoveryService().onDeviceStateUpdated(dishwasherDevice);
280
281         Thread.sleep(10);
282
283         // when:
284         // This order of invocation is enforced by the webservice implementation.
285         getDiscoveryService().onDeviceRemoved(SERIAL_NUMBER_2);
286         getDiscoveryService().onDeviceStateUpdated(hoodDevice);
287
288         // then:
289         waitForAssert(() -> {
290             List<DiscoveryResult> results = getInbox().stream().collect(Collectors.toList());
291             assertEquals(1, results.size(), "Amount of things in inbox does not match expected number");
292
293             assertValidDiscoveryResult(HOOD_DEVICE_THING_UID, SERIAL_NUMBER, SERIAL_NUMBER, DEVICE_NAME,
294                     DEVICE_TYPE_NAME_HOOD + " " + TECH_TYPE);
295         });
296     }
297
298     @Test
299     public void testIfNoDeviceNameIsSetThenTheDiscoveryLabelIsTheDeviceTypePlusTheTechType() {
300         // given:
301         DeviceState deviceState = createDeviceState(SERIAL_NUMBER, TECH_TYPE, "", DeviceType.FRIDGE_FREEZER_COMBINATION,
302                 DEVICE_TYPE_NAME_FRIDGE_FREEZER);
303
304         // when:
305         getDiscoveryService().onDeviceStateUpdated(deviceState);
306
307         // then:
308         waitForAssert(() -> {
309             List<DiscoveryResult> results = getInbox().stream().collect(Collectors.toList());
310             assertEquals(1, results.size(), "Amount of things in inbox does not match expected number");
311
312             assertValidDiscoveryResult(FRIDGE_FREEZER_DEVICE_THING_UID, SERIAL_NUMBER, SERIAL_NUMBER,
313                     "Fridge Freezer " + TECH_TYPE, DEVICE_TYPE_NAME_FRIDGE_FREEZER + " " + TECH_TYPE);
314         });
315     }
316
317     @Test
318     public void testIfNeitherDeviceTypeNorDeviceNameAreSetThenTheDiscoveryModelIdAndTheLabelAreTheTechType() {
319         // given:
320         DeviceState deviceState = createDeviceState(SERIAL_NUMBER, TECH_TYPE, "", DeviceType.FRIDGE_FREEZER_COMBINATION,
321                 "");
322
323         // when:
324         getDiscoveryService().onDeviceStateUpdated(deviceState);
325
326         // then:
327         waitForAssert(() -> {
328             List<DiscoveryResult> results = getInbox().stream().collect(Collectors.toList());
329             assertEquals(1, results.size(), "Amount of things in inbox does not match expected number");
330
331             assertValidDiscoveryResult(FRIDGE_FREEZER_DEVICE_THING_UID, SERIAL_NUMBER, SERIAL_NUMBER, TECH_TYPE,
332                     TECH_TYPE);
333         });
334     }
335
336     @Test
337     public void testIfNeitherTechTypeNorDeviceNameAreSetThenTheDiscoveryModelIdAndTheLabelAreTheDeviceType() {
338         // given:
339         DeviceState deviceState = createDeviceState(SERIAL_NUMBER, "", "", DeviceType.FRIDGE_FREEZER_COMBINATION,
340                 DEVICE_TYPE_NAME_FRIDGE_FREEZER);
341
342         // when:
343         getDiscoveryService().onDeviceStateUpdated(deviceState);
344
345         // then:
346         waitForAssert(() -> {
347             List<DiscoveryResult> results = getInbox().stream().collect(Collectors.toList());
348             assertEquals(1, results.size(), "Amount of things in inbox does not match expected number");
349
350             assertValidDiscoveryResult(FRIDGE_FREEZER_DEVICE_THING_UID, SERIAL_NUMBER, SERIAL_NUMBER,
351                     DEVICE_TYPE_NAME_FRIDGE_FREEZER, DEVICE_TYPE_NAME_FRIDGE_FREEZER);
352         });
353     }
354
355     @Test
356     public void testIfNeitherTechTypeNorDeviceTypeNorDeviceNameAreSetThenTheDiscoveryModelIdIsUnknownAndTheLabelIsMieleDevice() {
357         // given:
358         DeviceState deviceState = createDeviceState(SERIAL_NUMBER, "", "", DeviceType.FRIDGE_FREEZER_COMBINATION, "");
359
360         // when:
361         getDiscoveryService().onDeviceStateUpdated(deviceState);
362
363         // then:
364         waitForAssert(() -> {
365             List<DiscoveryResult> results = getInbox().stream().collect(Collectors.toList());
366             assertEquals(1, results.size(), "Amount of things in inbox does not match expected number");
367
368             assertValidDiscoveryResult(FRIDGE_FREEZER_DEVICE_THING_UID, SERIAL_NUMBER, SERIAL_NUMBER, "Miele Device",
369                     "Unknown");
370         });
371     }
372
373     @Test
374     public void testIfNoSerialNumberIsSetThenTheDeviceIdentifierIsUsedAsReplacement() {
375         // given:
376         DeviceIdentLabel deviceIdentLabel = mock(DeviceIdentLabel.class);
377         when(deviceIdentLabel.getFabNumber()).thenReturn(Optional.of(""));
378         when(deviceIdentLabel.getTechType()).thenReturn(Optional.of(TECH_TYPE));
379
380         Type type = mock(Type.class);
381         when(type.getValueRaw()).thenReturn(DeviceType.FRIDGE_FREEZER_COMBINATION);
382         when(type.getValueLocalized()).thenReturn(Optional.of(DEVICE_TYPE_NAME_FRIDGE_FREEZER));
383
384         Ident ident = mock(Ident.class);
385         when(ident.getDeviceIdentLabel()).thenReturn(Optional.of(deviceIdentLabel));
386         when(ident.getType()).thenReturn(Optional.of(type));
387         when(ident.getDeviceName()).thenReturn(Optional.of(""));
388
389         Device device = mock(Device.class);
390         when(device.getIdent()).thenReturn(Optional.of(ident));
391         DeviceState deviceState = new DeviceState(SERIAL_NUMBER, device);
392
393         // when:
394         getDiscoveryService().onDeviceStateUpdated(deviceState);
395
396         // then:
397         waitForAssert(() -> {
398             List<DiscoveryResult> results = getInbox().stream().collect(Collectors.toList());
399             assertEquals(1, results.size(), "Amount of things in inbox does not match expected number");
400
401             assertValidDiscoveryResult(FRIDGE_FREEZER_DEVICE_THING_UID, SERIAL_NUMBER, SERIAL_NUMBER,
402                     DEVICE_TYPE_NAME_FRIDGE_FREEZER + " " + TECH_TYPE,
403                     DEVICE_TYPE_NAME_FRIDGE_FREEZER + " " + TECH_TYPE);
404         });
405     }
406 }