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