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