]> git.basschouten.com Git - openhab-addons.git/blob
07b85168206f72f9c86a0f0b0f7b6cf01b16e468
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.bluetooth.discovery.internal;
14
15 import static org.hamcrest.CoreMatchers.*;
16 import static org.hamcrest.MatcherAssert.assertThat;
17 import static org.junit.jupiter.api.Assertions.*;
18
19 import java.util.Collections;
20 import java.util.List;
21 import java.util.Set;
22 import java.util.concurrent.CountDownLatch;
23 import java.util.concurrent.atomic.AtomicInteger;
24 import java.util.function.BiConsumer;
25
26 import org.apache.commons.lang.RandomStringUtils;
27 import org.eclipse.jdt.annotation.NonNull;
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.Disabled;
32 import org.junit.jupiter.api.Test;
33 import org.junit.jupiter.api.extension.ExtendWith;
34 import org.mockito.ArgumentCaptor;
35 import org.mockito.ArgumentMatchers;
36 import org.mockito.Mock;
37 import org.mockito.Mockito;
38 import org.mockito.Spy;
39 import org.mockito.junit.jupiter.MockitoExtension;
40 import org.mockito.junit.jupiter.MockitoSettings;
41 import org.mockito.quality.Strictness;
42 import org.openhab.binding.bluetooth.BluetoothAdapter;
43 import org.openhab.binding.bluetooth.BluetoothAddress;
44 import org.openhab.binding.bluetooth.BluetoothBindingConstants;
45 import org.openhab.binding.bluetooth.BluetoothCharacteristic.GattCharacteristic;
46 import org.openhab.binding.bluetooth.BluetoothDevice;
47 import org.openhab.binding.bluetooth.MockBluetoothAdapter;
48 import org.openhab.binding.bluetooth.MockBluetoothDevice;
49 import org.openhab.binding.bluetooth.TestUtils;
50 import org.openhab.binding.bluetooth.discovery.BluetoothDiscoveryDevice;
51 import org.openhab.binding.bluetooth.discovery.BluetoothDiscoveryParticipant;
52 import org.openhab.binding.bluetooth.notification.BluetoothConnectionStatusNotification;
53 import org.openhab.core.config.discovery.DiscoveryListener;
54 import org.openhab.core.config.discovery.DiscoveryResult;
55 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
56 import org.openhab.core.thing.ThingTypeUID;
57 import org.openhab.core.thing.ThingUID;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
60
61 /**
62  * Tests {@link BluetoothDiscoveryService}.
63  *
64  * @author Connor Petty - Initial contribution
65  */
66 @ExtendWith(MockitoExtension.class)
67 @MockitoSettings(strictness = Strictness.WARN)
68 @NonNullByDefault
69 @Disabled("Needs to be updated for OH3")
70 public class BluetoothDiscoveryServiceTest {
71
72     private static final int TIMEOUT = 2000;
73
74     private final Logger logger = LoggerFactory.getLogger(BluetoothDiscoveryServiceTest.class);
75
76     private @NonNullByDefault({}) BluetoothDiscoveryService discoveryService;
77
78     private @Spy @NonNullByDefault({}) MockDiscoveryParticipant participant1 = new MockDiscoveryParticipant();
79     private @Mock @NonNullByDefault({}) DiscoveryListener mockDiscoveryListener;
80
81     @BeforeEach
82     public void setup() {
83         discoveryService = new BluetoothDiscoveryService();
84         discoveryService.addDiscoveryListener(mockDiscoveryListener);
85         discoveryService.addBluetoothDiscoveryParticipant(participant1);
86     }
87
88     @Test
89     public void ignoreDuplicateTest() {
90         BluetoothAdapter mockAdapter1 = new MockBluetoothAdapter();
91         BluetoothDevice device = mockAdapter1.getDevice(TestUtils.randomAddress());
92         discoveryService.deviceDiscovered(device);
93         // this second call should not produce another result
94         discoveryService.deviceDiscovered(device);
95
96         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(1)).thingDiscovered(
97                 ArgumentMatchers.same(discoveryService),
98                 ArgumentMatchers.argThat(arg -> arg.getThingTypeUID().equals(participant1.typeUID)));
99     }
100
101     @Test
102     public void ignoreOtherDuplicateTest() {
103         BluetoothAdapter mockAdapter1 = new MockBluetoothAdapter();
104         BluetoothAdapter mockAdapter2 = new MockBluetoothAdapter();
105         BluetoothAddress address = TestUtils.randomAddress();
106         BluetoothDevice device1 = mockAdapter1.getDevice(address);
107         BluetoothDevice device2 = mockAdapter2.getDevice(address);
108         discoveryService.deviceDiscovered(device1);
109         discoveryService.deviceDiscovered(device2);
110         // this should not produce another result
111         discoveryService.deviceDiscovered(device1);
112
113         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(2)).thingDiscovered(
114                 ArgumentMatchers.same(discoveryService),
115                 ArgumentMatchers.argThat(arg -> arg.getThingTypeUID().equals(participant1.typeUID)));
116     }
117
118     @Test
119     public void ignoreRssiDuplicateTest() {
120         MockBluetoothAdapter mockAdapter1 = new MockBluetoothAdapter();
121         MockBluetoothDevice device = mockAdapter1.getDevice(TestUtils.randomAddress());
122         discoveryService.deviceDiscovered(device);
123         // changing the rssi should not result in a new discovery
124         device.setRssi(100);
125         discoveryService.deviceDiscovered(device);
126
127         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(1)).thingDiscovered(
128                 ArgumentMatchers.same(discoveryService),
129                 ArgumentMatchers.argThat(arg -> arg.getThingTypeUID().equals(participant1.typeUID)));
130     }
131
132     @Test
133     public void nonDuplicateNameTest() throws InterruptedException {
134         MockBluetoothAdapter mockAdapter1 = new MockBluetoothAdapter();
135         MockBluetoothDevice device = mockAdapter1.getDevice(TestUtils.randomAddress());
136         discoveryService.deviceDiscovered(device);
137         // this second call should produce another result
138         device.setName("sdfad");
139         discoveryService.deviceDiscovered(device);
140
141         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(2)).thingDiscovered(
142                 ArgumentMatchers.same(discoveryService),
143                 ArgumentMatchers.argThat(arg -> arg.getThingTypeUID().equals(participant1.typeUID)));
144     }
145
146     @Test
147     public void nonDuplicateTxPowerTest() {
148         MockBluetoothAdapter mockAdapter1 = new MockBluetoothAdapter();
149         MockBluetoothDevice device = mockAdapter1.getDevice(TestUtils.randomAddress());
150         discoveryService.deviceDiscovered(device);
151         // this second call should produce another result
152         device.setTxPower(10);
153         discoveryService.deviceDiscovered(device);
154
155         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(2)).thingDiscovered(
156                 ArgumentMatchers.same(discoveryService),
157                 ArgumentMatchers.argThat(arg -> arg.getThingTypeUID().equals(participant1.typeUID)));
158     }
159
160     @Test
161     public void nonDuplicateManufacturerIdTest() {
162         MockBluetoothAdapter mockAdapter1 = new MockBluetoothAdapter();
163         MockBluetoothDevice device = mockAdapter1.getDevice(TestUtils.randomAddress());
164         discoveryService.deviceDiscovered(device);
165         // this second call should produce another result
166         device.setManufacturerId(100);
167         discoveryService.deviceDiscovered(device);
168
169         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(2)).thingDiscovered(
170                 ArgumentMatchers.same(discoveryService),
171                 ArgumentMatchers.argThat(arg -> arg.getThingTypeUID().equals(participant1.typeUID)));
172     }
173
174     @Test
175     public void useResultFromAnotherAdapterTest() {
176         BluetoothAdapter mockAdapter1 = new MockBluetoothAdapter();
177         BluetoothAdapter mockAdapter2 = new MockBluetoothAdapter();
178         BluetoothAddress address = TestUtils.randomAddress();
179
180         discoveryService.deviceDiscovered(mockAdapter1.getDevice(address));
181         discoveryService.deviceDiscovered(mockAdapter2.getDevice(address));
182
183         ArgumentCaptor<DiscoveryResult> resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class);
184         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(2))
185                 .thingDiscovered(ArgumentMatchers.same(discoveryService), resultCaptor.capture());
186
187         List<DiscoveryResult> results = resultCaptor.getAllValues();
188         DiscoveryResult result1 = results.get(0);
189         DiscoveryResult result2 = results.get(1);
190
191         assertNotEquals(result1.getBridgeUID(), result2.getBridgeUID());
192         assertThat(result1.getBridgeUID(), anyOf(is(mockAdapter1.getUID()), is(mockAdapter2.getUID())));
193         assertThat(result2.getBridgeUID(), anyOf(is(mockAdapter1.getUID()), is(mockAdapter2.getUID())));
194         assertEquals(result1.getThingUID().getId(), result2.getThingUID().getId());
195         assertEquals(result1.getLabel(), result2.getLabel());
196         assertEquals(result1.getRepresentationProperty(), result2.getRepresentationProperty());
197     }
198
199     @Test
200     public void connectionParticipantTest() {
201         Mockito.doReturn(true).when(participant1).requiresConnection(ArgumentMatchers.any());
202         BluetoothAddress address = TestUtils.randomAddress();
203
204         MockBluetoothAdapter mockAdapter1 = new MockBluetoothAdapter();
205         MockBluetoothDevice mockDevice = mockAdapter1.getDevice(address);
206         String deviceName = RandomStringUtils.randomAlphanumeric(10);
207         mockDevice.setDeviceName(deviceName);
208
209         BluetoothDevice device = Mockito.spy(mockDevice);
210
211         discoveryService.deviceDiscovered(device);
212
213         Mockito.verify(device, Mockito.timeout(TIMEOUT).times(1)).connect();
214         Mockito.verify(device, Mockito.timeout(TIMEOUT).times(1)).readCharacteristic(
215                 ArgumentMatchers.argThat(ch -> ch.getGattCharacteristic() == GattCharacteristic.DEVICE_NAME));
216         Mockito.verify(device, Mockito.timeout(TIMEOUT).times(1)).disconnect();
217
218         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(1)).thingDiscovered(
219                 ArgumentMatchers.same(discoveryService),
220                 ArgumentMatchers.argThat(arg -> arg.getThingTypeUID().equals(participant1.typeUID)
221                         && arg.getThingUID().getId().equals(deviceName)));
222     }
223
224     @Test
225     public void multiDiscoverySingleConnectionTest() {
226         Mockito.doReturn(true).when(participant1).requiresConnection(ArgumentMatchers.any());
227         BluetoothAddress address = TestUtils.randomAddress();
228
229         MockBluetoothAdapter mockAdapter1 = new MockBluetoothAdapter();
230         MockBluetoothAdapter mockAdapter2 = new MockBluetoothAdapter();
231         MockBluetoothDevice mockDevice1 = mockAdapter1.getDevice(address);
232         MockBluetoothDevice mockDevice2 = mockAdapter2.getDevice(address);
233         String deviceName = RandomStringUtils.randomAlphanumeric(10);
234         mockDevice1.setDeviceName(deviceName);
235         mockDevice2.setDeviceName(deviceName);
236
237         BluetoothDevice device1 = Mockito.spy(mockDevice1);
238         BluetoothDevice device2 = Mockito.spy(mockDevice2);
239
240         discoveryService.deviceDiscovered(device1);
241
242         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(1)).thingDiscovered(
243                 ArgumentMatchers.same(discoveryService),
244                 ArgumentMatchers.argThat(arg -> arg.getThingTypeUID().equals(participant1.typeUID)
245                         && mockAdapter1.getUID().equals(arg.getBridgeUID())
246                         && arg.getThingUID().getId().equals(deviceName)));
247
248         Mockito.verify(device1, Mockito.times(1)).connect();
249         Mockito.verify(device1, Mockito.times(1)).readCharacteristic(
250                 ArgumentMatchers.argThat(ch -> ch.getGattCharacteristic() == GattCharacteristic.DEVICE_NAME));
251         Mockito.verify(device1, Mockito.times(1)).disconnect();
252
253         discoveryService.deviceDiscovered(device2);
254
255         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(1)).thingDiscovered(
256                 ArgumentMatchers.same(discoveryService),
257                 ArgumentMatchers.argThat(arg -> arg.getThingTypeUID().equals(participant1.typeUID)
258                         && mockAdapter2.getUID().equals(arg.getBridgeUID())
259                         && arg.getThingUID().getId().equals(deviceName)));
260
261         Mockito.verify(device2, Mockito.never()).connect();
262         Mockito.verify(device2, Mockito.never()).readCharacteristic(
263                 ArgumentMatchers.argThat(ch -> ch.getGattCharacteristic() == GattCharacteristic.DEVICE_NAME));
264         Mockito.verify(device2, Mockito.never()).disconnect();
265     }
266
267     @Test
268     public void nonConnectionParticipantTest() {
269         MockBluetoothAdapter mockAdapter1 = new MockBluetoothAdapter();
270         MockBluetoothDevice mockDevice = mockAdapter1.getDevice(TestUtils.randomAddress());
271         String deviceName = RandomStringUtils.randomAlphanumeric(10);
272         mockDevice.setDeviceName(deviceName);
273
274         BluetoothDevice device = Mockito.spy(mockDevice);
275
276         discoveryService.deviceDiscovered(device);
277
278         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(1)).thingDiscovered(
279                 ArgumentMatchers.same(discoveryService),
280                 ArgumentMatchers.argThat(arg -> arg.getThingTypeUID().equals(participant1.typeUID)
281                         && !arg.getThingUID().getId().equals(deviceName)));
282         Mockito.verify(device, Mockito.never()).connect();
283         Mockito.verify(device, Mockito.never()).readCharacteristic(
284                 ArgumentMatchers.argThat(ch -> ch.getGattCharacteristic() == GattCharacteristic.DEVICE_NAME));
285         Mockito.verify(device, Mockito.never()).disconnect();
286     }
287
288     @Test
289     public void defaultResultTest() {
290         Mockito.doReturn(null).when(participant1).createResult(ArgumentMatchers.any());
291         BluetoothAdapter mockAdapter1 = new MockBluetoothAdapter();
292         BluetoothDevice device = mockAdapter1.getDevice(TestUtils.randomAddress());
293         discoveryService.deviceDiscovered(device);
294
295         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(1))
296                 .thingDiscovered(ArgumentMatchers.same(discoveryService), ArgumentMatchers
297                         .argThat(arg -> arg.getThingTypeUID().equals(BluetoothBindingConstants.THING_TYPE_BEACON)));
298     }
299
300     @Test
301     public void removeDefaultDeviceTest() {
302         Mockito.doReturn(null).when(participant1).createResult(ArgumentMatchers.any());
303         BluetoothAdapter mockAdapter1 = new MockBluetoothAdapter();
304         BluetoothDevice device = mockAdapter1.getDevice(TestUtils.randomAddress());
305         discoveryService.deviceDiscovered(device);
306         discoveryService.deviceRemoved(device);
307
308         ArgumentCaptor<DiscoveryResult> resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class);
309         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(1))
310                 .thingDiscovered(ArgumentMatchers.same(discoveryService), resultCaptor.capture());
311
312         DiscoveryResult result = resultCaptor.getValue();
313
314         assertEquals(BluetoothBindingConstants.THING_TYPE_BEACON, result.getThingTypeUID());
315
316         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(1)).thingRemoved(
317                 ArgumentMatchers.same(discoveryService),
318                 ArgumentMatchers.argThat(arg -> arg.equals(result.getThingUID())));
319     }
320
321     @Test
322     public void removeUpdatedDefaultDeviceTest() {
323         Mockito.doReturn(null).when(participant1).createResult(ArgumentMatchers.any());
324         MockBluetoothAdapter mockAdapter1 = new MockBluetoothAdapter();
325         MockBluetoothDevice device = mockAdapter1.getDevice(TestUtils.randomAddress());
326         discoveryService.deviceDiscovered(device);
327         device.setName("somename");
328         discoveryService.deviceDiscovered(device);
329
330         ArgumentCaptor<DiscoveryResult> resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class);
331         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(2))
332                 .thingDiscovered(ArgumentMatchers.same(discoveryService), resultCaptor.capture());
333
334         DiscoveryResult result = resultCaptor.getValue();
335
336         assertEquals(BluetoothBindingConstants.THING_TYPE_BEACON, result.getThingTypeUID());
337
338         discoveryService.deviceRemoved(device);
339
340         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(1)).thingRemoved(
341                 ArgumentMatchers.same(discoveryService),
342                 ArgumentMatchers.argThat(arg -> arg.equals(result.getThingUID())));
343     }
344
345     @Test
346     public void bluezConnectionTimeoutTest() {
347         Mockito.doReturn(true).when(participant1).requiresConnection(ArgumentMatchers.any());
348
349         BluetoothAdapter mockAdapter1 = new MockBluetoothAdapter();
350         BadConnectionDevice device = new BadConnectionDevice(mockAdapter1, TestUtils.randomAddress(), 100);
351         discoveryService.deviceDiscovered(device);
352
353         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(1))
354                 .thingDiscovered(ArgumentMatchers.same(discoveryService), ArgumentMatchers
355                         .argThat(arg -> arg.getThingTypeUID().equals(BluetoothBindingConstants.THING_TYPE_BEACON)));
356     }
357
358     @Test
359     public void replaceOlderDiscoveryTest() {
360         Mockito.doReturn(null).when(participant1).createResult(ArgumentMatchers.any());
361
362         MockBluetoothAdapter mockAdapter1 = new MockBluetoothAdapter();
363         MockBluetoothDevice device = mockAdapter1.getDevice(TestUtils.randomAddress());
364
365         MockDiscoveryParticipant participant2 = new MockDiscoveryParticipant() {
366             @Override
367             public @Nullable DiscoveryResult createResult(BluetoothDiscoveryDevice device) {
368                 Integer manufacturer = device.getManufacturerId();
369                 if (manufacturer != null && manufacturer.equals(10)) {
370                     // without a device name it should produce a random ThingUID
371                     return super.createResult(device);
372                 }
373                 return null;
374             }
375         };
376
377         discoveryService.addBluetoothDiscoveryParticipant(participant2);
378
379         // lets start with producing a default result
380         discoveryService.deviceDiscovered(device);
381
382         ArgumentCaptor<DiscoveryResult> resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class);
383         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(1))
384                 .thingDiscovered(ArgumentMatchers.same(discoveryService), resultCaptor.capture());
385
386         DiscoveryResult result = resultCaptor.getValue();
387
388         assertEquals(BluetoothBindingConstants.THING_TYPE_BEACON, result.getThingTypeUID());
389
390         device.setManufacturerId(10);
391
392         // lets start with producing a default result
393         discoveryService.deviceDiscovered(device);
394
395         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(1)).thingRemoved(
396                 ArgumentMatchers.same(discoveryService),
397                 ArgumentMatchers.argThat(arg -> arg.equals(result.getThingUID())));
398
399         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(1)).thingDiscovered(
400                 ArgumentMatchers.same(discoveryService),
401                 ArgumentMatchers.argThat(arg -> arg.getThingTypeUID().equals(participant2.typeUID)));
402     }
403
404     @Test
405     public void recursiveFutureTest() throws InterruptedException {
406         /*
407          * 1. deviceDiscovered(device1)
408          * 2. cause discovery to pause at participant1
409          * participant1 should make a field non-null for device1 upon unpause
410          * 3. make the same field non-null for device2
411          * 4. deviceDiscovered(device2)
412          * this discovery should be waiting for first discovery to finish
413          * 5. unpause participant
414          * End result:
415          * - participant should only have been called once
416          * - thingDiscovered should have been called twice
417          */
418         Mockito.doReturn(null).when(participant1).createResult(ArgumentMatchers.any());
419
420         AtomicInteger callCount = new AtomicInteger(0);
421         final CountDownLatch pauseLatch = new CountDownLatch(1);
422
423         BluetoothAddress address = TestUtils.randomAddress();
424         MockBluetoothAdapter mockAdapter1 = new MockBluetoothAdapter();
425         MockBluetoothAdapter mockAdapter2 = new MockBluetoothAdapter();
426         MockBluetoothDevice mockDevice1 = mockAdapter1.getDevice(address);
427         MockBluetoothDevice mockDevice2 = mockAdapter2.getDevice(address);
428         String deviceName = RandomStringUtils.randomAlphanumeric(10);
429
430         MockDiscoveryParticipant participant2 = new MockDiscoveryParticipant() {
431             @Override
432             public @Nullable DiscoveryResult createResult(BluetoothDiscoveryDevice device) {
433                 try {
434                     pauseLatch.await();
435                 } catch (InterruptedException e) {
436                     // do nothing
437                 }
438                 ((BluetoothDeviceSnapshot) device).setName(deviceName);
439                 callCount.incrementAndGet();
440                 return super.createResult(device);
441             }
442         };
443
444         discoveryService.addBluetoothDiscoveryParticipant(participant2);
445
446         discoveryService.deviceDiscovered(mockDevice1);
447         mockDevice2.setName(deviceName);
448         discoveryService.deviceDiscovered(mockDevice2);
449
450         pauseLatch.countDown();
451
452         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(2)).thingDiscovered(
453                 ArgumentMatchers.same(discoveryService),
454                 ArgumentMatchers.argThat(arg -> arg.getThingTypeUID().equals(participant2.typeUID)));
455
456         assertEquals(1, callCount.get());
457     }
458
459     @Test
460     public void roamingDiscoveryTest() {
461         RoamingDiscoveryParticipant roamingParticipant = new RoamingDiscoveryParticipant();
462         MockBluetoothAdapter roamingAdapter = roamingParticipant.roamingAdapter;
463         discoveryService.addBluetoothDiscoveryParticipant(roamingParticipant);
464
465         BluetoothAdapter mockAdapter1 = new MockBluetoothAdapter();
466         BluetoothDevice device = mockAdapter1.getDevice(TestUtils.randomAddress());
467         discoveryService.deviceDiscovered(device);
468
469         ArgumentCaptor<DiscoveryResult> resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class);
470         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(2))
471                 .thingDiscovered(ArgumentMatchers.same(discoveryService), resultCaptor.capture());
472
473         List<DiscoveryResult> results = resultCaptor.getAllValues();
474         DiscoveryResult result1 = results.get(0);
475         DiscoveryResult result2 = results.get(1);
476
477         assertNotEquals(result1.getBridgeUID(), result2.getBridgeUID());
478         assertThat(result1.getBridgeUID(), anyOf(is(mockAdapter1.getUID()), is(roamingAdapter.getUID())));
479         assertThat(result2.getBridgeUID(), anyOf(is(mockAdapter1.getUID()), is(roamingAdapter.getUID())));
480         assertEquals(result1.getThingUID().getId(), result2.getThingUID().getId());
481         assertEquals(result1.getLabel(), result2.getLabel());
482         assertEquals(result1.getRepresentationProperty(), result2.getRepresentationProperty());
483     }
484
485     @Test
486     public void roamingDiscoveryRetractionTest() {
487         RoamingDiscoveryParticipant roamingParticipant = new RoamingDiscoveryParticipant();
488         MockBluetoothAdapter roamingAdapter = roamingParticipant.roamingAdapter;
489         discoveryService.addBluetoothDiscoveryParticipant(roamingParticipant);
490
491         MockBluetoothAdapter mockAdapter1 = new MockBluetoothAdapter();
492         MockBluetoothDevice device = mockAdapter1.getDevice(TestUtils.randomAddress());
493         discoveryService.deviceDiscovered(device);
494         device.setName("dasf");
495         discoveryService.deviceDiscovered(device);
496
497         ArgumentCaptor<ThingUID> resultCaptor = ArgumentCaptor.forClass(ThingUID.class);
498         Mockito.verify(mockDiscoveryListener, Mockito.timeout(TIMEOUT).times(2))
499                 .thingRemoved(ArgumentMatchers.same(discoveryService), resultCaptor.capture());
500
501         List<ThingUID> results = resultCaptor.getAllValues();
502         ThingUID result1 = results.get(0);
503         ThingUID result2 = results.get(1);
504
505         assertNotEquals(result1.getBridgeIds(), result2.getBridgeIds());
506         assertThat(result1.getBridgeIds().get(0),
507                 anyOf(is(mockAdapter1.getUID().getId()), is(roamingAdapter.getUID().getId())));
508         assertThat(result2.getBridgeIds().get(0),
509                 anyOf(is(mockAdapter1.getUID().getId()), is(roamingAdapter.getUID().getId())));
510         assertEquals(result1.getId(), result2.getId());
511     }
512
513     private class RoamingDiscoveryParticipant implements BluetoothDiscoveryParticipant {
514
515         private MockBluetoothAdapter roamingAdapter = new MockBluetoothAdapter();
516
517         @Override
518         public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
519             return Collections.emptySet();
520         }
521
522         @Override
523         public @Nullable DiscoveryResult createResult(BluetoothDiscoveryDevice device) {
524             return null;
525         }
526
527         @Override
528         public @Nullable ThingUID getThingUID(BluetoothDiscoveryDevice device) {
529             return null;
530         }
531
532         @Override
533         public void publishAdditionalResults(DiscoveryResult result,
534                 BiConsumer<BluetoothAdapter, DiscoveryResult> publisher) {
535             publisher.accept(roamingAdapter, result);
536         }
537     }
538
539     private class MockDiscoveryParticipant implements BluetoothDiscoveryParticipant {
540
541         private ThingTypeUID typeUID;
542
543         public MockDiscoveryParticipant() {
544             this.typeUID = new ThingTypeUID("mock", RandomStringUtils.randomAlphabetic(6));
545         }
546
547         @Override
548         public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
549             return Collections.singleton(typeUID);
550         }
551
552         @Override
553         public @Nullable DiscoveryResult createResult(BluetoothDiscoveryDevice device) {
554             return DiscoveryResultBuilder.create(getThingUID(device)).withLabel(RandomStringUtils.randomAlphabetic(6))
555                     .withRepresentationProperty(RandomStringUtils.randomAlphabetic(6))
556                     .withBridge(device.getAdapter().getUID()).build();
557         }
558
559         @Override
560         public @NonNull ThingUID getThingUID(BluetoothDiscoveryDevice device) {
561             String deviceName = device.getName();
562             String id = deviceName != null ? deviceName : RandomStringUtils.randomAlphabetic(6);
563             return new ThingUID(typeUID, device.getAdapter().getUID(), id);
564         }
565     }
566
567     private class BadConnectionDevice extends MockBluetoothDevice {
568
569         private int sleepTime;
570
571         public BadConnectionDevice(BluetoothAdapter adapter, BluetoothAddress address, int sleepTime) {
572             super(adapter, address);
573             this.sleepTime = sleepTime;
574         }
575
576         @Override
577         public boolean connect() {
578             notifyListeners(BluetoothEventType.CONNECTION_STATE,
579                     new BluetoothConnectionStatusNotification(ConnectionState.CONNECTED));
580             try {
581                 Thread.sleep(sleepTime);
582             } catch (InterruptedException e) {
583                 // do nothing
584             }
585             notifyListeners(BluetoothEventType.CONNECTION_STATE,
586                     new BluetoothConnectionStatusNotification(ConnectionState.DISCONNECTED));
587             return false;
588         }
589     }
590 }