Restore discovery tests (#19562)

* Restore tests

Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com>
This commit is contained in:
Nadahar
2025-10-26 19:24:33 +01:00
committed by GitHub
parent e009276b53
commit c30d9d9cd7
18 changed files with 160 additions and 221 deletions
@@ -18,6 +18,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@@ -117,6 +118,15 @@ public class ThingDiscoveryService extends AbstractThingHandlerDiscoveryService<
super(BridgeHandler.class, SUPPORTED_THING_TYPES, SEARCH_TIME);
}
/**
* Constructor for tests only.
*
* @param scheduler the {@link ScheduledExecutorService} to use during testing.
*/
ThingDiscoveryService(ScheduledExecutorService scheduler) {
super(scheduler, BridgeHandler.class, SUPPORTED_THING_TYPES, SEARCH_TIME, true, null, null);
}
@Override
public void initialize() {
logger.trace("initialize");
@@ -255,7 +265,7 @@ public class ThingDiscoveryService extends AbstractThingHandlerDiscoveryService<
* Translates a Bosch device ID to an openHAB-compliant thing ID.
* <p>
* Characters that are not allowed in thing IDs are replaced by underscores.
*
*
* @param deviceId the Bosch device ID
* @return the translated openHAB-compliant thing ID
*/
@@ -21,21 +21,12 @@ import static org.mockito.Mockito.*;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.Callable;
import java.util.concurrent.Delayed;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Response.CompleteListener;
@@ -58,6 +49,7 @@ import org.openhab.binding.boschshc.internal.devices.bridge.dto.UserDefinedState
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.exceptions.LongPollingFailedException;
import org.openhab.binding.boschshc.internal.tests.common.CommonTestUtils;
import org.openhab.core.util.SameThreadExecutorService;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
@@ -72,131 +64,6 @@ import com.google.gson.JsonSyntaxException;
@ExtendWith(MockitoExtension.class)
class LongPollingTest {
/**
* A dummy implementation of {@link ScheduledFuture}.
* <p>
* This is required because we can not return <code>null</code> in the executor service test implementation (see
* below).
*
* @author David Pace - Initial contribution
*
* @param <T> The result type returned by this Future
*/
private static class NullScheduledFuture<T> implements ScheduledFuture<T> {
@Override
public long getDelay(@Nullable TimeUnit unit) {
return 0;
}
@Override
public int compareTo(@Nullable Delayed o) {
return 0;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return false;
}
@Override
public T get() throws InterruptedException, ExecutionException {
return null;
}
@Override
public T get(long timeout, @Nullable TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return null;
}
}
/**
* Executor service implementation that runs all runnables in the same thread in order to enable deterministic
* testing.
*
* @author David Pace - Initial contribution
*
*/
private static class SameThreadExecutorService extends AbstractExecutorService implements ScheduledExecutorService {
private volatile boolean terminated;
@Override
public void shutdown() {
terminated = true;
}
@NonNullByDefault({})
@Override
public List<Runnable> shutdownNow() {
return Collections.emptyList();
}
@Override
public boolean isShutdown() {
return terminated;
}
@Override
public boolean isTerminated() {
return terminated;
}
@Override
public boolean awaitTermination(long timeout, @Nullable TimeUnit unit) throws InterruptedException {
shutdown();
return terminated;
}
@Override
public void execute(@Nullable Runnable command) {
if (command != null) {
// execute in the same thread in unit tests
command.run();
}
}
@Override
public ScheduledFuture<?> schedule(@Nullable Runnable command, long delay, @Nullable TimeUnit unit) {
// not used in this tests
return new NullScheduledFuture<>();
}
@Override
public <V> ScheduledFuture<V> schedule(@Nullable Callable<V> callable, long delay, @Nullable TimeUnit unit) {
return new NullScheduledFuture<>();
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(@Nullable Runnable command, long initialDelay, long period,
@Nullable TimeUnit unit) {
if (command != null) {
command.run();
}
return new NullScheduledFuture<>();
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(@Nullable Runnable command, long initialDelay, long delay,
@Nullable TimeUnit unit) {
if (command != null) {
command.run();
}
return new NullScheduledFuture<>();
}
}
private @NonNullByDefault({}) LongPolling fixture;
private @NonNullByDefault({}) BoschHttpClient httpClient;
@@ -20,7 +20,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -47,6 +46,7 @@ import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.util.SameThreadExecutorService;
/**
* Unit tests for {@link ThingDiscoveryService}.
@@ -66,7 +66,7 @@ class ThingDiscoveryServiceTest {
@BeforeEach
void beforeEach() {
fixture = new ThingDiscoveryService();
fixture = new ThingDiscoveryService(new SameThreadExecutorService());
fixture.addDiscoveryListener(discoveryListener);
fixture.setThingHandler(bridgeHandler);
}
@@ -160,7 +160,7 @@ class ThingDiscoveryServiceTest {
fixture.addDevices(devices, emptyRooms);
// two calls for the two devices expected
verify(discoveryListener, timeout(1000L).times(2)).thingDiscovered(any(), any());
verify(discoveryListener, times(2)).thingDiscovered(any(), any());
}
@Test
@@ -186,8 +186,7 @@ class ThingDiscoveryServiceTest {
device.name = "Test Name";
fixture.addDevice(device, "TestRoom");
verify(discoveryListener, timeout(1000L)).thingDiscovered(discoveryServiceCaptor.capture(),
discoveryResultCaptor.capture());
verify(discoveryListener).thingDiscovered(discoveryServiceCaptor.capture(), discoveryResultCaptor.capture());
assertThat(discoveryServiceCaptor.getValue().getClass(), is(ThingDiscoveryService.class));
DiscoveryResult result = discoveryResultCaptor.getValue();
@@ -228,8 +227,7 @@ class ThingDiscoveryServiceTest {
device.id = "testDevice:ID";
device.name = deviceName;
fixture.addDevice(device, roomName);
verify(discoveryListener, timeout(1000L)).thingDiscovered(discoveryServiceCaptor.capture(),
discoveryResultCaptor.capture());
verify(discoveryListener).thingDiscovered(discoveryServiceCaptor.capture(), discoveryResultCaptor.capture());
assertThat(discoveryServiceCaptor.getValue().getClass(), is(ThingDiscoveryService.class));
DiscoveryResult result = discoveryResultCaptor.getValue();
assertThat(result.getLabel(), is(expectedNiceName));
@@ -18,6 +18,7 @@ import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -72,6 +73,15 @@ public class ThingDiscoveryService extends AbstractThingHandlerDiscoveryService<
super(DeconzBridgeHandler.class, SUPPORTED_THING_TYPES_UIDS, 30);
}
/**
* Constructor for tests only.
*
* @param scheduler the {@link ScheduledExecutorService} to use during testing.
*/
public ThingDiscoveryService(ScheduledExecutorService scheduler) {
super(scheduler, DeconzBridgeHandler.class, SUPPORTED_THING_TYPES_UIDS, 30, true, null, null);
}
@Override
public void startScan() {
thingHandler.getBridgeFullState().thenAccept(fullState -> {
@@ -14,7 +14,7 @@ package org.openhab.binding.deconz;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.times;
import java.io.IOException;
import java.io.InputStream;
@@ -51,6 +51,7 @@ import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.util.SameThreadExecutorService;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -92,13 +93,13 @@ public class DeconzTest {
Mockito.doAnswer(answer -> CompletableFuture.completedFuture(Optional.of(bridgeFullState))).when(bridgeHandler)
.getBridgeFullState();
ThingDiscoveryService discoveryService = new ThingDiscoveryService();
ThingDiscoveryService discoveryService = new ThingDiscoveryService(new SameThreadExecutorService());
discoveryService.modified(Map.of(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY, false));
discoveryService.setThingHandler(bridgeHandler);
discoveryService.initialize();
discoveryService.addDiscoveryListener(discoveryListener);
discoveryService.startScan();
Mockito.verify(discoveryListener, timeout(1000L).times(20)).thingDiscovered(any(), any());
Mockito.verify(discoveryListener, times(20)).thingDiscovered(any(), any());
}
public static <T> T getObjectFromJson(String filename, Class<T> clazz, Gson gson) throws IOException {
@@ -17,6 +17,7 @@ import static org.openhab.binding.homematic.internal.HomematicBindingConstants.B
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNull;
import org.openhab.binding.homematic.internal.common.HomematicConfig;
@@ -55,6 +56,11 @@ public class HomematicDeviceDiscoveryService
super(HomematicBridgeHandler.class, Set.of(new ThingTypeUID(BINDING_ID, "-")), DISCOVER_TIMEOUT_SECONDS, false);
}
HomematicDeviceDiscoveryService(ScheduledExecutorService scheduler) {
super(scheduler, HomematicBridgeHandler.class, Set.of(new ThingTypeUID(BINDING_ID, "-")),
DISCOVER_TIMEOUT_SECONDS, false, null, null);
}
@Override
public void initialize() {
thingHandler.setDiscoveryService(this);
@@ -149,7 +155,7 @@ public class HomematicDeviceDiscoveryService
private void waitForLoadDevicesFinished() throws InterruptedException, ExecutionException {
Future<?> loadFuture;
if ((loadFuture = loadDevicesFuture) != null) {
loadFuture.get(); // TODO: Bug - doesn't always complete
loadFuture.get();
}
}
@@ -22,7 +22,6 @@ import static org.openhab.binding.homematic.test.util.DimmerHelper.createDimmerH
import java.io.IOException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.openhab.binding.homematic.internal.communicator.HomematicGateway;
import org.openhab.binding.homematic.internal.handler.HomematicBridgeHandler;
@@ -35,6 +34,7 @@ import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.util.SameThreadExecutorService;
/**
* Tests for {@link HomematicDeviceDiscoveryServiceTest}.
@@ -50,7 +50,7 @@ public class HomematicDeviceDiscoveryServiceTest extends JavaTest {
@BeforeEach
public void setup() throws IOException {
this.homematicBridgeHandler = mockHomematicBridgeHandler();
this.homematicDeviceDiscoveryService = new HomematicDeviceDiscoveryService();
this.homematicDeviceDiscoveryService = new HomematicDeviceDiscoveryService(new SameThreadExecutorService());
this.homematicDeviceDiscoveryService.setThingHandler(homematicBridgeHandler);
}
@@ -79,7 +79,6 @@ public class HomematicDeviceDiscoveryServiceTest extends JavaTest {
return mock(HomematicTypeGenerator.class);
}
@Disabled
@Test
public void testDiscoveryResultIsReportedForNewDevice() {
SimpleDiscoveryListener discoveryListener = new SimpleDiscoveryListener();
@@ -92,7 +91,6 @@ public class HomematicDeviceDiscoveryServiceTest extends JavaTest {
discoveryResultMatchesHmDevice(discoveryListener.discoveredResults.element(), hmDevice);
}
@Disabled
@Test
public void testDevicesAreLoadedFromBridgeDuringDiscovery() throws IOException {
startScanAndWaitForLoadedDevices();
@@ -100,7 +98,6 @@ public class HomematicDeviceDiscoveryServiceTest extends JavaTest {
verify(homematicBridgeHandler.getGateway()).loadAllDeviceMetadata();
}
@Disabled
@Test
public void testInstallModeIsNotActiveDuringInitialDiscovery() throws IOException {
startScanAndWaitForLoadedDevices();
@@ -108,7 +105,6 @@ public class HomematicDeviceDiscoveryServiceTest extends JavaTest {
verify(homematicBridgeHandler.getGateway(), never()).setInstallMode(eq(true), anyInt());
}
@Disabled
@Test
public void testInstallModeIsActiveDuringSubsequentDiscovery() throws IOException {
homematicBridgeHandler.getThing()
@@ -116,26 +112,23 @@ public class HomematicDeviceDiscoveryServiceTest extends JavaTest {
startScanAndWaitForLoadedDevices();
verify(homematicBridgeHandler.getGateway(), after(500L)).setInstallMode(true, 60);
verify(homematicBridgeHandler.getGateway()).setInstallMode(true, 60);
}
@Disabled
@Test
public void testStoppingDiscoveryDisablesInstallMode() throws IOException, InterruptedException {
public void testStoppingDiscoveryDisablesInstallMode() throws IOException {
homematicBridgeHandler.getThing()
.setStatusInfo(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""));
homematicDeviceDiscoveryService.startScan();
Thread.sleep(500L);
homematicDeviceDiscoveryService.stopScan();
verify(homematicBridgeHandler.getGateway(), after(500L)).setInstallMode(false, 0);
verify(homematicBridgeHandler.getGateway()).setInstallMode(false, 0);
}
private void startScanAndWaitForLoadedDevices() {
homematicDeviceDiscoveryService.startScan();
waitForAssert(() -> verify(homematicBridgeHandler, after(500L)).setOfflineStatus(), 1000, 50);
waitForAssert(() -> verify(homematicBridgeHandler).setOfflineStatus(), 1000, 50);
}
private void discoveryResultMatchesHmDevice(DiscoveryResult result, HmDevice device) {
@@ -18,6 +18,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.mspa.internal.MSpaConstants;
@@ -44,6 +45,15 @@ public class MSpaDiscoveryService extends AbstractDiscoveryService {
super(Set.of(MSpaConstants.THING_TYPE_POOL), 0, false);
}
/**
* Constructor for tests only.
*
* @param scheduler the {@link ScheduledExecutorService} to use during testing.
*/
public MSpaDiscoveryService(ScheduledExecutorService scheduler) {
super(scheduler, Set.of(MSpaConstants.THING_TYPE_POOL), 0, false, null, null);
}
@Override
protected void startScan() {
accountList.forEach(account -> {
@@ -47,6 +47,7 @@ import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.internal.BridgeImpl;
import org.openhab.core.thing.internal.ThingImpl;
import org.openhab.core.types.State;
import org.openhab.core.util.SameThreadExecutorService;
/**
* {@link TestMessages} tests some generic use cases
@@ -87,10 +88,10 @@ class TestMessages {
}
@Test
void testDiscovery() throws InterruptedException {
void testDiscovery() {
Bridge thing = new BridgeImpl(THING_TYPE_OWNER_ACCOUNT, new ThingUID("mspa", "account"));
Map<String, Object> configMap = new HashMap<>();
MSpaDiscoveryService discovery = new MSpaDiscoveryService();
MSpaDiscoveryService discovery = new MSpaDiscoveryService(new SameThreadExecutorService());
DiscoveryListenerMock discoveryListener = new DiscoveryListenerMock();
discovery.addDiscoveryListener(discoveryListener);
configMap.put("email", "a@b.c");
@@ -102,7 +103,6 @@ class TestMessages {
try {
String content = new String(Files.readAllBytes(Paths.get(fileName)));
account.decodeDevices(content);
Thread.sleep(500L);
List<DiscoveryResult> results = discoveryListener.getResults();
assertEquals(1, results.size(), "Number of discovery results");
DiscoveryResult result = results.get(0);
@@ -21,9 +21,6 @@ import static org.mockito.Mockito.*;
import java.io.IOException;
import java.time.Duration;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;
import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -40,6 +37,7 @@ import org.openhab.binding.network.internal.utils.NetworkUtils;
import org.openhab.binding.network.internal.utils.NetworkUtils.ArpPingUtilEnum;
import org.openhab.binding.network.internal.utils.NetworkUtils.IpPingMethodEnum;
import org.openhab.binding.network.internal.utils.PingResult;
import org.openhab.core.util.SameThreadExecutorService;
/**
* Tests cases for {@see PresenceDetectionValue}
@@ -55,9 +53,6 @@ public class PresenceDetectionTest {
private @NonNullByDefault({}) PresenceDetection asyncSubject;
private @Mock @NonNullByDefault({}) Consumer<PresenceDetectionValue> callback;
private @Mock @NonNullByDefault({}) ExecutorService detectionExecutorService;
private @Mock @NonNullByDefault({}) ExecutorService waitForResultExecutorService;
private @Mock @NonNullByDefault({}) ScheduledExecutorService scheduledExecutorService;
private @Mock @NonNullByDefault({}) PresenceDetectionListener listener;
private @Mock @NonNullByDefault({}) NetworkUtils networkUtils;
@@ -81,7 +76,7 @@ public class PresenceDetectionTest {
subject.setUseArpPing(true, "arping", ArpPingUtilEnum.IPUTILS_ARPING);
subject.setUseIcmpPing(true);
asyncSubject = spy(new PresenceDetection(listener, Duration.ofSeconds(2), Executors.newSingleThreadExecutor()));
asyncSubject = spy(new PresenceDetection(listener, Duration.ofSeconds(2), new SameThreadExecutorService()));
asyncSubject.networkUtils = networkUtils;
asyncSubject.setHostname("127.0.0.1");
@@ -174,7 +169,6 @@ public class PresenceDetectionTest {
// Get value will issue a PresenceDetection internally.
asyncSubject.getValue(callback);
verify(asyncSubject).performPresenceDetection();
Thread.sleep(200); // give it some time to execute
// Callback should be called once with the result (since we use direct executor)
verify(callback, times(1)).accept(any());
@@ -182,9 +176,9 @@ public class PresenceDetectionTest {
asyncSubject.getValue(callback);
verify(callback, times(2)).accept(any());
// Invalidate value, we should not get a new callback immediately again
// Invalidate value, we should get a new callback immediately
asyncSubject.cache.invalidateValue();
asyncSubject.getValue(callback);
verify(callback, times(2)).accept(any());
verify(callback, times(3)).accept(any());
}
}
@@ -17,6 +17,7 @@ import static org.openhab.binding.salus.internal.SalusBindingConstants.SalusDevi
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.salus.internal.handler.CloudApi;
@@ -46,6 +47,18 @@ public class SalusDiscovery extends AbstractDiscoveryService {
this.bridgeUid = bridgeUid;
}
/**
* Constructor for tests only.
*
* @param scheduler the {@link ScheduledExecutorService} to use during testing.
*/
SalusDiscovery(ScheduledExecutorService scheduler, CloudApi cloudApi, ThingUID bridgeUid)
throws IllegalArgumentException {
super(scheduler, SUPPORTED_THING_TYPES_UIDS, 10, true, null, null);
this.cloudApi = cloudApi;
this.bridgeUid = bridgeUid;
}
@Override
protected void startScan() {
try {
@@ -28,7 +28,6 @@ import javax.validation.constraints.NotNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.openhab.binding.salus.internal.handler.CloudApi;
@@ -36,6 +35,7 @@ import org.openhab.binding.salus.internal.rest.Device;
import org.openhab.binding.salus.internal.rest.exceptions.SalusApiException;
import org.openhab.core.config.discovery.DiscoveryListener;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.util.SameThreadExecutorService;
/**
* @author Martin Grześlowski - Initial contribution
@@ -43,14 +43,13 @@ import org.openhab.core.thing.ThingUID;
@NonNullByDefault
public class SalusDiscoveryTest {
@Disabled
@Test
@DisplayName("Method filters out disconnected devices and adds connected devices as things using addThing method")
void testFiltersOutDisconnectedDevicesAndAddsConnectedDevicesAsThings() throws Exception {
// Given
var cloudApi = mock(CloudApi.class);
var bridgeUid = new ThingUID("salus", "salus-device", "boo");
var discoveryService = new SalusDiscovery(cloudApi, bridgeUid);
var discoveryService = new SalusDiscovery(new SameThreadExecutorService(), cloudApi, bridgeUid);
var discoveryListener = mock(DiscoveryListener.class);
discoveryService.addDiscoveryListener(discoveryListener);
var device1 = randomDevice(true);
@@ -76,14 +75,13 @@ public class SalusDiscoveryTest {
argThat(discoveryResult -> discoveryResult.getLabel().equals(device4.name())));
}
@Disabled
@Test
@DisplayName("Cloud API throws an exception during device retrieval, method logs the error")
void testLogsErrorWhenCloudApiThrowsException() throws Exception {
// Given
var cloudApi = mock(CloudApi.class);
var bridgeUid = mock(ThingUID.class);
var discoveryService = new SalusDiscovery(cloudApi, bridgeUid);
var discoveryService = new SalusDiscovery(new SameThreadExecutorService(), cloudApi, bridgeUid);
given(cloudApi.findDevices()).willThrow(new SalusApiException("API error"));
@@ -20,6 +20,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -68,6 +69,18 @@ public class SatelDeviceDiscoveryService extends AbstractDiscoveryService {
this.thingTypeProvider = thingTypeProvider;
}
/**
* Constructor for tests only.
*
* @param scheduler the {@link ScheduledExecutorService} to use during testing.
*/
SatelDeviceDiscoveryService(ScheduledExecutorService scheduler, SatelBridgeHandler bridgeHandler,
Function<ThingTypeUID, ThingType> thingTypeProvider) {
super(SUPPORTED_THING_TYPES, 60, false);
this.bridgeHandler = bridgeHandler;
this.thingTypeProvider = thingTypeProvider;
}
@Override
protected void startScan() {
scanStopped = false;
@@ -22,16 +22,15 @@ import static org.openhab.binding.satel.internal.command.SatelCommand.State.FAIL
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.function.Function;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.satel.internal.command.ReadDeviceInfoCommand;
@@ -39,10 +38,12 @@ import org.openhab.binding.satel.internal.event.EventDispatcher;
import org.openhab.binding.satel.internal.handler.SatelBridgeHandler;
import org.openhab.binding.satel.internal.protocol.SatelMessage;
import org.openhab.binding.satel.internal.types.IntegraType;
import org.openhab.core.config.discovery.DiscoveryListener;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.internal.BridgeImpl;
import org.openhab.core.thing.type.ThingType;
import org.openhab.core.util.SameThreadExecutorService;
/**
* @author Krzysztof Goworek - Initial contribution
@@ -61,27 +62,20 @@ class SatelDeviceDiscoveryServiceTest {
@Mock
private EventDispatcher eventDispatcher;
private final List<DiscoveryResult> results = new ArrayList<>();
@Mock
private DiscoveryListener listener;
@Mock
private SameThreadExecutorService scheduler;
@InjectMocks
private SatelDeviceDiscoveryService testSubject;
@BeforeEach
void setUp() {
when(bridgeHandler.getIntegraType()).thenReturn(IntegraType.I24);
when(bridgeHandler.getEncoding()).thenReturn(bridgeEncoding);
testSubject = new SatelDeviceDiscoveryService(bridgeHandler, thingTypeProvider) {
@NonNullByDefault
@Override
protected void thingDiscovered(DiscoveryResult discoveryResult) {
results.add(discoveryResult);
}
};
}
@AfterEach
void tearDown() {
results.clear();
testSubject.addDiscoveryListener(listener);
}
@Test
@@ -90,7 +84,7 @@ class SatelDeviceDiscoveryServiceTest {
testSubject.startScan();
assertEquals(0, results.size());
verifyNoInteractions(listener);
}
@Test
@@ -102,6 +96,10 @@ class SatelDeviceDiscoveryServiceTest {
testSubject.startScan();
ArgumentCaptor<DiscoveryResult> resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class);
verify(listener, atLeastOnce()).thingDiscovered(any(), resultCaptor.capture());
List<DiscoveryResult> results = resultCaptor.getAllValues();
assertEquals(2, results.size());
assertEquals(THING_TYPE_SYSTEM, results.get(0).getThingTypeUID());
assertEquals(THING_TYPE_EVENTLOG, results.get(1).getThingTypeUID());
@@ -113,7 +111,7 @@ class SatelDeviceDiscoveryServiceTest {
testSubject.startScan();
assertEquals(0, results.size());
verifyNoInteractions(listener);
verify(bridgeHandler, times(52)).sendCommand(any(), eq(false));
}
@@ -125,6 +123,9 @@ class SatelDeviceDiscoveryServiceTest {
testSubject.startScan();
ArgumentCaptor<DiscoveryResult> resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class);
verify(listener, atLeastOnce()).thingDiscovered(any(), resultCaptor.capture());
List<DiscoveryResult> results = resultCaptor.getAllValues();
assertEquals(4,
results.stream().filter(result -> THING_TYPE_PARTITION.equals(result.getThingTypeUID())).count());
assertEquals(24, results.stream().filter(result -> THING_TYPE_ZONE.equals(result.getThingTypeUID())).count());
@@ -145,10 +146,12 @@ class SatelDeviceDiscoveryServiceTest {
testSubject.startScan();
List<DiscoveryResult> shutterResults = results.stream()
ArgumentCaptor<DiscoveryResult> resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class);
verify(listener, atLeastOnce()).thingDiscovered(any(), resultCaptor.capture());
List<DiscoveryResult> results = resultCaptor.getAllValues().stream()
.filter(result -> THING_TYPE_SHUTTER.equals(result.getThingTypeUID())).toList();
assertEquals(24, shutterResults.size());
for (DiscoveryResult result : shutterResults) {
assertEquals(24, results.size());
for (DiscoveryResult result : results) {
assertEquals("Device", result.getLabel());
assertEquals(bridge.getUID(), result.getBridgeUID());
assertEquals(2, result.getProperties().size());
@@ -162,6 +165,9 @@ class SatelDeviceDiscoveryServiceTest {
testSubject.startScan();
ArgumentCaptor<DiscoveryResult> resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class);
verify(listener, atLeastOnce()).thingDiscovered(any(), resultCaptor.capture());
List<DiscoveryResult> results = resultCaptor.getAllValues();
assertEquals(0, results.stream().filter(result -> THING_TYPE_OUTPUT.equals(result.getThingTypeUID())).count());
assertEquals(0, results.stream().filter(result -> THING_TYPE_SHUTTER.equals(result.getThingTypeUID())).count());
}
@@ -173,6 +179,9 @@ class SatelDeviceDiscoveryServiceTest {
testSubject.startScan();
ArgumentCaptor<DiscoveryResult> resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class);
verify(listener, atLeastOnce()).thingDiscovered(any(), resultCaptor.capture());
List<DiscoveryResult> results = resultCaptor.getAllValues();
assertEquals(0, results.stream().filter(result -> THING_TYPE_OUTPUT.equals(result.getThingTypeUID())).count());
assertEquals(0, results.stream().filter(result -> THING_TYPE_SHUTTER.equals(result.getThingTypeUID())).count());
}
@@ -198,7 +207,7 @@ class SatelDeviceDiscoveryServiceTest {
thread.join();
verifyNoMoreInteractions(bridgeHandler);
assertEquals(0, results.size());
verifyNoInteractions(listener);
}
private void setUpCommandResponse(int deviceKind) {
@@ -25,6 +25,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@@ -75,6 +76,19 @@ public class TPLinkSmartHomeDiscoveryService extends AbstractDiscoveryService im
Connection.TP_LINK_SMART_HOME_PORT);
}
/**
* Constructor for tests only.
*
* @param scheduler the {@link ScheduledExecutorService} to use during testing.
*/
TPLinkSmartHomeDiscoveryService(ScheduledExecutorService scheduler) throws UnknownHostException {
super(scheduler, SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT_SECONDS, true, null, null);
final InetAddress broadcast = InetAddress.getByName(BROADCAST_IP);
final byte[] discoverbuffer = CryptUtil.encrypt(Commands.getSysinfo());
discoverPacket = new DatagramPacket(discoverbuffer, discoverbuffer.length, broadcast,
Connection.TP_LINK_SMART_HOME_PORT);
}
@Override
public @Nullable String getLastKnownIpAddress(String deviceId) {
return idInetAddressCache.get(deviceId);
@@ -35,6 +35,7 @@ import org.mockito.stubbing.Answer;
import org.openhab.binding.tplinksmarthome.internal.model.ModelTestUtil;
import org.openhab.core.config.discovery.DiscoveryListener;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.util.SameThreadExecutorService;
/**
* Test class for {@link TPLinkSmartHomeDiscoveryService} class.
@@ -57,7 +58,7 @@ public class TPLinkSmartHomeDiscoveryServiceTest {
}
public void setUp(String filename) throws IOException {
discoveryService = new TPLinkSmartHomeDiscoveryService() {
discoveryService = new TPLinkSmartHomeDiscoveryService(new SameThreadExecutorService()) {
@Override
protected DatagramSocket sendDiscoveryPacket() throws IOException {
return discoverSocket;
@@ -91,7 +92,7 @@ public class TPLinkSmartHomeDiscoveryServiceTest {
setUp(filename);
discoveryService.startScan();
ArgumentCaptor<DiscoveryResult> discoveryResultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class);
verify(discoveryListener, timeout(1000L)).thingDiscovered(any(), discoveryResultCaptor.capture());
verify(discoveryListener).thingDiscovered(any(), discoveryResultCaptor.capture());
DiscoveryResult discoveryResult = discoveryResultCaptor.getValue();
assertEquals(TPLinkSmartHomeBindingConstants.BINDING_ID, discoveryResult.getBindingId(),
"Check if correct binding id found");
@@ -21,6 +21,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -71,6 +72,15 @@ public class TradfriDiscoveryService extends AbstractThingHandlerDiscoveryServic
super(TradfriGatewayHandler.class, SUPPORTED_DEVICE_TYPES_UIDS, 10, true);
}
/**
* Constructor for tests only.
*
* @param scheduler the {@link ScheduledExecutorService} to use during testing.
*/
TradfriDiscoveryService(ScheduledExecutorService scheduler) {
super(scheduler, TradfriGatewayHandler.class, SUPPORTED_DEVICE_TYPES_UIDS, 10, true, null, null);
}
@Override
protected void startScan() {
thingHandler.startScan();
@@ -38,6 +38,7 @@ import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.builder.BridgeBuilder;
import org.openhab.core.util.SameThreadExecutorService;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@@ -82,7 +83,7 @@ public class TradfriDiscoveryServiceTest {
public void setUp() {
when(handler.getThing()).thenReturn(BridgeBuilder.create(GATEWAY_TYPE_UID, "1").build());
discovery = new TradfriDiscoveryService();
discovery = new TradfriDiscoveryService(new SameThreadExecutorService());
discovery.setThingHandler(handler);
discovery.addDiscoveryListener(listener);
}
@@ -108,13 +109,12 @@ public class TradfriDiscoveryServiceTest {
}
@Test
public void validDiscoveryResultWhiteLightW() throws InterruptedException {
public void validDiscoveryResultWhiteLightW() {
String json = "{\"9001\":\"TRADFRI bulb E27 W opal 1000lm\",\"9002\":1492856270,\"9020\":1507194357,\"9003\":65537,\"3311\":[{\"5850\":1,\"5851\":254,\"9003\":0}],\"9054\":0,\"5750\":2,\"9019\":1,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI bulb E27 W opal 1000lm\",\"2\":\"\",\"3\":\"1.2.214\",\"6\":1}}";
JsonObject data = JsonParser.parseString(json).getAsJsonObject();
discovery.onUpdate("65537", data);
Thread.sleep(500L);
assertNotNull(discoveryResult);
assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW));
assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0100:1:65537")));
@@ -125,13 +125,12 @@ public class TradfriDiscoveryServiceTest {
}
@Test
public void validDiscoveryResultWhiteLightWS() throws InterruptedException {
public void validDiscoveryResultWhiteLightWS() {
String json = "{\"9001\":\"TRADFRI bulb E27 WS opal 980lm\",\"9002\":1492955148,\"9020\":1507200447,\"9003\":65537,\"3311\":[{\"5710\":26909,\"5850\":1,\"5851\":203,\"5707\":0,\"5708\":0,\"5709\":30140,\"5711\":370,\"5706\":\"f1e0b5\",\"9003\":0}],\"9054\":0,\"5750\":2,\"9019\":1,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI bulb E27 WS opal 980lm\",\"2\":\"\",\"3\":\"1.2.217\",\"6\":1}}";
JsonObject data = JsonParser.parseString(json).getAsJsonObject();
discovery.onUpdate("65537", data);
Thread.sleep(500L);
assertNotNull(discoveryResult);
assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW));
assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0220:1:65537")));
@@ -142,7 +141,7 @@ public class TradfriDiscoveryServiceTest {
}
@Test
public void validDiscoveryResultWhiteLightWSWithIncompleteJson() throws InterruptedException {
public void validDiscoveryResultWhiteLightWSWithIncompleteJson() {
// We do not always receive a COLOR = "5706" attribute, even the light supports it - but the gateway does not
// seem to have this information, if the bulb is unreachable.
String json = "{\"9001\":\"TRADFRI bulb E27 WS opal 980lm\",\"9002\":1492955148,\"9020\":1506968670,\"9003\":65537,\"3311\":[{\"9003\":0}],\"9054\":0,\"5750\":2,\"9019\":0,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI bulb E27 WS opal 980lm\",\"2\":\"\",\"3\":\"1.2.217\",\"6\":1}}";
@@ -150,7 +149,6 @@ public class TradfriDiscoveryServiceTest {
discovery.onUpdate("65537", data);
Thread.sleep(500L);
assertNotNull(discoveryResult);
assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW));
assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0220:1:65537")));
@@ -161,13 +159,12 @@ public class TradfriDiscoveryServiceTest {
}
@Test
public void validDiscoveryResultColorLightCWS() throws InterruptedException {
public void validDiscoveryResultColorLightCWS() {
String json = "{\"9001\":\"TRADFRI bulb E27 CWS opal 600lm\",\"9002\":1505151864,\"9020\":1505433527,\"9003\":65550,\"9019\":1,\"9054\":0,\"5750\":2,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI bulb E27 CWS opal 600lm\",\"2\":\"\",\"3\":\"1.3.002\",\"6\":1},\"3311\":[{\"5850\":1,\"5708\":0,\"5851\":254,\"5707\":0,\"5709\":33137,\"5710\":27211,\"5711\":0,\"5706\":\"efd275\",\"9003\":0}]}";
JsonObject data = JsonParser.parseString(json).getAsJsonObject();
discovery.onUpdate("65550", data);
Thread.sleep(500L);
assertNotNull(discoveryResult);
assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW));
assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0210:1:65550")));
@@ -178,13 +175,12 @@ public class TradfriDiscoveryServiceTest {
}
@Test
public void validDiscoveryResultAlternativeColorLightCWS() throws InterruptedException {
public void validDiscoveryResultAlternativeColorLightCWS() {
String json = "{\"3311\":[{\"5850\":1,\"5709\":32886,\"5851\":216,\"5707\":5309,\"5708\":52400,\"5710\":27217,\"5706\":\"efd275\",\"9003\":0}],\"9001\":\"Mushroom lamp\",\"9002\":1571036916,\"9020\":1571588312,\"9003\":65539,\"9054\":0,\"9019\":1,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI bulb E27 C\\/WS opal 600\",\"2\":\"\",\"3\":\"1.3.009\",\"6\":1},\"5750\":2}";
JsonObject data = JsonParser.parseString(json).getAsJsonObject();
discovery.onUpdate("65539", data);
Thread.sleep(500L);
assertNotNull(discoveryResult);
assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW));
assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0210:1:65539")));
@@ -195,13 +191,12 @@ public class TradfriDiscoveryServiceTest {
}
@Test
public void validDiscoveryResultRemoteControl() throws InterruptedException {
public void validDiscoveryResultRemoteControl() {
String json = "{\"9001\":\"TRADFRI remote control\",\"9002\":1492843083,\"9020\":1506977986,\"9003\":65536,\"9054\":0,\"5750\":0,\"9019\":1,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI remote control\",\"2\":\"\",\"3\":\"1.2.214\",\"6\":3,\"9\":47},\"15009\":[{\"9003\":0}]}";
JsonObject data = JsonParser.parseString(json).getAsJsonObject();
discovery.onUpdate("65536", data);
Thread.sleep(500L);
assertNotNull(discoveryResult);
assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW));
assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0830:1:65536")));
@@ -212,13 +207,12 @@ public class TradfriDiscoveryServiceTest {
}
@Test
public void validDiscoveryResultWirelessDimmer() throws InterruptedException {
public void validDiscoveryResultWirelessDimmer() {
String json = "{\"9001\":\"TRADFRI wireless dimmer\",\"9002\":1492843083,\"9020\":1506977986,\"9003\":65536,\"9054\":0,\"5750\":0,\"9019\":1,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI wireless dimmer\",\"2\":\"\",\"3\":\"1.2.214\",\"6\":3,\"9\":47},\"15009\":[{\"9003\":0}]}";
JsonObject data = JsonParser.parseString(json).getAsJsonObject();
discovery.onUpdate("65536", data);
Thread.sleep(500L);
assertNotNull(discoveryResult);
assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW));
assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0820:1:65536")));
@@ -229,13 +223,12 @@ public class TradfriDiscoveryServiceTest {
}
@Test
public void validDiscoveryResultMotionSensor() throws InterruptedException {
public void validDiscoveryResultMotionSensor() {
String json = "{\"9001\":\"TRADFRI motion sensor\",\"9002\":1492955083,\"9020\":1507120083,\"9003\":65538,\"9054\":0,\"5750\":4,\"9019\":1,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI motion sensor\",\"2\":\"\",\"3\":\"1.2.214\",\"6\":3,\"9\":60},\"3300\":[{\"9003\":0}]}";
JsonObject data = JsonParser.parseString(json).getAsJsonObject();
discovery.onUpdate("65538", data);
Thread.sleep(500L);
assertNotNull(discoveryResult);
assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW));
assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0107:1:65538")));
@@ -246,13 +239,12 @@ public class TradfriDiscoveryServiceTest {
}
@Test
public void validDiscoveryResultAirPurifier() throws InterruptedException {
public void validDiscoveryResultAirPurifier() {
String json = "{\"3\":{\"0\":\"IKEAofSweden\",\"1\":\"STARKVINDAirpurifier\",\"2\":\"\",\"3\":\"1.0.033\",\"6\":1,\"7\":4364},\"5750\":10,\"9001\":\"Luftreiniger\",\"9002\":1633096623,\"9003\":65548,\"9019\":1,\"9020\":1633096633,\"9054\":0,\"15025\":[{\"5900\":1,\"5902\":2,\"5903\":0,\"5904\":259200,\"5905\":0,\"5906\":0,\"5907\":5,\"5908\":10,\"5909\":2,\"5910\":259198,\"9003\":0}]}";
JsonObject data = JsonParser.parseString(json).getAsJsonObject();
discovery.onUpdate("65548", data);
Thread.sleep(500L);
assertNotNull(discoveryResult);
assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW));
assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0007:1:65548")));