package org.sputnikdev.bluetooth.manager.impl; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InOrder; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.internal.util.reflection.Whitebox; import org.powermock.api.mockito.PowerMockito; import org.powermock.modules.junit4.PowerMockRunner; import org.sputnikdev.bluetooth.Filter; import org.sputnikdev.bluetooth.RssiKalmanFilter; import org.sputnikdev.bluetooth.URL; import org.sputnikdev.bluetooth.manager.AdapterGovernor; import org.sputnikdev.bluetooth.manager.BluetoothGovernor; import org.sputnikdev.bluetooth.manager.BluetoothObjectType; import org.sputnikdev.bluetooth.manager.BluetoothObjectVisitor; import org.sputnikdev.bluetooth.manager.BluetoothSmartDeviceListener; import org.sputnikdev.bluetooth.manager.CharacteristicGovernor; import org.sputnikdev.bluetooth.manager.DeviceGovernor; import org.sputnikdev.bluetooth.manager.GattCharacteristic; import org.sputnikdev.bluetooth.manager.GattService; import org.sputnikdev.bluetooth.manager.GenericBluetoothDeviceListener; import org.sputnikdev.bluetooth.manager.transport.BluetoothObjectFactory; import org.sputnikdev.bluetooth.manager.transport.Characteristic; import org.sputnikdev.bluetooth.manager.transport.Device; import org.sputnikdev.bluetooth.manager.transport.Notification; import org.sputnikdev.bluetooth.manager.transport.Service; import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.function.Function; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyList; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @RunWith(PowerMockRunner.class) public class DeviceGovernorImplTest { private static final int BLUETOOTH_CLASS = 0; private static final String ALIAS = "device alias"; private static final String NAME = "device name"; private static final short RSSI = -90; @Mock private static final URL URL = new URL("/11:22:33:44:55:66/12:34:56:78:90:12"); private static final String SERVICE_1 = "0000180f-0000-1000-8000-00805f9b34fb"; private static final URL SERVICE_1_URL = URL.copyWith(SERVICE_1, null); private static final URL CHARACTERISTIC_1_URL = URL.copyWith(SERVICE_1, "00002a19-0000-1000-8000-00805f9b34fb"); private static final URL CHARACTERISTIC_2_URL = URL.copyWith(SERVICE_1, "00002a20-0000-1000-8000-00805f9b34fb"); @Mock(name = "bluetoothObject") private Device device; private BluetoothManagerImpl bluetoothManager = mock(BluetoothManagerImpl.class); @Mock private GenericBluetoothDeviceListener genericDeviceListener; @Mock private BluetoothSmartDeviceListener bluetoothSmartDeviceListener; @Mock private BluetoothObjectFactory bluetoothObjectFactory; @Mock private AdapterGovernorImpl adapterGovernor; @Mock private Filter<Short> rssiFilter; @Spy @InjectMocks private DeviceGovernorImpl governor = new DeviceGovernorImpl(bluetoothManager, URL); @Captor private ArgumentCaptor<Notification<Short>> rssiCaptor; @Captor private ArgumentCaptor<Notification<Boolean>> blockedCaptor; @Captor private ArgumentCaptor<Notification<Boolean>> connectedCaptor; @Captor private ArgumentCaptor<Notification<Boolean>> servicesResolvedCaptor; @Captor private ArgumentCaptor<Notification<Map<String, byte[]>>> serviceDataCaptor; @Captor private ArgumentCaptor<Notification<Map<Short, byte[]>>> manufacturerDataCaptor; @Before public void setUp() throws Exception { // not sure why, but adapter does not get injected properly, hence a workaround here: Whitebox.setInternalState(governor, "bluetoothObject", device); doNothing().when(device).enableRSSINotifications(rssiCaptor.capture()); doNothing().when(device).enableBlockedNotifications(blockedCaptor.capture()); doNothing().when(device).enableConnectedNotifications(connectedCaptor.capture()); doNothing().when(device).enableServicesResolvedNotifications(servicesResolvedCaptor.capture()); doNothing().when(device).enableServiceDataNotifications(serviceDataCaptor.capture()); doNothing().when(device).enableManufacturerDataNotifications(manufacturerDataCaptor.capture()); governor.addGenericBluetoothDeviceListener(genericDeviceListener); governor.addBluetoothSmartDeviceListener(bluetoothSmartDeviceListener); when(bluetoothManager.getFactory(any())).thenReturn(bluetoothObjectFactory); when(bluetoothObjectFactory.getDevice(URL)).thenReturn(device); when(device.getBluetoothClass()).thenReturn(BLUETOOTH_CLASS); when(device.getAlias()).thenReturn(ALIAS); when(device.getName()).thenReturn(NAME); List<Service> services = new ArrayList<>(); List<Characteristic> characteristics = new ArrayList<>(); Service gattService = mock(Service.class); when(gattService.getURL()).thenReturn(SERVICE_1_URL); services.add(gattService); Characteristic gattCharacteristic = mock(Characteristic.class); characteristics.add(gattCharacteristic); when(gattCharacteristic.getURL()).thenReturn(CHARACTERISTIC_1_URL); gattCharacteristic = mock(Characteristic.class); characteristics.add(gattCharacteristic); when(gattCharacteristic.getURL()).thenReturn(CHARACTERISTIC_2_URL); when(gattService.getCharacteristics()).thenReturn(characteristics); when(device.getServices()).thenReturn(services); List<BluetoothGovernor> charGovernors = new ArrayList<>(); charGovernors.add(mockCharacteristicGovernor(CHARACTERISTIC_1_URL)); charGovernors.add(mockCharacteristicGovernor(CHARACTERISTIC_2_URL)); when(bluetoothManager.getGovernors(any())).thenReturn(charGovernors); adapterGovernor = mock(AdapterGovernorImpl.class); when(adapterGovernor.isReady()).thenReturn(true); when(adapterGovernor.isPowered()).thenReturn(true); when(bluetoothManager.getAdapterGovernor(URL)).thenReturn(adapterGovernor); governor.setRssiFilter(null); governor.setRssiReportingRate(0); PowerMockito.when(governor, "createFilter", any(Class.class)).thenReturn(rssiFilter); MockUtils.mockImplicitNotifications(bluetoothManager); } @Test public void testInit() { when(device.isConnected()).thenReturn(true); when(device.isServicesResolved()).thenReturn(true); governor.init(device); verify(device).enableRSSINotifications(rssiCaptor.getValue()); verify(device).enableBlockedNotifications(blockedCaptor.getValue()); verify(device).enableConnectedNotifications(connectedCaptor.getValue()); verify(device).enableServicesResolvedNotifications(servicesResolvedCaptor.getValue()); verify(device).enableServiceDataNotifications(serviceDataCaptor.getValue()); verify(device).enableManufacturerDataNotifications(manufacturerDataCaptor.getValue()); verify(device).isConnected(); verify(device).isServicesResolved(); verify(device).getServices(); verify(bluetoothSmartDeviceListener).connected(); verify(bluetoothSmartDeviceListener).servicesResolved(anyList()); verify(bluetoothSmartDeviceListener).authenticated(); verifyNoMoreInteractions(device, genericDeviceListener, bluetoothSmartDeviceListener); } @Test public void testUpdateBlocked() { governor.setBlockedControl(false); when(device.isBlocked()).thenReturn(false); governor.update(device); verify(device, never()).setBlocked(false); governor.setBlockedControl(false); when(device.isBlocked()).thenReturn(true); governor.update(device); verify(device, times(1)).setBlocked(false); governor.setBlockedControl(true); when(device.isBlocked()).thenReturn(true); governor.update(device); verify(device, never()).setBlocked(true); governor.setBlockedControl(true); when(device.isBlocked()).thenReturn(false); governor.update(device); verify(device, times(1)).setBlocked(true); } @Test public void testUpdateConnectedLastChanged() { // this test verifies if "lastChanged" gets updated // fixed variables governor.setBlockedControl(false); when(device.isBlocked()).thenReturn(false); when(device.connect()).thenReturn(true); short rssi = -77; when(device.getRSSI()).thenReturn(rssi); // not connected when(device.isConnected()).thenReturn(false); governor.setConnectionControl(false); Instant lastChanged = governor.getLastInteracted(); assertNull(lastChanged); governor.update(device); // nothing should be changed lastChanged = governor.getLastInteracted(); assertNull(lastChanged); verify(device, never()).getRSSI(); verify(genericDeviceListener, never()).rssiChanged(rssi); assertNull(lastChanged); // connected when(device.isConnected()).thenReturn(true); governor.setConnectionControl(true); governor.update(device); lastChanged = governor.getLastInteracted(); assertNotNull(lastChanged); // when connected, "lastChanged" should always be updated verify(device, times(1)).getRSSI(); verify(genericDeviceListener, times(1)).rssiChanged(rssi); } @Test public void testUpdateConnected() { // this test checks if native device gets updated in accordance with various combination of: // connection control and the native device connection status doReturn(true).when(governor).isBleEnabled(); doReturn(false).when(governor).isOnline(); governor.setBlockedControl(false); when(device.isBlocked()).thenReturn(false); when(device.connect()).thenReturn(true); // not connected and control == false when(device.isConnected()).thenReturn(false); governor.setConnectionControl(false); governor.update(device); verify(device, never()).connect(); verify(device, never()).disconnect(); // connected and control == true when(device.isConnected()).thenReturn(true); governor.setConnectionControl(true); governor.update(device); verify(device, never()).connect(); verify(device, never()).disconnect(); // connected and control == false when(device.isConnected()).thenReturn(true); when(device.disconnect()).thenReturn(true); governor.setConnectionControl(false); governor.update(device); verify(device, never()).connect(); verify(device).disconnect(); // not connected and control == true, but it is offline when(device.isConnected()).thenReturn(false, true); governor.setConnectionControl(true); governor.update(device); verify(device, never()).connect(); verify(device).disconnect(); // not connected and control == true, and now it is online when(device.isConnected()).thenReturn(false, true); governor.setConnectionControl(true); doReturn(true).when(governor).isOnline(); governor.update(device); verify(device).connect(); verify(device).disconnect(); } @Test public void testUpdateConnectAndBlock() { doReturn(true).when(governor).isBleEnabled(); doReturn(true).when(governor).isOnline(); when(device.connect()).thenReturn(true); governor.setBlockedControl(true); when(device.isConnected()).thenReturn(false); governor.setConnectionControl(true); governor.update(device); verify(device, never()).connect(); verify(device, never()).disconnect(); when(device.isConnected()).thenReturn(true); governor.setConnectionControl(false); governor.update(device); verify(device, never()).connect(); verify(device, never()).disconnect(); governor.setBlockedControl(false); when(device.isConnected()).thenReturn(true); when(device.disconnect()).thenReturn(true); governor.setConnectionControl(false); governor.update(device); verify(device, never()).connect(); verify(device).disconnect(); when(device.isConnected()).thenReturn(false, true); governor.setConnectionControl(true); governor.update(device); verify(device).connect(); verify(device).disconnect(); } @Test public void testUpdateAdapterIsNotPowered() { AdapterGovernorImpl adapterGovernor = mock(AdapterGovernorImpl.class); when(adapterGovernor.isReady()).thenReturn(false); when(adapterGovernor.isPowered()).thenReturn(false); when(bluetoothManager.getAdapterGovernor(URL)).thenReturn(adapterGovernor); governor.update(device); verifyNoMoreInteractions(device); } @Test public void testUpdateOnline() { int onlineTimeout = 20; governor.setBlockedControl(false); governor.setConnectionControl(true); when(device.isBlocked()).thenReturn(false); when(device.isConnected()).thenReturn(true); when(device.connect()).thenReturn(true); governor.setOnlineTimeout(onlineTimeout); governor.update(); verify(genericDeviceListener, times(1)).online(); governor.update(); verify(genericDeviceListener, times(1)).online(); governor.setConnectionControl(false); when(device.disconnect()).thenReturn(true); governor.update(); verify(genericDeviceListener, times(0)).offline(); Whitebox.setInternalState(governor, "lastInteracted", Instant.now().minusSeconds(onlineTimeout)); governor.setBlockedControl(true); when(device.isBlocked()).thenReturn(true); governor.update(); verify(genericDeviceListener, times(1)).offline(); } @Test public void testReset() { Whitebox.setInternalState(governor, "online", false); when(device.isConnected()).thenReturn(false); governor.reset(device); verify(device, times(1)).disableRSSINotifications(); verify(device, times(1)).disableServicesResolvedNotifications(); verify(device, times(1)).disableConnectedNotifications(); verify(device, times(1)).disableBlockedNotifications(); verify(device, times(1)).disableServiceDataNotifications(); verify(device, times(1)).disableManufacturerDataNotifications(); verify(device, times(1)).isConnected(); verify(device, times(0)).disconnect(); verify(bluetoothSmartDeviceListener, times(0)).disconnected(); verify(genericDeviceListener, times(0)).offline(); verify(bluetoothManager, times(0)).resetDescendants(URL); Whitebox.setInternalState(governor, "online", true); when(device.isConnected()).thenReturn(true); governor.reset(device); verify(device, times(2)).disableRSSINotifications(); verify(device, times(2)).disableServicesResolvedNotifications(); verify(device, times(2)).disableConnectedNotifications(); verify(device, times(2)).disableBlockedNotifications(); verify(device, times(2)).disableServiceDataNotifications(); verify(device, times(2)).disableManufacturerDataNotifications(); verify(device, times(2)).isConnected(); verify(device, times(1)).disconnect(); verify(bluetoothSmartDeviceListener, times(1)).disconnected(); verify(genericDeviceListener, never()).offline(); verifyNoMoreInteractions(device, genericDeviceListener, bluetoothSmartDeviceListener); } @Test public void testGetBluetoothClass() { assertEquals(BLUETOOTH_CLASS, governor.getBluetoothClass()); verify(device, times(1)).getBluetoothClass(); } @Test public void testIsBleEnabled() { when(device.isBleEnabled()).thenReturn(true).thenReturn(false); assertTrue(governor.isBleEnabled()); verify(device, times(1)).isBleEnabled(); assertFalse(governor.isBleEnabled()); verify(device, times(2)).isBleEnabled(); } @Test public void testGetName() throws Exception { assertEquals(NAME, governor.getName()); verify(device, times(1)).getName(); verifyNoMoreInteractions(device); verify(governor).interact(eq("getName"), any(Function.class)); } @Test public void testGetDisplayName() throws Exception { when(device.getAlias()).thenReturn(ALIAS).thenReturn(null); when(device.getName()).thenReturn(NAME); assertEquals(ALIAS, governor.getDisplayName()); verify(device, times(1)).getAlias(); assertEquals(NAME, governor.getDisplayName()); verify(device, times(2)).getAlias(); verify(device, times(1)).getName(); verifyNoMoreInteractions(device); verify(governor, times(2)).interact(eq("getAlias"), any(Function.class)); verify(governor, times(1)).interact(eq("getName"), any(Function.class)); } @Test public void testGetAlias() throws Exception { assertEquals(ALIAS, governor.getAlias()); verify(device, times(1)).getAlias(); verifyNoMoreInteractions(device); verify(governor).interact(eq("getAlias"), any(Function.class)); } @Test public void testSetAlias() throws Exception { String newAlias = "new alias"; governor.setAlias(newAlias); verify(device, times(1)).setAlias(newAlias); verifyNoMoreInteractions(device); verify(governor).interact(eq("setAlias"), any(), eq(newAlias)); } @Test public void testGetSetConnectionControl() { governor.setConnectionControl(true); assertTrue(governor.getConnectionControl()); governor.setConnectionControl(false); assertFalse(governor.getConnectionControl()); } @Test public void testGetSetBlockedControl() { governor.setBlockedControl(true); assertTrue(governor.getBlockedControl()); governor.setBlockedControl(false); assertFalse(governor.getBlockedControl()); } @Test public void testIsConnected() throws Exception { when(device.isConnected()).thenReturn(false).thenReturn(true); assertFalse(governor.isConnected()); verify(device, times(1)).isConnected(); assertTrue(governor.isConnected()); verify(device, times(2)).isConnected(); verifyNoMoreInteractions(device); verify(governor, times(2)).interact(eq("isConnected"), any(Function.class)); } @Test public void testIsBlocked() throws Exception { when(device.isBlocked()).thenReturn(false).thenReturn(true); assertFalse(governor.isBlocked()); verify(device, times(1)).isBlocked(); assertTrue(governor.isBlocked()); verify(device, times(2)).isBlocked(); verifyNoMoreInteractions(device); verify(governor, times(2)).interact(eq("isBlocked"), any(Function.class)); } @Test public void testIsOnline() { int onlineTimeout = 20; governor.setOnlineTimeout(onlineTimeout); Whitebox.setInternalState(governor, "lastInteracted", Instant.now()); assertTrue(governor.isOnline()); Whitebox.setInternalState(governor, "lastInteracted", Instant.now().minusSeconds(onlineTimeout)); assertFalse(governor.isOnline()); } @Test public void testGetRSSI() { when(device.getRSSI()).thenReturn(RSSI); assertEquals(RSSI, governor.getRSSI()); verify(device, times(1)).getRSSI(); } @Test public void testAddRemoveBluetoothSmartDeviceListener() { BluetoothSmartDeviceListener listener = mock(BluetoothSmartDeviceListener.class); governor.addBluetoothSmartDeviceListener(listener); ArgumentCaptor<List> listArgumentCaptor = ArgumentCaptor.forClass(List.class); doNothing().when(listener).servicesResolved(listArgumentCaptor.capture()); governor.notifyConnected(false); governor.notifyServicesResolved(governor.getResolvedServices()); verify(listener, never()).connected(); verify(listener, times(1)).disconnected(); verify(listener, never()).servicesUnresolved(); verify(listener, times(1)).servicesResolved(listArgumentCaptor.getValue()); governor.notifyConnected(true); governor.notifyServicesUnresolved(); verify(listener, times(1)).connected(); verify(listener, times(1)).disconnected(); verify(listener, times(1)).servicesUnresolved(); verify(listener, times(1)).servicesResolved(listArgumentCaptor.getValue()); List<GattService> gattServices = listArgumentCaptor.getValue(); assertEquals(1, gattServices.size()); GattService gattService = gattServices.get(0); assertEquals(SERVICE_1_URL, gattService.getURL()); assertEquals(2, gattService.getCharacteristics().size()); List<GattCharacteristic> gattCharacteristics = new ArrayList(gattService.getCharacteristics()); Collections.sort(gattCharacteristics, new Comparator<GattCharacteristic>() { @Override public int compare(GattCharacteristic first, GattCharacteristic second) { return first.getURL().compareTo(second.getURL()); } }); assertEquals(CHARACTERISTIC_1_URL, gattCharacteristics.get(0).getURL()); assertEquals(CHARACTERISTIC_2_URL, gattCharacteristics.get(1).getURL()); governor.removeBluetoothSmartDeviceListener(listener); governor.notifyConnected(true); governor.notifyServicesResolved(gattServices); verifyNoMoreInteractions(listener); } @Test public void testAddRemoveGenericBluetoothDeviceListener() { GenericBluetoothDeviceListener listener = mock(GenericBluetoothDeviceListener.class); governor.addGenericBluetoothDeviceListener(listener); governor.notifyBlocked(true); governor.updateRSSI((short) -45); governor.notifyOnline(false); verify(listener, times(1)).blocked(true); verify(listener, times(1)).rssiChanged((short) -45); verify(listener, times(1)).offline(); verify(listener, never()).online(); governor.notifyBlocked(false); governor.updateRSSI((short) -84); governor.notifyOnline(true); verify(listener, times(1)).blocked(false); verify(listener, times(1)).rssiChanged((short) -84); verify(listener, times(1)).offline(); verify(listener, times(1)).online(); governor.removeGenericBluetoothDeviceListener(listener); governor.notifyBlocked(true); governor.updateRSSI((short) -64); governor.notifyOnline(false); verifyNoMoreInteractions(listener); } @Test public void testGetServicesToCharacteristicsMap() { Map<URL, List<CharacteristicGovernor>> characteristicsMap = governor.getServicesToCharacteristicsMap(); assertEquals(1, characteristicsMap.size()); assertEquals(SERVICE_1_URL, characteristicsMap.keySet().iterator().next()); assertEquals(2, characteristicsMap.get(SERVICE_1_URL).size()); List<CharacteristicGovernor> gattCharacteristics = new ArrayList(characteristicsMap.get(SERVICE_1_URL)); Collections.sort(gattCharacteristics, new Comparator<CharacteristicGovernor>() { @Override public int compare(CharacteristicGovernor first, CharacteristicGovernor second) { return first.getURL().compareTo(second.getURL()); } }); assertEquals(CHARACTERISTIC_1_URL, gattCharacteristics.get(0).getURL()); assertEquals(CHARACTERISTIC_2_URL, gattCharacteristics.get(1).getURL()); } @Test public void testGetCharacteristics() { List<URL> characteristics = new ArrayList(governor.getCharacteristics()); assertEquals(2, characteristics.size()); Collections.sort(characteristics); assertEquals(CHARACTERISTIC_1_URL, characteristics.get(0)); assertEquals(CHARACTERISTIC_2_URL, characteristics.get(1)); } @Test public void testGetCharacteristicGovernors() { List<CharacteristicGovernor> governors = new ArrayList(governor.getCharacteristicGovernors()); assertEquals(2, governors.size()); Collections.sort(governors, new Comparator<CharacteristicGovernor>() { @Override public int compare(CharacteristicGovernor first, CharacteristicGovernor second) { return first.getURL().compareTo(second.getURL()); } }); assertEquals(CHARACTERISTIC_1_URL, governors.get(0).getURL()); assertEquals(CHARACTERISTIC_2_URL, governors.get(1).getURL()); } @Test public void testToString() { when(device.getAlias()).thenReturn(ALIAS).thenReturn(null); when(device.isBleEnabled()).thenReturn(true).thenReturn(false); assertEquals("[Device] " + URL + " [" + ALIAS + "] [BLE]", governor.toString()); assertEquals("[Device] " + URL + " [" + NAME + "]", governor.toString()); } @Test public void testEquals() { URL url1 = new URL("/11:22:33:44:55:67/12:34:56:78:90:12"); URL url2 = new URL("/11:22:33:44:55:66/12:34:56:78:90:13"); assertFalse(url1.equals(url2)); DeviceGovernorImpl gov1 = new DeviceGovernorImpl(bluetoothManager, url1); DeviceGovernorImpl gov2 = new DeviceGovernorImpl(bluetoothManager, url2); assertEquals(gov1, gov1); assertFalse(gov1.equals(new Object())); assertFalse(gov1.equals(gov2)); gov2 = new DeviceGovernorImpl(bluetoothManager, url1); assertTrue(gov1.equals(gov2)); } @Test public void testHashCode() { URL url1 = new URL("/11:22:33:44:55:67/12:34:56:78:90:12"); URL url2 = new URL("/11:22:33:44:55:66/12:34:56:78:90:13"); assertFalse(url1.hashCode() == url2.hashCode()); DeviceGovernorImpl gov1 = new DeviceGovernorImpl(bluetoothManager, url1); DeviceGovernorImpl gov2 = new DeviceGovernorImpl(bluetoothManager, url2); assertFalse(gov1.hashCode() == gov2.hashCode()); gov2 = new DeviceGovernorImpl(bluetoothManager, url1); assertTrue(gov1.hashCode() == gov2.hashCode()); } @Test public void testGetType() { assertEquals(BluetoothObjectType.DEVICE, governor.getType()); } @Test public void testAccept() throws Exception { governor.accept(new BluetoothObjectVisitor() { @Override public void visit(AdapterGovernor governor) { assertFalse(true); } @Override public void visit(DeviceGovernor governor) { assertEquals(DeviceGovernorImplTest.this.governor, governor); } @Override public void visit(CharacteristicGovernor governor) { assertFalse(true); } }); } @Test public void testGetSetOnlineTimeout() { governor.setOnlineTimeout(50); assertEquals(50, governor.getOnlineTimeout()); } @Test public void testNotifyOnline() { GenericBluetoothDeviceListener listener1 = mock(GenericBluetoothDeviceListener.class); GenericBluetoothDeviceListener listener2 = mock(GenericBluetoothDeviceListener.class); governor.addGenericBluetoothDeviceListener(listener1); governor.addGenericBluetoothDeviceListener(listener2); governor.notifyOnline(true); InOrder inOrder = inOrder(listener1, listener2); inOrder.verify(listener1, times(1)).online(); inOrder.verify(listener2, times(1)).online(); // this should be ignored by governor, a log message must be issued doThrow(Exception.class).when(listener1).online(); governor.notifyOnline(true); inOrder.verify(listener1, times(1)).online(); inOrder.verify(listener2, times(1)).online(); inOrder.verify(listener1, never()).offline(); inOrder.verify(listener2, never()).offline(); governor.notifyOnline(false); inOrder.verify(listener1, times(1)).offline(); inOrder.verify(listener2, times(1)).offline(); inOrder.verifyNoMoreInteractions(); } @Test public void testNotifyRSSIChangedNoFilter() { GenericBluetoothDeviceListener listener1 = mock(GenericBluetoothDeviceListener.class); GenericBluetoothDeviceListener listener2 = mock(GenericBluetoothDeviceListener.class); governor.addGenericBluetoothDeviceListener(listener1); governor.addGenericBluetoothDeviceListener(listener2); governor.updateRSSI(RSSI); InOrder inOrder = inOrder(listener1, listener2); inOrder.verify(listener1, times(1)).rssiChanged(RSSI); inOrder.verify(listener2, times(1)).rssiChanged(RSSI); // this should be ignored by governor, a log message must be issued doThrow(Exception.class).when(listener1).rssiChanged(RSSI); governor.updateRSSI(RSSI); inOrder.verify(listener1, times(1)).rssiChanged(RSSI); inOrder.verify(listener2, times(1)).rssiChanged(RSSI); inOrder.verifyNoMoreInteractions(); } @Test public void testNotifyRSSIChangedWithFilter() { GenericBluetoothDeviceListener listener = mock(GenericBluetoothDeviceListener.class); governor.addGenericBluetoothDeviceListener(listener); short filteredRssi = -60; when(rssiFilter.next(RSSI)).thenReturn(filteredRssi); governor.setRssiFilter(/* does not matter */ RssiKalmanFilter.class); assertTrue(governor.isRssiFilteringEnabled()); assertEquals(rssiFilter, governor.getRssiFilter()); governor.updateRSSI(RSSI); verify(rssiFilter, times(1)).next(RSSI); verify(listener, times(1)).rssiChanged(filteredRssi); governor.setRssiReportingRate(0); governor.setRssiFilteringEnabled(false); governor.updateRSSI(RSSI); verify(rssiFilter, times(1)).next(RSSI); verify(listener, times(1)).rssiChanged(RSSI); } @Test public void testNotifyRSSIReportingRate() { GenericBluetoothDeviceListener listener = mock(GenericBluetoothDeviceListener.class); governor.addGenericBluetoothDeviceListener(listener); governor.setRssiReportingRate(0); assertEquals(0, governor.getRssiReportingRate()); governor.updateRSSI(RSSI); governor.updateRSSI(RSSI); verify(listener, times(2)).rssiChanged(RSSI); governor.setRssiReportingRate(5000); assertEquals(5000, governor.getRssiReportingRate()); // these ones should be skipped governor.updateRSSI(RSSI); governor.updateRSSI(RSSI); verify(listener, times(2)).rssiChanged(RSSI); } @Test public void testNotifyServicesResolved() { BluetoothSmartDeviceListener listener1 = mock(BluetoothSmartDeviceListener.class); BluetoothSmartDeviceListener listener2 = mock(BluetoothSmartDeviceListener.class); governor.addBluetoothSmartDeviceListener(listener1); governor.addBluetoothSmartDeviceListener(listener2); governor.notifyServicesResolved(anyList()); InOrder inOrder = inOrder(listener1, listener2); inOrder.verify(listener1, times(1)).servicesResolved(any()); inOrder.verify(listener2, times(1)).servicesResolved(any()); // this should be ignored by governor, a log message must be issued doThrow(Exception.class).when(listener1).servicesResolved(any()); governor.notifyServicesResolved(anyList()); inOrder.verify(listener1, times(1)).servicesResolved(any()); inOrder.verify(listener2, times(1)).servicesResolved(any()); inOrder.verify(listener1, never()).servicesUnresolved(); inOrder.verify(listener2, never()).servicesUnresolved(); governor.notifyServicesUnresolved(); inOrder.verify(listener1, times(1)).servicesUnresolved(); inOrder.verify(listener2, times(1)).servicesUnresolved(); inOrder.verifyNoMoreInteractions(); } @Test public void testNotifyConnected() { BluetoothSmartDeviceListener listener1 = mock(BluetoothSmartDeviceListener.class); BluetoothSmartDeviceListener listener2 = mock(BluetoothSmartDeviceListener.class); governor.addBluetoothSmartDeviceListener(listener1); governor.addBluetoothSmartDeviceListener(listener2); governor.notifyConnected(true); InOrder inOrder = inOrder(listener1, listener2); inOrder.verify(listener1, times(1)).connected(); inOrder.verify(listener2, times(1)).connected(); // this should be ignored by governor, a log message must be issued doThrow(Exception.class).when(listener1).connected(); governor.notifyConnected(true); inOrder.verify(listener1, times(1)).connected(); inOrder.verify(listener2, times(1)).connected(); inOrder.verify(listener1, never()).disconnected(); inOrder.verify(listener2, never()).disconnected(); governor.notifyConnected(false); inOrder.verify(listener1, times(1)).disconnected(); inOrder.verify(listener2, times(1)).disconnected(); inOrder.verifyNoMoreInteractions(); } @Test public void testNotifyBlocked() { GenericBluetoothDeviceListener listener1 = mock(GenericBluetoothDeviceListener.class); GenericBluetoothDeviceListener listener2 = mock(GenericBluetoothDeviceListener.class); governor.addGenericBluetoothDeviceListener(listener1); governor.addGenericBluetoothDeviceListener(listener2); governor.notifyBlocked(true); InOrder inOrder = inOrder(listener1, listener2); inOrder.verify(listener1, times(1)).blocked(true); inOrder.verify(listener2, times(1)).blocked(true); // this should be ignored by governor, a log message must be issued doThrow(Exception.class).when(listener1).blocked(anyBoolean()); governor.notifyBlocked(true); inOrder.verify(listener1, times(1)).blocked(true); inOrder.verify(listener2, times(1)).blocked(true); inOrder.verifyNoMoreInteractions(); } @Test public void testConnectionNotification() { ArgumentCaptor<Notification> notificationCaptor = ArgumentCaptor.forClass(Notification.class); doNothing().when(device).enableConnectedNotifications(notificationCaptor.capture()); // init method will enable notifications, if they are not enabled already governor.init(device); verify(device, times(1)).enableConnectedNotifications(notificationCaptor.getValue()); notificationCaptor.getValue().notify(Boolean.TRUE); verify(bluetoothSmartDeviceListener, times(1)).connected(); verify(governor, times(1)).notifyConnected(true); verify(governor, times(1)).updateLastInteracted(); notificationCaptor.getValue().notify(Boolean.FALSE); verify(bluetoothSmartDeviceListener, times(1)).disconnected(); verify(governor, times(1)).notifyConnected(false); verify(governor, times(2)).updateLastInteracted(); } @Test public void testServicesResolvedNotification() { ArgumentCaptor<Notification> notificationCaptor = ArgumentCaptor.forClass(Notification.class); doNothing().when(device).enableServicesResolvedNotifications(notificationCaptor.capture()); // init method will enable notifications, if they are not enabled already governor.init(device); verify(device, times(1)).enableServicesResolvedNotifications(notificationCaptor.getValue()); notificationCaptor.getValue().notify(Boolean.TRUE); verify(bluetoothSmartDeviceListener, times(1)).servicesResolved(any()); verify(governor).notifyServicesResolved(any()); verify(governor).updateLastInteracted(); verify(bluetoothManager).updateDescendants(URL); notificationCaptor.getValue().notify(Boolean.FALSE); verify(bluetoothSmartDeviceListener, times(1)).servicesUnresolved(); verify(governor).notifyServicesUnresolved(); verify(governor).updateLastInteracted(); verify(bluetoothManager).updateDescendants(URL); verify(bluetoothManager).resetDescendants(URL); } @Test public void testRSSINotification() { ArgumentCaptor<Notification> notificationCaptor = ArgumentCaptor.forClass(Notification.class); doNothing().when(device).enableRSSINotifications(notificationCaptor.capture()); // init method will enable notifications, if they are not enabled already governor.init(device); verify(device, times(1)).enableRSSINotifications(notificationCaptor.getValue()); notificationCaptor.getValue().notify(RSSI); verify(genericDeviceListener, times(1)).rssiChanged(RSSI); verify(governor, times(1)).updateRSSI(RSSI); verify(governor, times(1)).updateLastAdvertised(); } @Test public void testBlockedNotification() { ArgumentCaptor<Notification> notificationCaptor = ArgumentCaptor.forClass(Notification.class); doNothing().when(device).enableBlockedNotifications(notificationCaptor.capture()); // init method will enable notifications, if they are not enabled already governor.init(device); verify(device, times(1)).enableBlockedNotifications(notificationCaptor.getValue()); notificationCaptor.getValue().notify(true); verify(genericDeviceListener, times(1)).blocked(true); verify(governor, times(1)).notifyBlocked(true); verify(governor, times(1)).updateLastInteracted(); notificationCaptor.getValue().notify(false); verify(genericDeviceListener, times(1)).blocked(false); verify(governor, times(1)).notifyBlocked(false); verify(governor, times(2)).updateLastInteracted(); } @Test public void testEstimatedDistanceRssiFilter() { // The calculation is based on the logarithmetic function: d = 10 ^ ((TxPower - RSSI) / 10n) // where n ranges from 2 to 4 (environemnt specific factor, e.g. 2 outdoors -> 4 indoors) governor.setSignalPropagationExponent(2.0); assertEquals(2.0, governor.getSignalPropagationExponent(), 0.1); governor.setRssiFilteringEnabled(true); governor.setRssiFilter(/* does not matter */ RssiKalmanFilter.class); governor.setMeasuredTxPower((short) -60); assertEquals(-60, governor.getMeasuredTxPower()); when(rssiFilter.current()).thenReturn((short) -60); // Tx Power is not reported by device and measured Tx Power is not set, then a default Tx Power is used (-60), // this means that Tx Power equals to RSSI, therefore estimated distance should be 1m assertEquals(1.0, governor.getEstimatedDistance(), 0.01); // outdoor, same distance, signal is stronger, compensated by propagation exponent governor.setSignalPropagationExponent(2.0); when(rssiFilter.current()).thenReturn((short) -65); assertEquals(1.778, governor.getEstimatedDistance(), 0.001); // indoor, same distance, signal is weaker, compensated by propagation exponent governor.setSignalPropagationExponent(4.0); when(rssiFilter.current()).thenReturn((short) -70); assertEquals(1.778, governor.getEstimatedDistance(), 0.001); governor.setSignalPropagationExponent(2.0); when(rssiFilter.current()).thenReturn((short) -55); assertEquals(0.562, governor.getEstimatedDistance(), 0.001); governor.setSignalPropagationExponent(4.0); when(rssiFilter.current()).thenReturn((short) -65); assertEquals(1.333, governor.getEstimatedDistance(), 0.001); governor.setSignalPropagationExponent(4.0); when(rssiFilter.current()).thenReturn((short) -55); assertEquals(0.749, governor.getEstimatedDistance(), 0.001); // checking if Tx Power makes any difference governor.setMeasuredTxPower((short) -65); when(rssiFilter.current()).thenReturn((short) -65); assertEquals(1.0, governor.getEstimatedDistance(), 0.001); } @Test public void testEstimatedDistanceDisabledRssiFilter() { // The calculation is based on the logarithmetic function: d = 10 ^ ((TxPower - RSSI) / 10n) // where n ranges from 2 to 4 (environemnt specific factor, e.g. 2 outdoors -> 4 indoors) governor.setSignalPropagationExponent(2.0); assertEquals(2.0, governor.getSignalPropagationExponent(), 0.1); governor.setMeasuredTxPower((short) -60); assertEquals(-60, governor.getMeasuredTxPower()); when(device.getRSSI()).thenReturn((short) -60); // device is not ready Whitebox.setInternalState(governor, "bluetoothObject", null); assertEquals(0.0, governor.getEstimatedDistance(), 0.01); // now it has become ready Whitebox.setInternalState(governor, "bluetoothObject", device); // Tx Power is not reported by device and measured Tx Power is not set, then a default Tx Power is used (-60), // this means that Tx Power equals to RSSI, therefore estimated distance should be 1m assertEquals(1.0, governor.getEstimatedDistance(), 0.01); // outdoor, same distance, signal is stronger, compensated by propagation exponent governor.setSignalPropagationExponent(2.0); when(device.getRSSI()).thenReturn((short) -65); assertEquals(1.778, governor.getEstimatedDistance(), 0.001); // indoor, same distance, signal is weaker, compensated by propagation exponent governor.setSignalPropagationExponent(4.0); when(device.getRSSI()).thenReturn((short) -70); assertEquals(1.778, governor.getEstimatedDistance(), 0.001); governor.setSignalPropagationExponent(2.0); when(device.getRSSI()).thenReturn((short) -55); assertEquals(0.562, governor.getEstimatedDistance(), 0.001); governor.setSignalPropagationExponent(4.0); when(device.getRSSI()).thenReturn((short) -65); assertEquals(1.333, governor.getEstimatedDistance(), 0.001); governor.setSignalPropagationExponent(4.0); when(device.getRSSI()).thenReturn((short) -55); assertEquals(0.749, governor.getEstimatedDistance(), 0.001); } @Test public void testEstimatedDistanceTxPower() { // The calculation is based on the logarithmetic function: d = 10 ^ ((TxPower - RSSI) / 10n) // where n ranges from 2 to 4 (environemnt specific factor, e.g. 2 outdoors -> 4 indoors) governor.setSignalPropagationExponent(2.0); assertEquals(2.0, governor.getSignalPropagationExponent(), 0.1); governor.setRssiFilteringEnabled(true); governor.setRssiFilter(/* does not matter */ RssiKalmanFilter.class); // the default Tx Power is -55 when(rssiFilter.current()).thenReturn((short) -55); assertEquals(1.0, governor.getEstimatedDistance(), 0.001); when(device.getTxPower()).thenReturn((short) -60); governor.update(device); governor.setMeasuredTxPower((short) 0); assertEquals(0.562, governor.getEstimatedDistance(), 0.001); // measured Tx Power should get precedence when(device.getTxPower()).thenReturn((short) -60); governor.update(device); governor.setMeasuredTxPower((short) -65); assertEquals(0.316, governor.getEstimatedDistance(), 0.001); } private CharacteristicGovernor mockCharacteristicGovernor(URL url) { CharacteristicGovernor governor = mock(CharacteristicGovernor.class); when(governor.getURL()).thenReturn(url); return governor; } }