[mielecloud] Initial contribution of the Miele Cloud binding (#9146)

Also-by: Bert Plonus <bert.plonus@miele.com>
Also-by: Martin Lepsy <martin.lepsy@miele.com>
Also-by: Benjamin Bolte <benjamin.bolte@itemis.de>
Signed-off-by: Björn Lange <bjoern.lange@itemis.de>
This commit is contained in:
Björn Lange
2021-05-25 22:06:49 +02:00
committed by GitHub
parent 5ec535f37b
commit 705f5c577c
230 changed files with 61858 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public final class MieleCloudBindingTestConstants {
private MieleCloudBindingTestConstants() {
throw new IllegalStateException("MieleCloudTestConstants must not be instantiated");
}
public static final String BRIDGE_ID = "genesis";
public static final String SERVICE_HANDLE = MieleCloudBindingConstants.THING_TYPE_BRIDGE.getAsString() + ":"
+ BRIDGE_ID;
}

View File

@@ -0,0 +1,334 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.auth;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.openhab.binding.mielecloud.internal.MieleCloudBindingTestConstants;
import org.openhab.binding.mielecloud.internal.util.ReflectionUtil;
import org.openhab.core.auth.client.oauth2.AccessTokenRefreshListener;
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
import org.openhab.core.auth.client.oauth2.OAuthClientService;
import org.openhab.core.auth.client.oauth2.OAuthFactory;
import org.openhab.core.auth.client.oauth2.OAuthResponseException;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class OpenHabOAuthTokenRefresherTest {
private static final String ACCESS_TOKEN = "DE_0123456789abcdef0123456789abcdef";
private boolean hasAccessTokenRefreshListenerForServiceHandle(OpenHabOAuthTokenRefresher refresher,
String serviceHandle)
throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
return ReflectionUtil
.<Map<String, @Nullable AccessTokenRefreshListener>> getPrivate(refresher, "listenerByServiceHandle")
.get(MieleCloudBindingTestConstants.SERVICE_HANDLE) != null;
}
private AccessTokenRefreshListener getAccessTokenRefreshListenerByServiceHandle(
OpenHabOAuthTokenRefresher refresher, String serviceHandle)
throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
AccessTokenRefreshListener listener = ReflectionUtil
.<Map<String, @Nullable AccessTokenRefreshListener>> getPrivate(refresher, "listenerByServiceHandle")
.get(MieleCloudBindingTestConstants.SERVICE_HANDLE);
assertNotNull(listener);
return Objects.requireNonNull(listener);
}
@Test
public void whenTheAccountWasNotConfiguredPriorToTheThingInitializingThenNoRefreshListenerCanBeRegistered() {
// given:
OAuthFactory oauthFactory = mock(OAuthFactory.class);
OpenHabOAuthTokenRefresher refresher = new OpenHabOAuthTokenRefresher(oauthFactory);
OAuthTokenRefreshListener listener = mock(OAuthTokenRefreshListener.class);
// when:
assertThrows(OAuthException.class, () -> {
refresher.setRefreshListener(listener, MieleCloudBindingTestConstants.SERVICE_HANDLE);
});
}
@Test
public void whenARefreshListenerIsRegisteredThenAListenerIsRegisteredAtTheClientService()
throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
// given:
OAuthClientService oauthClientService = mock(OAuthClientService.class);
OAuthFactory oauthFactory = mock(OAuthFactory.class);
when(oauthFactory.getOAuthClientService(MieleCloudBindingTestConstants.SERVICE_HANDLE))
.thenReturn(oauthClientService);
OpenHabOAuthTokenRefresher refresher = new OpenHabOAuthTokenRefresher(oauthFactory);
OAuthTokenRefreshListener listener = mock(OAuthTokenRefreshListener.class);
// when:
refresher.setRefreshListener(listener, MieleCloudBindingTestConstants.SERVICE_HANDLE);
// then:
verify(oauthClientService).addAccessTokenRefreshListener(any());
assertNotNull(
getAccessTokenRefreshListenerByServiceHandle(refresher, MieleCloudBindingTestConstants.SERVICE_HANDLE));
}
@Test
public void whenTokenIsRefreshedThenTheListenerIsCalledWithTheNewAccessToken()
throws org.openhab.core.auth.client.oauth2.OAuthException, IOException, OAuthResponseException {
// given:
AccessTokenResponse accessTokenResponse = new AccessTokenResponse();
accessTokenResponse.setAccessToken(ACCESS_TOKEN);
OAuthClientService oauthClientService = mock(OAuthClientService.class);
OAuthFactory oauthFactory = mock(OAuthFactory.class);
when(oauthFactory.getOAuthClientService(MieleCloudBindingTestConstants.SERVICE_HANDLE))
.thenReturn(oauthClientService);
OpenHabOAuthTokenRefresher refresher = new OpenHabOAuthTokenRefresher(oauthFactory);
when(oauthClientService.refreshToken()).thenAnswer(new Answer<@Nullable AccessTokenResponse>() {
@Override
@Nullable
public AccessTokenResponse answer(@Nullable InvocationOnMock invocation) throws Throwable {
getAccessTokenRefreshListenerByServiceHandle(refresher, MieleCloudBindingTestConstants.SERVICE_HANDLE)
.onAccessTokenResponse(accessTokenResponse);
return accessTokenResponse;
}
});
OAuthTokenRefreshListener listener = mock(OAuthTokenRefreshListener.class);
refresher.setRefreshListener(listener, MieleCloudBindingTestConstants.SERVICE_HANDLE);
// when:
refresher.refreshToken(MieleCloudBindingTestConstants.SERVICE_HANDLE);
// then:
verify(listener).onNewAccessToken(ACCESS_TOKEN);
}
@Test
public void whenTokenIsRefreshedAndNoAccessTokenIsProvidedThenTheListenerIsNotNotified()
throws org.openhab.core.auth.client.oauth2.OAuthException, IOException, OAuthResponseException {
// given:
AccessTokenResponse accessTokenResponse = new AccessTokenResponse();
OAuthClientService oauthClientService = mock(OAuthClientService.class);
OAuthFactory oauthFactory = mock(OAuthFactory.class);
when(oauthFactory.getOAuthClientService(MieleCloudBindingTestConstants.SERVICE_HANDLE))
.thenReturn(oauthClientService);
OpenHabOAuthTokenRefresher refresher = new OpenHabOAuthTokenRefresher(oauthFactory);
when(oauthClientService.refreshToken()).thenAnswer(new Answer<@Nullable AccessTokenResponse>() {
@Override
@Nullable
public AccessTokenResponse answer(@Nullable InvocationOnMock invocation) throws Throwable {
getAccessTokenRefreshListenerByServiceHandle(refresher, MieleCloudBindingTestConstants.SERVICE_HANDLE)
.onAccessTokenResponse(accessTokenResponse);
return accessTokenResponse;
}
});
OAuthTokenRefreshListener listener = mock(OAuthTokenRefreshListener.class);
refresher.setRefreshListener(listener, MieleCloudBindingTestConstants.SERVICE_HANDLE);
// when:
assertThrows(OAuthException.class, () -> {
try {
refresher.refreshToken(MieleCloudBindingTestConstants.SERVICE_HANDLE);
} catch (OAuthException e) {
verifyNoInteractions(listener);
throw e;
}
});
}
@Test
public void whenTokenRefreshFailsWithOAuthExceptionThenTheListenerIsNotNotified()
throws org.openhab.core.auth.client.oauth2.OAuthException, IOException, OAuthResponseException {
// given:
OAuthClientService oauthClientService = mock(OAuthClientService.class);
when(oauthClientService.refreshToken()).thenThrow(new org.openhab.core.auth.client.oauth2.OAuthException());
OAuthFactory oauthFactory = mock(OAuthFactory.class);
when(oauthFactory.getOAuthClientService(MieleCloudBindingTestConstants.SERVICE_HANDLE))
.thenReturn(oauthClientService);
OpenHabOAuthTokenRefresher refresher = new OpenHabOAuthTokenRefresher(oauthFactory);
OAuthTokenRefreshListener listener = mock(OAuthTokenRefreshListener.class);
refresher.setRefreshListener(listener, MieleCloudBindingTestConstants.SERVICE_HANDLE);
// when:
assertThrows(OAuthException.class, () -> {
try {
refresher.refreshToken(MieleCloudBindingTestConstants.SERVICE_HANDLE);
} catch (OAuthException e) {
verifyNoInteractions(listener);
throw e;
}
});
}
@Test
public void whenTokenRefreshFailsDueToNetworkErrorThenTheListenerIsNotNotified()
throws org.openhab.core.auth.client.oauth2.OAuthException, IOException, OAuthResponseException {
// given:
OAuthClientService oauthClientService = mock(OAuthClientService.class);
when(oauthClientService.refreshToken()).thenThrow(new IOException());
OAuthFactory oauthFactory = mock(OAuthFactory.class);
when(oauthFactory.getOAuthClientService(MieleCloudBindingTestConstants.SERVICE_HANDLE))
.thenReturn(oauthClientService);
OpenHabOAuthTokenRefresher refresher = new OpenHabOAuthTokenRefresher(oauthFactory);
OAuthTokenRefreshListener listener = mock(OAuthTokenRefreshListener.class);
refresher.setRefreshListener(listener, MieleCloudBindingTestConstants.SERVICE_HANDLE);
// when:
assertThrows(OAuthException.class, () -> {
try {
refresher.refreshToken(MieleCloudBindingTestConstants.SERVICE_HANDLE);
} catch (OAuthException e) {
verifyNoInteractions(listener);
throw e;
}
});
}
@Test
public void whenTokenRefreshFailsDueToAnIllegalResponseThenTheListenerIsNotNotified()
throws org.openhab.core.auth.client.oauth2.OAuthException, IOException, OAuthResponseException {
// given:
OAuthClientService oauthClientService = mock(OAuthClientService.class);
when(oauthClientService.refreshToken()).thenThrow(new OAuthResponseException());
OAuthFactory oauthFactory = mock(OAuthFactory.class);
when(oauthFactory.getOAuthClientService(MieleCloudBindingTestConstants.SERVICE_HANDLE))
.thenReturn(oauthClientService);
OpenHabOAuthTokenRefresher refresher = new OpenHabOAuthTokenRefresher(oauthFactory);
OAuthTokenRefreshListener listener = mock(OAuthTokenRefreshListener.class);
refresher.setRefreshListener(listener, MieleCloudBindingTestConstants.SERVICE_HANDLE);
// when:
assertThrows(OAuthException.class, () -> {
try {
refresher.refreshToken(MieleCloudBindingTestConstants.SERVICE_HANDLE);
} catch (OAuthException e) {
verifyNoInteractions(listener);
throw e;
}
});
}
@Test
public void whenTheRefreshListenerIsUnsetAndWasNotRegisteredBeforeThenNothingHappens()
throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
// given:
OAuthFactory oauthFactory = mock(OAuthFactory.class);
OpenHabOAuthTokenRefresher refresher = new OpenHabOAuthTokenRefresher(oauthFactory);
// when:
refresher.unsetRefreshListener(MieleCloudBindingTestConstants.SERVICE_HANDLE);
// then:
assertFalse(hasAccessTokenRefreshListenerForServiceHandle(refresher,
MieleCloudBindingTestConstants.SERVICE_HANDLE));
}
@Test
public void whenTheRefreshListenerIsUnsetAndTheClientServiceIsNotAvailableThenTheListenerIsCleared()
throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
// given:
OAuthClientService oauthClientService = mock(OAuthClientService.class);
OAuthFactory oauthFactory = mock(OAuthFactory.class);
when(oauthFactory.getOAuthClientService(MieleCloudBindingTestConstants.SERVICE_HANDLE))
.thenReturn(oauthClientService);
OpenHabOAuthTokenRefresher refresher = new OpenHabOAuthTokenRefresher(oauthFactory);
OAuthTokenRefreshListener listener = mock(OAuthTokenRefreshListener.class);
refresher.setRefreshListener(listener, MieleCloudBindingTestConstants.SERVICE_HANDLE);
// when:
refresher.unsetRefreshListener(MieleCloudBindingTestConstants.SERVICE_HANDLE);
// then:
assertFalse(hasAccessTokenRefreshListenerForServiceHandle(refresher,
MieleCloudBindingTestConstants.SERVICE_HANDLE));
}
@Test
public void whenTheRefreshListenerIsUnsetThenTheListenerIsClearedAndRemovedFromTheClientService()
throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
// given:
OAuthClientService oauthClientService = mock(OAuthClientService.class);
OAuthFactory oauthFactory = mock(OAuthFactory.class);
when(oauthFactory.getOAuthClientService(MieleCloudBindingTestConstants.SERVICE_HANDLE))
.thenReturn(oauthClientService);
OpenHabOAuthTokenRefresher refresher = new OpenHabOAuthTokenRefresher(oauthFactory);
OAuthTokenRefreshListener listener = mock(OAuthTokenRefreshListener.class);
refresher.setRefreshListener(listener, MieleCloudBindingTestConstants.SERVICE_HANDLE);
// when:
refresher.unsetRefreshListener(MieleCloudBindingTestConstants.SERVICE_HANDLE);
// then:
verify(oauthClientService).removeAccessTokenRefreshListener(any());
assertFalse(hasAccessTokenRefreshListenerForServiceHandle(refresher,
MieleCloudBindingTestConstants.SERVICE_HANDLE));
}
@Test
public void whenTokensAreRemovedThenTheRuntimeIsRequestedToDeleteServiceAndAccessToken()
throws org.openhab.core.auth.client.oauth2.OAuthException, IOException, OAuthResponseException {
// given:
OAuthClientService oauthClientService = mock(OAuthClientService.class);
OAuthFactory oauthFactory = mock(OAuthFactory.class);
when(oauthFactory.getOAuthClientService(MieleCloudBindingTestConstants.SERVICE_HANDLE))
.thenReturn(oauthClientService);
OpenHabOAuthTokenRefresher refresher = new OpenHabOAuthTokenRefresher(oauthFactory);
OAuthTokenRefreshListener listener = mock(OAuthTokenRefreshListener.class);
refresher.setRefreshListener(listener, MieleCloudBindingTestConstants.SERVICE_HANDLE);
// when:
refresher.removeTokensFromStorage(MieleCloudBindingTestConstants.SERVICE_HANDLE);
// then:
verify(oauthFactory).deleteServiceAndAccessToken(MieleCloudBindingTestConstants.SERVICE_HANDLE);
}
}

View File

@@ -0,0 +1,358 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.config;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.openhab.binding.mielecloud.internal.util.ReflectionUtil.getPrivate;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.openhab.binding.mielecloud.internal.MieleCloudBindingConstants;
import org.openhab.binding.mielecloud.internal.MieleCloudBindingTestConstants;
import org.openhab.binding.mielecloud.internal.auth.OAuthException;
import org.openhab.binding.mielecloud.internal.config.exception.NoOngoingAuthorizationException;
import org.openhab.binding.mielecloud.internal.config.exception.OngoingAuthorizationException;
import org.openhab.core.auth.client.oauth2.OAuthClientService;
import org.openhab.core.auth.client.oauth2.OAuthFactory;
import org.openhab.core.auth.client.oauth2.OAuthResponseException;
import org.openhab.core.thing.ThingUID;
/**
* @author Björn Lange - Initial Contribution
*/
@NonNullByDefault
public final class OAuthAuthorizationHandlerImplTest {
private static final String CLIENT_ID = "01234567-890a-bcde-f012-34567890abcd";
private static final String CLIENT_SECRET = "0123456789abcdefghijklmnopqrstiu";
private static final String REDIRECT_URL = "http://127.0.0.1:8080/mielecloud/result";
private static final String AUTH_CODE = "abcdef";
private static final ThingUID BRIDGE_UID = new ThingUID(MieleCloudBindingConstants.THING_TYPE_BRIDGE,
MieleCloudBindingTestConstants.BRIDGE_ID);
private static final String EMAIL = "openhab@openhab.org";
@Nullable
private OAuthClientService clientService;
@Nullable
private ScheduledFuture<?> timer;
@Nullable
private Runnable scheduledRunnable;
@Nullable
private OAuthAuthorizationHandler authorizationHandler;
private OAuthClientService getClientService() {
final OAuthClientService clientService = this.clientService;
assertNotNull(clientService);
return Objects.requireNonNull(clientService);
}
private ScheduledFuture<?> getTimer() {
final ScheduledFuture<?> timer = this.timer;
assertNotNull(timer);
return Objects.requireNonNull(timer);
}
private Runnable getScheduledRunnable() {
final Runnable scheduledRunnable = this.scheduledRunnable;
assertNotNull(scheduledRunnable);
return Objects.requireNonNull(scheduledRunnable);
}
private OAuthAuthorizationHandler getAuthorizationHandler() {
final OAuthAuthorizationHandler authorizationHandler = this.authorizationHandler;
assertNotNull(authorizationHandler);
return Objects.requireNonNull(authorizationHandler);
}
@BeforeEach
public void setUp() {
OAuthClientService clientService = mock(OAuthClientService.class);
OAuthFactory oauthFactory = mock(OAuthFactory.class);
when(oauthFactory.createOAuthClientService(anyString(), anyString(), anyString(), anyString(), anyString(),
isNull(), any())).thenReturn(clientService);
ScheduledFuture<?> timer = mock(ScheduledFuture.class);
when(timer.isDone()).thenReturn(false);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
when(scheduler.schedule(ArgumentMatchers.<Runnable> any(), anyLong(), any())).thenAnswer(invocation -> {
scheduledRunnable = invocation.getArgument(0);
return timer;
});
OAuthAuthorizationHandler authorizationHandler = new OAuthAuthorizationHandlerImpl(oauthFactory, scheduler);
this.clientService = clientService;
this.timer = timer;
this.scheduledRunnable = null;
this.authorizationHandler = authorizationHandler;
}
@Test
public void whenTheAuthorizationIsCompletedInTimeThenTheTimerIsCancelledAndAllResourcesAreCleanedUp()
throws Exception {
// given:
getAuthorizationHandler().beginAuthorization(CLIENT_ID, CLIENT_SECRET, BRIDGE_UID, EMAIL);
getAuthorizationHandler().getAuthorizationUrl(REDIRECT_URL);
// when:
getAuthorizationHandler().completeAuthorization("http://127.0.0.1:8080/mielecloud/result?code=abc&state=def");
// then:
assertNull(getPrivate(getAuthorizationHandler(), "timer"));
verify(getTimer()).cancel(false);
assertNull(getPrivate(getAuthorizationHandler(), "oauthClientService"));
assertNull(getPrivate(getAuthorizationHandler(), "bridgeUid"));
assertNull(getPrivate(getAuthorizationHandler(), "redirectUri"));
assertNull(getPrivate(getAuthorizationHandler(), "email"));
verify(getClientService()).extractAuthCodeFromAuthResponse(anyString());
verify(getClientService()).getAccessTokenResponseByAuthorizationCode(isNull(), anyString());
}
@Test
public void whenTheAuthorizationTimesOutThenTheOngoingAuthorizationIsCancelled() throws Exception {
// given:
getAuthorizationHandler().beginAuthorization(CLIENT_ID, CLIENT_SECRET, BRIDGE_UID, EMAIL);
getAuthorizationHandler().getAuthorizationUrl(REDIRECT_URL);
// when:
getScheduledRunnable().run();
// then:
assertNull(getPrivate(getAuthorizationHandler(), "oauthClientService"));
assertNull(getPrivate(getAuthorizationHandler(), "bridgeUid"));
assertNull(getPrivate(getAuthorizationHandler(), "redirectUri"));
assertNull(getPrivate(getAuthorizationHandler(), "email"));
assertNull(getPrivate(getAuthorizationHandler(), "timer"));
verify(getTimer()).cancel(false);
}
@Test
public void whenTheAuthorizationCompletesAfterItTimedOutThenAnNoOngoingAuthorizationExceptionIsThrown()
throws Exception {
// given:
getAuthorizationHandler().beginAuthorization(CLIENT_ID, CLIENT_SECRET, BRIDGE_UID, EMAIL);
getAuthorizationHandler().getAuthorizationUrl(REDIRECT_URL);
getScheduledRunnable().run();
// when:
assertThrows(NoOngoingAuthorizationException.class, () -> {
getAuthorizationHandler()
.completeAuthorization("http://127.0.0.1:8080/mielecloud/result?code=abc&state=def");
});
}
@Test
public void whenASecondAuthorizationIsBegunWhileAnotherIsStillOngoingThenAnOngoingAuthorizationExceptionIsThrown() {
// given:
getAuthorizationHandler().beginAuthorization(CLIENT_ID, CLIENT_SECRET, BRIDGE_UID, EMAIL);
// when:
assertThrows(OngoingAuthorizationException.class, () -> {
getAuthorizationHandler().beginAuthorization(CLIENT_ID, CLIENT_SECRET, BRIDGE_UID, EMAIL);
});
}
@Test
public void whenNoAuthorizationIsOngoingAndTheAuthorizationUrlIsRequestedThenAnNoOngoingAuthorizationExceptionIsThrown() {
// when:
assertThrows(NoOngoingAuthorizationException.class, () -> {
getAuthorizationHandler().getAuthorizationUrl(REDIRECT_URL);
});
}
@Test
public void whenGetAuthorizationUrlFromTheFrameworkFailsThenTheOngoingAuthorizationIsAborted()
throws org.openhab.core.auth.client.oauth2.OAuthException, IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, SecurityException {
// given:
when(getClientService().getAuthorizationUrl(anyString(), isNull(), isNull()))
.thenThrow(new org.openhab.core.auth.client.oauth2.OAuthException());
getAuthorizationHandler().beginAuthorization(CLIENT_ID, CLIENT_SECRET, BRIDGE_UID, EMAIL);
// when:
assertThrows(OAuthException.class, () -> {
try {
getAuthorizationHandler().getAuthorizationUrl(REDIRECT_URL);
} catch (OAuthException e) {
assertNull(getPrivate(getAuthorizationHandler(), "timer"));
assertNull(getPrivate(getAuthorizationHandler(), "oauthClientService"));
assertNull(getPrivate(getAuthorizationHandler(), "bridgeUid"));
assertNull(getPrivate(getAuthorizationHandler(), "redirectUri"));
assertNull(getPrivate(getAuthorizationHandler(), "email"));
throw e;
}
});
}
@Test
public void whenExtractingTheAuthCodeFromTheResponseFailsThenAnOAuthExceptionIsThrownAndAllResourcesAreCleanedUp()
throws Exception {
// given:
when(getClientService().extractAuthCodeFromAuthResponse(anyString()))
.thenThrow(new org.openhab.core.auth.client.oauth2.OAuthException());
getAuthorizationHandler().beginAuthorization(CLIENT_ID, CLIENT_SECRET, BRIDGE_UID, EMAIL);
getAuthorizationHandler().getAuthorizationUrl(REDIRECT_URL);
// when:
assertThrows(OAuthException.class, () -> {
try {
getAuthorizationHandler()
.completeAuthorization("http://127.0.0.1:8080/mielecloud/result?code=abc&state=def");
} catch (OAuthException e) {
assertNull(getPrivate(getAuthorizationHandler(), "timer"));
assertNull(getPrivate(getAuthorizationHandler(), "oauthClientService"));
assertNull(getPrivate(getAuthorizationHandler(), "bridgeUid"));
assertNull(getPrivate(getAuthorizationHandler(), "email"));
assertNull(getPrivate(getAuthorizationHandler(), "redirectUri"));
throw e;
}
});
}
@Test
public void whenRetrievingTheAccessTokenFailsDueToANetworkErrorThenAnOAuthExceptionIsThrownAndAllResourcesAreCleanedUp()
throws Exception {
// given:
when(getClientService().extractAuthCodeFromAuthResponse(anyString())).thenReturn(AUTH_CODE);
when(getClientService().getAccessTokenResponseByAuthorizationCode(anyString(), anyString()))
.thenThrow(new IOException());
getAuthorizationHandler().beginAuthorization(CLIENT_ID, CLIENT_SECRET, BRIDGE_UID, EMAIL);
getAuthorizationHandler().getAuthorizationUrl(REDIRECT_URL);
// when:
assertThrows(OAuthException.class, () -> {
try {
getAuthorizationHandler()
.completeAuthorization("http://127.0.0.1:8080/mielecloud/result?code=abc&state=def");
} catch (OAuthException e) {
assertNull(getPrivate(getAuthorizationHandler(), "timer"));
assertNull(getPrivate(getAuthorizationHandler(), "oauthClientService"));
assertNull(getPrivate(getAuthorizationHandler(), "bridgeUid"));
assertNull(getPrivate(getAuthorizationHandler(), "email"));
assertNull(getPrivate(getAuthorizationHandler(), "redirectUri"));
throw e;
}
});
}
@Test
public void whenRetrievingTheAccessTokenFailsDueToAnIllegalAnswerFromTheMieleServiceThenAnOAuthExceptionIsThrownAndAllResourcesAreCleanedUp()
throws Exception {
// given:
when(getClientService().extractAuthCodeFromAuthResponse(anyString())).thenReturn(AUTH_CODE);
when(getClientService().getAccessTokenResponseByAuthorizationCode(anyString(), anyString()))
.thenThrow(new OAuthResponseException());
getAuthorizationHandler().beginAuthorization(CLIENT_ID, CLIENT_SECRET, BRIDGE_UID, EMAIL);
getAuthorizationHandler().getAuthorizationUrl(REDIRECT_URL);
// when:
assertThrows(OAuthException.class, () -> {
try {
getAuthorizationHandler()
.completeAuthorization("http://127.0.0.1:8080/mielecloud/result?code=abc&state=def");
} catch (OAuthException e) {
assertNull(getPrivate(getAuthorizationHandler(), "timer"));
assertNull(getPrivate(getAuthorizationHandler(), "oauthClientService"));
assertNull(getPrivate(getAuthorizationHandler(), "bridgeUid"));
assertNull(getPrivate(getAuthorizationHandler(), "email"));
assertNull(getPrivate(getAuthorizationHandler(), "redirectUri"));
throw e;
}
});
}
@Test
public void whenRetrievingTheAccessTokenFailsWhileProcessingTheResponseThenAnOAuthExceptionIsThrownAndAllResourcesAreCleanedUp()
throws Exception {
// given:
when(getClientService().extractAuthCodeFromAuthResponse(anyString())).thenReturn(AUTH_CODE);
when(getClientService().getAccessTokenResponseByAuthorizationCode(anyString(), anyString()))
.thenThrow(new org.openhab.core.auth.client.oauth2.OAuthException());
getAuthorizationHandler().beginAuthorization(CLIENT_ID, CLIENT_SECRET, BRIDGE_UID, EMAIL);
getAuthorizationHandler().getAuthorizationUrl(REDIRECT_URL);
// when:
assertThrows(OAuthException.class, () -> {
try {
getAuthorizationHandler()
.completeAuthorization("http://127.0.0.1:8080/mielecloud/result?code=abc&state=def");
} catch (OAuthException e) {
assertNull(getPrivate(getAuthorizationHandler(), "timer"));
assertNull(getPrivate(getAuthorizationHandler(), "oauthClientService"));
assertNull(getPrivate(getAuthorizationHandler(), "bridgeUid"));
assertNull(getPrivate(getAuthorizationHandler(), "email"));
assertNull(getPrivate(getAuthorizationHandler(), "redirectUri"));
throw e;
}
});
}
@Test
public void whenNoAuthorizationIsOngoingThenGetBridgeUidThrowsNoOngoingAuthorizationException() {
// when:
assertThrows(NoOngoingAuthorizationException.class, () -> {
getAuthorizationHandler().getBridgeUid();
});
}
@Test
public void whenNoAuthorizationIsOngoingThenGetEmailThrowsNoOngoingAuthorizationException() {
// when:
assertThrows(NoOngoingAuthorizationException.class, () -> {
getAuthorizationHandler().getEmail();
});
}
@Test
public void whenAnAuthorizationIsOngoingThenGetBridgeUidReturnsTheUidOfTheBridgeBeingAuthorized() {
// given:
getAuthorizationHandler().beginAuthorization(CLIENT_ID, CLIENT_SECRET, BRIDGE_UID, EMAIL);
// when:
ThingUID bridgeUid = getAuthorizationHandler().getBridgeUid();
// then:
assertEquals(BRIDGE_UID, bridgeUid);
}
@Test
public void whenAnAuthorizationIsOngoingThenGetEmailReturnsTheEmailBeingAuthorized() {
// given:
getAuthorizationHandler().beginAuthorization(CLIENT_ID, CLIENT_SECRET, BRIDGE_UID, EMAIL);
// when:
String email = getAuthorizationHandler().getEmail();
// then:
assertEquals(EMAIL, email);
}
}

View File

@@ -0,0 +1,270 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.config;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.Test;
import org.openhab.binding.mielecloud.internal.MieleCloudBindingConstants;
import org.openhab.binding.mielecloud.internal.MieleCloudBindingTestConstants;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
/**
* @author Björn Lange - Initial Contribution
*/
@NonNullByDefault
public class ThingsTemplateGeneratorTest {
private static final String BRIDGE_ID = "genesis";
private static final String ALTERNATIVE_BRIDGE_ID = "mielebridge";
private static final String LOCALE = "en";
private static final String ALTERNATIVE_LOCALE = "de";
private static final String EMAIL = "openhab@openhab.org";
private static final String ALTERNATIVE_EMAIL = "everyone@openhab.org";
@Test
public void whenBridgeIdAndAccessTokenAndLocaleAreProvidedThenAValidBridgeConfigurationTemplateIsGenerated() {
// given:
ThingsTemplateGenerator templateGenerator = new ThingsTemplateGenerator();
// when:
String template = templateGenerator.createBridgeConfigurationTemplate(BRIDGE_ID, EMAIL, LOCALE);
// then:
assertEquals("Bridge mielecloud:account:genesis [ email=\"openhab@openhab.org\", locale=\"en\" ]", template);
}
@Test
public void whenAnAlternativeBridgeIdIsProvidedThenAValidBridgeConfigurationTemplateWithThatIdIsGenerated() {
// given:
ThingsTemplateGenerator templateGenerator = new ThingsTemplateGenerator();
// when:
String template = templateGenerator.createBridgeConfigurationTemplate(ALTERNATIVE_BRIDGE_ID, EMAIL, LOCALE);
// then:
assertEquals("Bridge mielecloud:account:mielebridge [ email=\"openhab@openhab.org\", locale=\"en\" ]",
template);
}
@Test
public void whenAnAlternativeAccessTokenIsProvidedThenAValidBridgeConfigurationTemplateWithThatAccessTokenIsGenerated() {
// given:
ThingsTemplateGenerator templateGenerator = new ThingsTemplateGenerator();
// when:
String template = templateGenerator.createBridgeConfigurationTemplate(BRIDGE_ID, EMAIL, LOCALE);
// then:
assertEquals("Bridge mielecloud:account:genesis [ email=\"openhab@openhab.org\", locale=\"en\" ]", template);
}
@Test
public void whenAnAlternativeLocaleIsProvidedThenAValidBridgeConfigurationTemplateWithThatLocaleIsGenerated() {
// given:
ThingsTemplateGenerator templateGenerator = new ThingsTemplateGenerator();
// when:
String template = templateGenerator.createBridgeConfigurationTemplate(BRIDGE_ID, EMAIL, ALTERNATIVE_LOCALE);
// then:
assertEquals("Bridge mielecloud:account:genesis [ email=\"openhab@openhab.org\", locale=\"de\" ]", template);
}
@Test
public void whenAnAlternativeEmailIsProvidedThenAValidBridgeConfigurationTemplateWithThatEmailIsGenerated() {
// given:
ThingsTemplateGenerator templateGenerator = new ThingsTemplateGenerator();
// when:
String template = templateGenerator.createBridgeConfigurationTemplate(BRIDGE_ID, ALTERNATIVE_EMAIL, LOCALE);
// then:
assertEquals("Bridge mielecloud:account:genesis [ email=\"everyone@openhab.org\", locale=\"en\" ]", template);
}
private Bridge createBridgeMock(String id, String locale, String email) {
Configuration configuration = mock(Configuration.class);
when(configuration.get(MieleCloudBindingConstants.CONFIG_PARAM_LOCALE)).thenReturn(locale);
when(configuration.get(MieleCloudBindingConstants.CONFIG_PARAM_EMAIL)).thenReturn(email);
Bridge bridge = mock(Bridge.class);
when(bridge.getUID()).thenReturn(new ThingUID(MieleCloudBindingConstants.THING_TYPE_BRIDGE, id));
when(bridge.getConfiguration()).thenReturn(configuration);
return bridge;
}
private Thing createThingMock(ThingTypeUID thingTypeUid, String deviceIdentifier, @Nullable String label,
String bridgeId) {
Configuration configuration = mock(Configuration.class);
when(configuration.get(MieleCloudBindingConstants.CONFIG_PARAM_DEVICE_IDENTIFIER)).thenReturn(deviceIdentifier);
Thing thing = mock(Thing.class);
when(thing.getThingTypeUID()).thenReturn(thingTypeUid);
when(thing.getUID()).thenReturn(new ThingUID(thingTypeUid, deviceIdentifier, bridgeId));
when(thing.getLabel()).thenReturn(label);
when(thing.getConfiguration()).thenReturn(configuration);
return thing;
}
private DiscoveryResult createDiscoveryResultMock(ThingTypeUID thingTypeUid, String id, String label,
String bridgeId) {
DiscoveryResult discoveryResult = mock(DiscoveryResult.class);
when(discoveryResult.getLabel()).thenReturn(label);
when(discoveryResult.getThingTypeUID()).thenReturn(thingTypeUid);
when(discoveryResult.getThingUID()).thenReturn(new ThingUID(thingTypeUid, id, bridgeId));
when(discoveryResult.getProperties())
.thenReturn(Collections.singletonMap(MieleCloudBindingConstants.CONFIG_PARAM_DEVICE_IDENTIFIER, id));
return discoveryResult;
}
@Test
public void whenNoThingsArePairedAndNoInboxEntriesAreAvailableThenAnEmptyConfigurationTemplateIsGenerated() {
// given:
ThingsTemplateGenerator templateGenerator = new ThingsTemplateGenerator();
Bridge bridge = createBridgeMock(MieleCloudBindingTestConstants.BRIDGE_ID, LOCALE, EMAIL);
// when:
String template = templateGenerator.createBridgeAndThingConfigurationTemplate(bridge, Collections.emptyList(),
Collections.emptyList());
// then:
assertEquals("Bridge mielecloud:account:genesis [ email=\"openhab@openhab.org\", locale=\"en\" ] {\n}",
template);
}
@Test
public void whenPairedThingsArePresentThenTheyArePresentInTheConfigurationTemplate() {
// given:
ThingsTemplateGenerator templateGenerator = new ThingsTemplateGenerator();
Bridge bridge = createBridgeMock(ALTERNATIVE_BRIDGE_ID, ALTERNATIVE_LOCALE, ALTERNATIVE_EMAIL);
Thing thing1 = createThingMock(MieleCloudBindingConstants.THING_TYPE_OVEN, "000137439123", "Oven H7860XY",
ALTERNATIVE_BRIDGE_ID);
Thing thing2 = createThingMock(MieleCloudBindingConstants.THING_TYPE_HOB, "000160106123", null,
ALTERNATIVE_BRIDGE_ID);
List<Thing> pairedThings = Arrays.asList(thing1, thing2);
// when:
String template = templateGenerator.createBridgeAndThingConfigurationTemplate(bridge, pairedThings,
Collections.emptyList());
// then:
assertEquals("Bridge mielecloud:account:mielebridge [ email=\"everyone@openhab.org\", locale=\"de\" ] {\n"
+ " Thing oven 000137439123 \"Oven H7860XY\" [ deviceIdentifier=\"000137439123\" ]\n"
+ " Thing hob 000160106123 [ deviceIdentifier=\"000160106123\" ]\n}", template);
}
@Test
public void whenDiscoveryResultsAreInTheInboxThenTheyArePresentInTheConfigurationTemplate() {
// given:
ThingsTemplateGenerator templateGenerator = new ThingsTemplateGenerator();
Bridge bridge = createBridgeMock(ALTERNATIVE_BRIDGE_ID, ALTERNATIVE_LOCALE, ALTERNATIVE_EMAIL);
DiscoveryResult discoveryResult1 = createDiscoveryResultMock(
MieleCloudBindingConstants.THING_TYPE_FRIDGE_FREEZER, "000154106123", "Fridge-Freezer Kitchen",
ALTERNATIVE_BRIDGE_ID);
DiscoveryResult discoveryResult2 = createDiscoveryResultMock(
MieleCloudBindingConstants.THING_TYPE_WASHING_MACHINE, "000189106123", "Washing Machine",
ALTERNATIVE_BRIDGE_ID);
List<DiscoveryResult> discoveredThings = Arrays.asList(discoveryResult1, discoveryResult2);
// when:
String template = templateGenerator.createBridgeAndThingConfigurationTemplate(bridge, Collections.emptyList(),
discoveredThings);
// then:
assertEquals("Bridge mielecloud:account:mielebridge [ email=\"everyone@openhab.org\", locale=\"de\" ] {\n"
+ " Thing fridge_freezer 000154106123 \"Fridge-Freezer Kitchen\" [ deviceIdentifier=\"000154106123\" ]\n"
+ " Thing washing_machine 000189106123 \"Washing Machine\" [ deviceIdentifier=\"000189106123\" ]\n}",
template);
}
@Test
public void whenThingsArePresentAndDiscoveryResultsAreInTheInboxThenTheyArePresentInTheConfigurationTemplate() {
// given:
ThingsTemplateGenerator templateGenerator = new ThingsTemplateGenerator();
Bridge bridge = createBridgeMock(ALTERNATIVE_BRIDGE_ID, ALTERNATIVE_LOCALE, EMAIL);
Thing thing1 = createThingMock(MieleCloudBindingConstants.THING_TYPE_OVEN, "000137439123", "Oven H7860XY",
ALTERNATIVE_BRIDGE_ID);
Thing thing2 = createThingMock(MieleCloudBindingConstants.THING_TYPE_HOB, "000160106123", null,
ALTERNATIVE_BRIDGE_ID);
List<Thing> pairedThings = Arrays.asList(thing1, thing2);
DiscoveryResult discoveryResult1 = createDiscoveryResultMock(
MieleCloudBindingConstants.THING_TYPE_FRIDGE_FREEZER, "000154106123", "Fridge-Freezer Kitchen",
ALTERNATIVE_BRIDGE_ID);
DiscoveryResult discoveryResult2 = createDiscoveryResultMock(
MieleCloudBindingConstants.THING_TYPE_WASHING_MACHINE, "000189106123", "Washing Machine",
ALTERNATIVE_BRIDGE_ID);
List<DiscoveryResult> discoveredThings = Arrays.asList(discoveryResult1, discoveryResult2);
// when:
String template = templateGenerator.createBridgeAndThingConfigurationTemplate(bridge, pairedThings,
discoveredThings);
// then:
assertEquals("Bridge mielecloud:account:mielebridge [ email=\"openhab@openhab.org\", locale=\"de\" ] {\n"
+ " Thing oven 000137439123 \"Oven H7860XY\" [ deviceIdentifier=\"000137439123\" ]\n"
+ " Thing hob 000160106123 [ deviceIdentifier=\"000160106123\" ]\n"
+ " Thing fridge_freezer 000154106123 \"Fridge-Freezer Kitchen\" [ deviceIdentifier=\"000154106123\" ]\n"
+ " Thing washing_machine 000189106123 \"Washing Machine\" [ deviceIdentifier=\"000189106123\" ]\n}",
template);
}
@Test
public void whenNoLocaleIsConfiguredThenTheDefaultIsUsed() {
// given:
ThingsTemplateGenerator templateGenerator = new ThingsTemplateGenerator();
Configuration configuration = mock(Configuration.class);
when(configuration.get(MieleCloudBindingConstants.CONFIG_PARAM_LOCALE)).thenReturn(null);
when(configuration.get(MieleCloudBindingConstants.CONFIG_PARAM_EMAIL)).thenReturn(EMAIL);
Bridge bridge = mock(Bridge.class);
when(bridge.getUID()).thenReturn(
new ThingUID(MieleCloudBindingConstants.THING_TYPE_BRIDGE, MieleCloudBindingTestConstants.BRIDGE_ID));
when(bridge.getConfiguration()).thenReturn(configuration);
// when:
String template = templateGenerator.createBridgeAndThingConfigurationTemplate(bridge, Collections.emptyList(),
Collections.emptyList());
// then:
assertEquals("Bridge mielecloud:account:genesis [ email=\"openhab@openhab.org\", locale=\"en\" ] {\n}",
template);
}
}

View File

@@ -0,0 +1,106 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.discovery;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;
import java.util.Optional;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.openhab.binding.mielecloud.internal.MieleCloudBindingConstants;
import org.openhab.binding.mielecloud.internal.webservice.api.DeviceState;
import org.openhab.binding.mielecloud.internal.webservice.api.json.DeviceType;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class ThingInformationExtractorTest {
private static Stream<Arguments> extractedPropertiesContainSerialNumberAndModelIdParameterSource() {
return Stream.of(
Arguments.of(MieleCloudBindingConstants.THING_TYPE_HOOD, DeviceType.HOOD, "000124430018",
Optional.of("000124430017"), Optional.of("Ventilation Hood"), Optional.of("DA-6996"),
"000124430017", "Ventilation Hood DA-6996", "000124430018"),
Arguments.of(MieleCloudBindingConstants.THING_TYPE_COFFEE_SYSTEM, DeviceType.COFFEE_SYSTEM,
"000124431235", Optional.of("000124431234"), Optional.of("Coffee Machine"),
Optional.of("CM-1234"), "000124431234", "Coffee Machine CM-1234", "000124431235"),
Arguments.of(MieleCloudBindingConstants.THING_TYPE_HOOD, DeviceType.HOOD, "000124430018",
Optional.empty(), Optional.of("Ventilation Hood"), Optional.of("DA-6996"), "000124430018",
"Ventilation Hood DA-6996", "000124430018"),
Arguments.of(MieleCloudBindingConstants.THING_TYPE_HOOD, DeviceType.HOOD, "000124430018",
Optional.empty(), Optional.empty(), Optional.of("DA-6996"), "000124430018", "DA-6996",
"000124430018"),
Arguments.of(MieleCloudBindingConstants.THING_TYPE_HOOD, DeviceType.HOOD, "000124430018",
Optional.empty(), Optional.of("Ventilation Hood"), Optional.empty(), "000124430018",
"Ventilation Hood", "000124430018"),
Arguments.of(MieleCloudBindingConstants.THING_TYPE_HOOD, DeviceType.HOOD, "000124430018",
Optional.empty(), Optional.empty(), Optional.empty(), "000124430018", "Unknown",
"000124430018"));
}
@ParameterizedTest
@MethodSource("extractedPropertiesContainSerialNumberAndModelIdParameterSource")
void extractedPropertiesContainSerialNumberAndModelId(ThingTypeUID thingTypeUid, DeviceType deviceType,
String deviceIdentifier, Optional<String> fabNumber, Optional<String> type, Optional<String> techType,
String expectedSerialNumber, String expectedModelId, String expectedDeviceIdentifier) {
// given:
var deviceState = mock(DeviceState.class);
when(deviceState.getRawType()).thenReturn(deviceType);
when(deviceState.getDeviceIdentifier()).thenReturn(deviceIdentifier);
when(deviceState.getFabNumber()).thenReturn(fabNumber);
when(deviceState.getType()).thenReturn(type);
when(deviceState.getTechType()).thenReturn(techType);
// when:
var properties = ThingInformationExtractor.extractProperties(thingTypeUid, deviceState);
// then:
assertEquals(3, properties.size());
assertEquals(expectedSerialNumber, properties.get(Thing.PROPERTY_SERIAL_NUMBER));
assertEquals(expectedModelId, properties.get(Thing.PROPERTY_MODEL_ID));
assertEquals(expectedDeviceIdentifier,
properties.get(MieleCloudBindingConstants.CONFIG_PARAM_DEVICE_IDENTIFIER));
}
@ParameterizedTest
@CsvSource({ "2,2", "4,4" })
void propertiesForHobContainPlateCount(int plateCount, String expectedPlateCountPropertyValue) {
// given:
var deviceState = mock(DeviceState.class);
when(deviceState.getRawType()).thenReturn(DeviceType.HOB_INDUCTION);
when(deviceState.getDeviceIdentifier()).thenReturn("000124430019");
when(deviceState.getFabNumber()).thenReturn(Optional.of("000124430019"));
when(deviceState.getType()).thenReturn(Optional.of("Induction Hob"));
when(deviceState.getTechType()).thenReturn(Optional.of("IH-7890"));
when(deviceState.getPlateStepCount()).thenReturn(Optional.of(plateCount));
// when:
var properties = ThingInformationExtractor.extractProperties(MieleCloudBindingConstants.THING_TYPE_HOB,
deviceState);
// then:
assertEquals(4, properties.size());
assertEquals("000124430019", properties.get(Thing.PROPERTY_SERIAL_NUMBER));
assertEquals("Induction Hob IH-7890", properties.get(Thing.PROPERTY_MODEL_ID));
assertEquals("000124430019", properties.get(MieleCloudBindingConstants.CONFIG_PARAM_DEVICE_IDENTIFIER));
assertEquals(expectedPlateCountPropertyValue, properties.get(MieleCloudBindingConstants.PROPERTY_PLATE_COUNT));
}
}

View File

@@ -0,0 +1,72 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.util;
import static org.junit.jupiter.api.Assertions.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
/**
* @author Björn Lange - Initial Contribution
*/
@NonNullByDefault
public class LocaleValidatorTest {
@Test
public void enIsAValidLanguage() {
// given:
String language = "en";
// when:
boolean valid = LocaleValidator.isValidLanguage(language);
// then:
assertTrue(valid);
}
@Test
public void deIsAValidLanguage() {
// given:
String language = "de";
// when:
boolean valid = LocaleValidator.isValidLanguage(language);
// then:
assertTrue(valid);
}
@Test
public void aFullLocaleIsNotAValidLanguage() {
// given:
String language = "en_us";
// when:
boolean valid = LocaleValidator.isValidLanguage(language);
// then:
assertFalse(valid);
}
@Test
public void textIsNotAValidLanguage() {
// given:
String language = "Hello World!";
// when:
boolean valid = LocaleValidator.isValidLanguage(language);
// then:
assertFalse(valid);
}
}

View File

@@ -0,0 +1,94 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.util;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.*;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.Request;
import org.openhab.binding.mielecloud.internal.webservice.api.json.Device;
import org.openhab.binding.mielecloud.internal.webservice.api.json.DeviceIdentLabel;
import org.openhab.binding.mielecloud.internal.webservice.api.json.Ident;
/**
* Utility class for creating common mocks.
*
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public final class MockUtil {
private MockUtil() {
}
public static Device mockDevice(String fabNumber) {
DeviceIdentLabel deviceIdentLabel = mock(DeviceIdentLabel.class);
when(deviceIdentLabel.getFabNumber()).thenReturn(Optional.of(fabNumber));
Ident ident = mock(Ident.class);
when(ident.getDeviceIdentLabel()).thenReturn(Optional.of(deviceIdentLabel));
Device device = mock(Device.class);
when(device.getIdent()).thenReturn(Optional.of(ident));
return device;
}
public static <T> T requireNonNull(@Nullable T obj) {
if (obj == null) {
throw new IllegalArgumentException("Object must not be null");
}
return obj;
}
/**
* Creates a mock for {@link HttpClient} circumventing the problem that {@link HttpClient#start()} is {@code final}
* and {@link HttpClient#doStart()} {@code protected} and unaccessible when mocking with Mockito.
*/
public static HttpClient mockHttpClient() {
return new HttpClient() {
@Override
protected void doStart() throws Exception {
}
};
}
/**
* Creates a mock for {@link HttpClient} circumventing the problem that {@link HttpClient#start()} is {@code final}
* and {@link HttpClient#doStart()} {@code protected} and unaccessible when mocking with Mockito.
*
* @param newRequestUri {@code uri} parameter of {@link HttpClient#newRequest(String)} to mock.
* @param newRequestReturnValue Return value of {@link HttpClient#newRequest(String)} to mock.
*/
public static HttpClient mockHttpClient(String newRequestUri, Request newRequestReturnValue) {
return new HttpClient() {
@Override
protected void doStart() throws Exception {
}
@Override
public Request newRequest(@Nullable String uri) {
if (newRequestUri.equals(uri)) {
return newRequestReturnValue;
} else {
fail();
throw new IllegalStateException();
}
}
};
}
}

View File

@@ -0,0 +1,142 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.util;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Utility class for reflection operations such as accessing private fields or methods.
*
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public final class ReflectionUtil {
private ReflectionUtil() {
}
/**
* Gets a private attribute.
*
* @param object The object to get the attribute from.
* @param fieldName The name of the field to get.
* @return The obtained value.
* @throws SecurityException if the operation is not allowed.
* @throws NoSuchFieldException if no field with the given name exists.
* @throws IllegalAccessException if the field is enforcing Java language access control and is inaccessible.
* @throws IllegalArgumentException if one of the passed parameters is invalid.
*/
@SuppressWarnings("unchecked")
public static <T> T getPrivate(Object object, String fieldName)
throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
Field field = getFieldFromClassHierarchy(object.getClass(), fieldName);
field.setAccessible(true);
return (T) field.get(object);
}
private static Field getFieldFromClassHierarchy(Class<?> clazz, String fieldName)
throws NoSuchFieldException, SecurityException {
Class<?> iteratedClass = clazz;
do {
try {
return iteratedClass.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
}
iteratedClass = iteratedClass.getSuperclass();
} while (iteratedClass != null);
throw new NoSuchFieldException();
}
/**
* Sets a private attribute.
*
* @param object The object to set the attribute on.
* @param fieldName The name of the field to set.
* @param value The value to set.
* @throws SecurityException if the operation is not allowed.
* @throws NoSuchFieldException if no field with the given name exists.
* @throws IllegalAccessException if the field is enforcing Java language access control and is inaccessible.
* @throws IllegalArgumentException if one of the passed parameters is invalid.
*/
public static void setPrivate(Object object, String fieldName, Object value)
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
}
/**
* Invokes a private method on an object.
*
* @param object The object to invoke the method on.
* @param methodName The name of the method to invoke.
* @param parameters The parameters of the method invocation.
* @return The method call's return value.
* @throws NoSuchMethodException if no method with the given parameters or name exists.
* @throws SecurityException if the operation is not allowed.
* @throws IllegalAccessException if the method is enforcing Java language access control and is inaccessible.
* @throws IllegalArgumentException if one of the passed parameters is invalid.
* @throws InvocationTargetException if the invoked method throws an exception.
*/
public static <T> T invokePrivate(Object object, String methodName, Object... parameters)
throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException {
Class<?>[] parameterTypes = new Class[parameters.length];
for (int i = 0; i < parameters.length; i++) {
parameterTypes[i] = parameters[i].getClass();
}
return invokePrivate(object, methodName, parameterTypes, parameters);
}
/**
* Invokes a private method on an object.
*
* @param object The object to invoke the method on.
* @param methodName The name of the method to invoke.
* @param parameterTypes The types of the parameters.
* @param parameters The parameters of the method invocation.
* @return The method call's return value.
* @throws NoSuchMethodException if no method with the given parameters or name exists.
* @throws SecurityException if the operation is not allowed.
* @throws IllegalAccessException if the method is enforcing Java language access control and is inaccessible.
* @throws IllegalArgumentException if one of the passed parameters is invalid.
* @throws InvocationTargetException if the invoked method throws an exception.
*/
@SuppressWarnings("unchecked")
public static <T> T invokePrivate(Object object, String methodName, Class<?>[] parameterTypes, Object... parameters)
throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException {
Method method = getMethodFromClassHierarchy(object.getClass(), methodName, parameterTypes);
method.setAccessible(true);
try {
return (T) method.invoke(object, parameters);
} catch (InvocationTargetException e) {
throw new IllegalStateException(e.getCause());
}
}
private static Method getMethodFromClassHierarchy(Class<?> clazz, String methodName, Class<?>[] parameterTypes)
throws NoSuchMethodException {
Class<?> iteratedClass = clazz;
do {
try {
return iteratedClass.getDeclaredMethod(methodName, parameterTypes);
} catch (NoSuchMethodException e) {
}
iteratedClass = iteratedClass.getSuperclass();
} while (iteratedClass != null);
throw new NoSuchMethodException();
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Utility class for handling test resources.
*
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public final class ResourceUtil {
private ResourceUtil() {
}
/**
* Gets the contents of a resource file as {@link String}.
*
* @param resourceName The resource name (path inside the resources source folder).
* @return The file contents.
* @throws IOException if reading the resource fails or it cannot be found.
*/
public static String getResourceAsString(String resourceName) throws IOException {
InputStream inputStream = ResourceUtil.class.getResourceAsStream(resourceName);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
return reader.lines().collect(Collectors.joining("\n"));
}
}
}

View File

@@ -0,0 +1,178 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import java.util.Optional;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.openhab.binding.mielecloud.internal.util.MockUtil;
import org.openhab.binding.mielecloud.internal.webservice.api.DeviceState;
import org.openhab.binding.mielecloud.internal.webservice.api.json.StateType;
import org.openhab.binding.mielecloud.internal.webservice.exception.AuthorizationFailedException;
import org.openhab.binding.mielecloud.internal.webservice.exception.MieleWebserviceException;
import org.openhab.binding.mielecloud.internal.webservice.exception.TooManyRequestsException;
/**
* @author Björn Lange - Initial Contribution
*/
@NonNullByDefault
public class ActionStateFetcherTest {
private ScheduledExecutorService mockImmediatelyExecutingExecutorService() {
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
when(scheduler.submit(ArgumentMatchers.<Runnable> any()))
.thenAnswer(new Answer<@Nullable ScheduledFuture<?>>() {
@Override
@Nullable
public ScheduledFuture<?> answer(@Nullable InvocationOnMock invocation) throws Throwable {
((Runnable) MockUtil.requireNonNull(invocation).getArgument(0)).run();
return null;
}
});
return scheduler;
}
@Test
public void testFetchActionsIsInvokedWhenInitialDeviceStateIsSet() {
// given:
ScheduledExecutorService scheduler = mockImmediatelyExecutingExecutorService();
MieleWebservice webservice = mock(MieleWebservice.class);
DeviceState deviceState = mock(DeviceState.class);
DeviceState newDeviceState = mock(DeviceState.class);
ActionStateFetcher actionsfetcher = new ActionStateFetcher(() -> webservice, scheduler);
when(deviceState.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(newDeviceState.getStateType()).thenReturn(Optional.of(StateType.END_PROGRAMMED));
// when:
actionsfetcher.onDeviceStateUpdated(deviceState);
// then:
verify(webservice).fetchActions(any());
}
@Test
public void testFetchActionsIsInvokedOnStateTransition() {
// given:
ScheduledExecutorService scheduler = mockImmediatelyExecutingExecutorService();
MieleWebservice webservice = mock(MieleWebservice.class);
DeviceState deviceState = mock(DeviceState.class);
DeviceState newDeviceState = mock(DeviceState.class);
ActionStateFetcher actionsfetcher = new ActionStateFetcher(() -> webservice, scheduler);
when(deviceState.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(newDeviceState.getStateType()).thenReturn(Optional.of(StateType.END_PROGRAMMED));
actionsfetcher.onDeviceStateUpdated(deviceState);
// when:
actionsfetcher.onDeviceStateUpdated(newDeviceState);
// then:
verify(webservice, times(2)).fetchActions(any());
}
@Test
public void testFetchActionsIsNotInvokedWhenNoStateTransitionOccurrs() {
// given:
ScheduledExecutorService scheduler = mockImmediatelyExecutingExecutorService();
MieleWebservice webservice = mock(MieleWebservice.class);
DeviceState deviceState = mock(DeviceState.class);
DeviceState newDeviceState = mock(DeviceState.class);
ActionStateFetcher actionsfetcher = new ActionStateFetcher(() -> webservice, scheduler);
when(deviceState.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(newDeviceState.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
actionsfetcher.onDeviceStateUpdated(deviceState);
// when:
actionsfetcher.onDeviceStateUpdated(newDeviceState);
// then:
verify(webservice, times(1)).fetchActions(any());
}
@Test
public void whenFetchActionsFailsWithAMieleWebserviceExceptionThenNoExceptionIsThrown() {
// given:
ScheduledExecutorService scheduler = mockImmediatelyExecutingExecutorService();
MieleWebservice webservice = mock(MieleWebservice.class);
doThrow(new MieleWebserviceException("It went wrong", ConnectionError.REQUEST_EXECUTION_FAILED))
.when(webservice).fetchActions(any());
DeviceState deviceState = mock(DeviceState.class);
when(deviceState.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
ActionStateFetcher actionsfetcher = new ActionStateFetcher(() -> webservice, scheduler);
// when:
actionsfetcher.onDeviceStateUpdated(deviceState);
// then:
verify(webservice, times(1)).fetchActions(any());
}
@Test
public void whenFetchActionsFailsWithAnAuthorizationFailedExceptionThenNoExceptionIsThrown() {
// given:
ScheduledExecutorService scheduler = mockImmediatelyExecutingExecutorService();
MieleWebservice webservice = mock(MieleWebservice.class);
doThrow(new AuthorizationFailedException("Authorization failed")).when(webservice).fetchActions(any());
DeviceState deviceState = mock(DeviceState.class);
when(deviceState.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
ActionStateFetcher actionsfetcher = new ActionStateFetcher(() -> webservice, scheduler);
// when:
actionsfetcher.onDeviceStateUpdated(deviceState);
// then:
verify(webservice, times(1)).fetchActions(any());
}
@Test
public void whenFetchActionsFailsWithATooManyRequestsExceptionThenNoExceptionIsThrown() {
// given:
ScheduledExecutorService scheduler = mockImmediatelyExecutingExecutorService();
MieleWebservice webservice = mock(MieleWebservice.class);
doThrow(new TooManyRequestsException("Too many requests", null)).when(webservice).fetchActions(any());
DeviceState deviceState = mock(DeviceState.class);
when(deviceState.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
ActionStateFetcher actionsfetcher = new ActionStateFetcher(() -> webservice, scheduler);
// when:
actionsfetcher.onDeviceStateUpdated(deviceState);
// then:
verify(webservice, times(1)).fetchActions(any());
}
}

View File

@@ -0,0 +1,742 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.openhab.binding.mielecloud.internal.util.ReflectionUtil.getPrivate;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpFields;
import org.junit.jupiter.api.Test;
import org.openhab.binding.mielecloud.internal.MieleCloudBindingTestConstants;
import org.openhab.binding.mielecloud.internal.auth.OAuthTokenRefresher;
import org.openhab.binding.mielecloud.internal.util.MockUtil;
import org.openhab.binding.mielecloud.internal.webservice.api.json.ProcessAction;
import org.openhab.binding.mielecloud.internal.webservice.exception.AuthorizationFailedException;
import org.openhab.binding.mielecloud.internal.webservice.exception.MieleWebserviceException;
import org.openhab.binding.mielecloud.internal.webservice.exception.MieleWebserviceTransientException;
import org.openhab.binding.mielecloud.internal.webservice.exception.TooManyRequestsException;
import org.openhab.binding.mielecloud.internal.webservice.language.LanguageProvider;
import org.openhab.binding.mielecloud.internal.webservice.request.RequestFactory;
import org.openhab.binding.mielecloud.internal.webservice.retry.AuthorizationFailedRetryStrategy;
import org.openhab.binding.mielecloud.internal.webservice.retry.NTimesRetryStrategy;
import org.openhab.binding.mielecloud.internal.webservice.retry.RetryStrategy;
import org.openhab.binding.mielecloud.internal.webservice.retry.RetryStrategyCombiner;
import org.openhab.core.io.net.http.HttpClientFactory;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class DefaultMieleWebserviceTest {
private static final String MESSAGE_INTERNAL_SERVER_ERROR = "{\"message\": \"Internal Server Error\"}";
private static final String MESSAGE_SERVICE_UNAVAILABLE = "{\"message\": \"unavailable\"}";
private static final String MESSAGE_INVALID_JSON = "{\"abc123: \"äfgh\"}";
private static final String DEVICE_IDENTIFIER = "000124430016";
private static final String SERVER_ADDRESS = "https://api.mcs3.miele.com";
private static final String ENDPOINT_DEVICES = SERVER_ADDRESS + "/v1/devices/";
private static final String ENDPOINT_ACTIONS = ENDPOINT_DEVICES + DEVICE_IDENTIFIER + "/actions";
private static final String ENDPOINT_LOGOUT = SERVER_ADDRESS + "/thirdparty/logout";
private static final String ACCESS_TOKEN = "DE_0123456789abcdef0123456789abcdef";
private final RetryStrategy retryStrategy = new UncatchedRetryStrategy();
private final Request request = mock(Request.class);
@Test
public void testDefaultRetryStrategyIsCombinationOfOneTimeRetryStrategyAndAuthorizationFailedStrategy()
throws Exception {
// given:
HttpClientFactory httpClientFactory = mock(HttpClientFactory.class);
when(httpClientFactory.createHttpClient(anyString())).thenReturn(MockUtil.mockHttpClient());
LanguageProvider languageProvider = mock(LanguageProvider.class);
OAuthTokenRefresher tokenRefresher = mock(OAuthTokenRefresher.class);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
// when:
DefaultMieleWebservice webservice = new DefaultMieleWebservice(MieleWebserviceConfiguration.builder()
.withHttpClientFactory(httpClientFactory).withLanguageProvider(languageProvider)
.withTokenRefresher(tokenRefresher).withServiceHandle(MieleCloudBindingTestConstants.SERVICE_HANDLE)
.withScheduler(scheduler).build());
// then:
RetryStrategy retryStrategy = getPrivate(webservice, "retryStrategy");
assertTrue(retryStrategy instanceof RetryStrategyCombiner);
RetryStrategy first = getPrivate(retryStrategy, "first");
assertTrue(first instanceof NTimesRetryStrategy);
int numberOfRetries = getPrivate(first, "numberOfRetries");
assertEquals(1, numberOfRetries);
RetryStrategy second = getPrivate(retryStrategy, "second");
assertTrue(second instanceof AuthorizationFailedRetryStrategy);
OAuthTokenRefresher internalTokenRefresher = getPrivate(second, "tokenRefresher");
assertEquals(tokenRefresher, internalTokenRefresher);
}
private ContentResponse createContentResponseMock(int errorCode, String content) {
ContentResponse response = mock(ContentResponse.class);
when(response.getStatus()).thenReturn(errorCode);
when(response.getContentAsString()).thenReturn(content);
return response;
}
private void performFetchActions() throws Exception {
RequestFactory requestFactory = mock(RequestFactory.class);
when(requestFactory.createGetRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN)).thenReturn(request);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, retryStrategy,
new DeviceStateDispatcher(), scheduler)) {
webservice.setAccessToken(ACCESS_TOKEN);
webservice.fetchActions(DEVICE_IDENTIFIER);
}
}
private void performFetchActionsExpectingFailure(ConnectionError expectedError) throws Exception {
try {
performFetchActions();
} catch (MieleWebserviceException e) {
assertEquals(expectedError, e.getConnectionError());
throw e;
} catch (MieleWebserviceTransientException e) {
assertEquals(expectedError, e.getConnectionError());
throw e;
}
}
@Test
public void testTimeoutExceptionWhilePerformingFetchActionsRequest() throws Exception {
// given:
when(request.send()).thenThrow(TimeoutException.class);
// when:
assertThrows(MieleWebserviceTransientException.class, () -> {
performFetchActionsExpectingFailure(ConnectionError.TIMEOUT);
});
}
@Test
public void test500InternalServerErrorWhilePerformingFetchActionsRequest() throws Exception {
// given:
ContentResponse contentResponse = createContentResponseMock(500, MESSAGE_INTERNAL_SERVER_ERROR);
when(request.send()).thenReturn(contentResponse);
// when:
assertThrows(MieleWebserviceTransientException.class, () -> {
performFetchActionsExpectingFailure(ConnectionError.SERVER_ERROR);
});
}
@Test
public void test503ServiceUnavailableWhilePerformingFetchActionsRequest() throws Exception {
// given:
ContentResponse contentResponse = createContentResponseMock(503, MESSAGE_SERVICE_UNAVAILABLE);
when(request.send()).thenReturn(contentResponse);
// when:
assertThrows(MieleWebserviceTransientException.class, () -> {
performFetchActionsExpectingFailure(ConnectionError.SERVICE_UNAVAILABLE);
});
}
@Test
public void testInvalidJsonWhilePerformingFetchActionsRequest() throws Exception {
// given:
ContentResponse contentResponse = createContentResponseMock(200, MESSAGE_INVALID_JSON);
when(request.send()).thenReturn(contentResponse);
// when:
assertThrows(MieleWebserviceTransientException.class, () -> {
performFetchActionsExpectingFailure(ConnectionError.RESPONSE_MALFORMED);
});
}
@Test
public void testInterruptedExceptionWhilePerformingFetchActionsRequest() throws Exception {
// given:
when(request.send()).thenThrow(InterruptedException.class);
// when:
assertThrows(MieleWebserviceException.class, () -> {
performFetchActionsExpectingFailure(ConnectionError.REQUEST_INTERRUPTED);
});
}
@Test
public void testExecutionExceptionWhilePerformingFetchActionsRequest() throws Exception {
// given:
when(request.send()).thenThrow(ExecutionException.class);
// when:
assertThrows(MieleWebserviceException.class, () -> {
performFetchActionsExpectingFailure(ConnectionError.REQUEST_EXECUTION_FAILED);
});
}
@Test
public void test400BadRequestWhilePerformingFetchActionsRequest() throws Exception {
// given:
ContentResponse response = createContentResponseMock(400, "{\"message\": \"grant_type is invalid\"}");
when(request.send()).thenReturn(response);
// when:
assertThrows(MieleWebserviceException.class, () -> {
performFetchActionsExpectingFailure(ConnectionError.OTHER_HTTP_ERROR);
});
}
@Test
public void test401UnauthorizedWhilePerformingFetchActionsRequest() throws Exception {
// given:
ContentResponse response = createContentResponseMock(401, "{\"message\": \"Unauthorized\"}");
when(request.send()).thenReturn(response);
// when:
assertThrows(AuthorizationFailedException.class, () -> {
performFetchActions();
});
}
@Test
public void test404NotFoundWhilePerformingFetchActionsRequest() throws Exception {
// given:
ContentResponse response = createContentResponseMock(404, "{\"message\": \"Not found\"}");
when(request.send()).thenReturn(response);
// when:
assertThrows(MieleWebserviceException.class, () -> {
performFetchActionsExpectingFailure(ConnectionError.OTHER_HTTP_ERROR);
});
}
@Test
public void test405MethodNotAllowedWhilePerformingFetchActionsRequest() throws Exception {
// given:
ContentResponse response = createContentResponseMock(405, "{\"message\": \"HTTP 405 Method Not Allowed\"}");
when(request.send()).thenReturn(response);
// when:
assertThrows(MieleWebserviceException.class, () -> {
performFetchActionsExpectingFailure(ConnectionError.OTHER_HTTP_ERROR);
});
}
@Test
public void test429TooManyRequestsWhilePerformingFetchActionsRequest() throws Exception {
// given:
HttpFields headerFields = mock(HttpFields.class);
when(headerFields.containsKey(anyString())).thenReturn(false);
ContentResponse response = createContentResponseMock(429, "{\"message\": \"Too Many Requests\"}");
when(response.getHeaders()).thenReturn(headerFields);
when(request.send()).thenReturn(response);
// when:
assertThrows(TooManyRequestsException.class, () -> {
performFetchActions();
});
}
@Test
public void test502BadGatewayWhilePerforminggFetchActionsRequest() throws Exception {
// given:
ContentResponse response = createContentResponseMock(502, "{\"message\": \"Bad Gateway\"}");
when(request.send()).thenReturn(response);
// when:
assertThrows(MieleWebserviceException.class, () -> {
performFetchActionsExpectingFailure(ConnectionError.OTHER_HTTP_ERROR);
});
}
@Test
public void testMalformatedBodyWhilePerforminggFetchActionsRequest() throws Exception {
// given:
ContentResponse response = createContentResponseMock(502, "{\"message \"Bad Gateway\"}");
when(request.send()).thenReturn(response);
// when:
assertThrows(MieleWebserviceException.class, () -> {
performFetchActionsExpectingFailure(ConnectionError.OTHER_HTTP_ERROR);
});
}
private void fillRequestMockWithDefaultContent() throws InterruptedException, TimeoutException, ExecutionException {
ContentResponse response = createContentResponseMock(200,
"{\"000124430016\":{\"ident\": {\"deviceName\": \"MyFancyHood\", \"deviceIdentLabel\": {\"fabNumber\": \"000124430016\"}}}}");
when(request.send()).thenReturn(response);
}
@Test
public void testAddDeviceStateListenerIsDelegatedToDeviceStateDispatcher() throws Exception {
// given:
RequestFactory requestFactory = mock(RequestFactory.class);
DeviceStateDispatcher dispatcher = mock(DeviceStateDispatcher.class);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
dispatcher, scheduler)) {
DeviceStateListener listener = mock(DeviceStateListener.class);
// when:
webservice.addDeviceStateListener(listener);
// then:
verify(dispatcher).addListener(listener);
verifyNoMoreInteractions(dispatcher);
}
}
@Test
public void testFetchActionsDelegatesDeviceStateListenerDispatchingToDeviceStateDispatcher() throws Exception {
// given:
fillRequestMockWithDefaultContent();
RequestFactory requestFactory = mock(RequestFactory.class);
when(requestFactory.createGetRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN)).thenReturn(request);
DeviceStateDispatcher dispatcher = mock(DeviceStateDispatcher.class);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, retryStrategy, dispatcher,
scheduler)) {
webservice.setAccessToken(ACCESS_TOKEN);
// when:
webservice.fetchActions(DEVICE_IDENTIFIER);
// then:
verify(dispatcher).dispatchActionStateUpdates(any(), any());
verifyNoMoreInteractions(dispatcher);
}
}
@Test
public void testFetchActionsThrowsMieleWebserviceTransientExceptionWhenRequestContentIsMalformatted()
throws Exception {
// given:
ContentResponse response = createContentResponseMock(200, "{\"}");
when(request.send()).thenReturn(response);
RequestFactory requestFactory = mock(RequestFactory.class);
when(requestFactory.createGetRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN)).thenReturn(request);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, retryStrategy,
new DeviceStateDispatcher(), scheduler)) {
webservice.setAccessToken(ACCESS_TOKEN);
// when:
assertThrows(MieleWebserviceTransientException.class, () -> {
webservice.fetchActions(DEVICE_IDENTIFIER);
});
}
}
@Test
public void testPutProcessActionSendsRequestWithCorrectJsonContent() throws Exception {
// given:
ContentResponse response = createContentResponseMock(204, "");
when(request.send()).thenReturn(response);
RequestFactory requestFactory = mock(RequestFactory.class);
when(requestFactory.createPutRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN, "{\"processAction\":1}"))
.thenReturn(request);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
new DeviceStateDispatcher(), scheduler)) {
webservice.setAccessToken(ACCESS_TOKEN);
// when:
webservice.putProcessAction(DEVICE_IDENTIFIER, ProcessAction.START);
// then:
verify(request).send();
verifyNoMoreInteractions(request);
}
}
@Test
public void testPutProcessActionThrowsIllegalArgumentExceptionWhenProcessActionIsUnknown() throws Exception {
// given:
RequestFactory requestFactory = mock(RequestFactory.class);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, retryStrategy,
new DeviceStateDispatcher(), scheduler)) {
// when:
assertThrows(IllegalArgumentException.class, () -> {
webservice.putProcessAction(DEVICE_IDENTIFIER, ProcessAction.UNKNOWN);
});
}
}
@Test
public void testPutProcessActionThrowsTooManyRequestsExceptionWhenHttpResponseCodeIs429() throws Exception {
// given:
HttpFields responseHeaders = mock(HttpFields.class);
when(responseHeaders.containsKey(anyString())).thenReturn(false);
ContentResponse response = createContentResponseMock(429, "{\"message\":\"Too many requests\"}");
when(response.getHeaders()).thenReturn(responseHeaders);
when(request.send()).thenReturn(response);
RequestFactory requestFactory = mock(RequestFactory.class);
when(requestFactory.createPutRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN, "{\"processAction\":1}"))
.thenReturn(request);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
new DeviceStateDispatcher(), scheduler)) {
webservice.setAccessToken(ACCESS_TOKEN);
// when:
assertThrows(TooManyRequestsException.class, () -> {
webservice.putProcessAction(DEVICE_IDENTIFIER, ProcessAction.START);
});
}
}
@Test
public void testPutLightSendsRequestWithCorrectJsonContentWhenTurningTheLightOn() throws Exception {
// given:
ContentResponse response = createContentResponseMock(204, "");
when(request.send()).thenReturn(response);
RequestFactory requestFactory = mock(RequestFactory.class);
when(requestFactory.createPutRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN, "{\"light\":1}")).thenReturn(request);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
new DeviceStateDispatcher(), scheduler)) {
webservice.setAccessToken(ACCESS_TOKEN);
// when:
webservice.putLight(DEVICE_IDENTIFIER, true);
// then:
verify(request).send();
verifyNoMoreInteractions(request);
}
}
@Test
public void testPutLightSendsRequestWithCorrectJsonContentWhenTurningTheLightOff() throws Exception {
// given:
ContentResponse response = createContentResponseMock(204, "");
when(request.send()).thenReturn(response);
RequestFactory requestFactory = mock(RequestFactory.class);
when(requestFactory.createPutRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN, "{\"light\":2}")).thenReturn(request);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
new DeviceStateDispatcher(), scheduler)) {
webservice.setAccessToken(ACCESS_TOKEN);
// when:
webservice.putLight(DEVICE_IDENTIFIER, false);
// then:
verify(request).send();
verifyNoMoreInteractions(request);
}
}
@Test
public void testPutLightThrowsTooManyRequestsExceptionWhenHttpResponseCodeIs429() throws Exception {
// given:
HttpFields responseHeaders = mock(HttpFields.class);
when(responseHeaders.containsKey(anyString())).thenReturn(false);
ContentResponse response = createContentResponseMock(429, "{\"message\":\"Too many requests\"}");
when(response.getHeaders()).thenReturn(responseHeaders);
when(request.send()).thenReturn(response);
RequestFactory requestFactory = mock(RequestFactory.class);
when(requestFactory.createPutRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN, "{\"light\":2}")).thenReturn(request);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
new DeviceStateDispatcher(), scheduler)) {
webservice.setAccessToken(ACCESS_TOKEN);
// when:
assertThrows(TooManyRequestsException.class, () -> {
webservice.putLight(DEVICE_IDENTIFIER, false);
});
}
}
@Test
public void testLogoutInvalidatesAccessTokenOnSuccess() throws Exception {
// given:
ContentResponse response = createContentResponseMock(204, "");
when(request.send()).thenReturn(response);
RequestFactory requestFactory = mock(RequestFactory.class);
when(requestFactory.createPostRequest(ENDPOINT_LOGOUT, ACCESS_TOKEN)).thenReturn(request);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, retryStrategy,
new DeviceStateDispatcher(), scheduler)) {
webservice.setAccessToken(ACCESS_TOKEN);
// when:
webservice.logout();
// then:
assertFalse(webservice.hasAccessToken());
verify(request).send();
verifyNoMoreInteractions(request);
}
}
@Test
public void testLogoutThrowsMieleWebserviceExceptionWhenMieleWebserviceTransientExceptionIsThrownInternally()
throws Exception {
// given:
when(request.send()).thenThrow(TimeoutException.class);
RequestFactory requestFactory = mock(RequestFactory.class);
when(requestFactory.createPostRequest(ENDPOINT_LOGOUT, ACCESS_TOKEN)).thenReturn(request);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, retryStrategy,
new DeviceStateDispatcher(), scheduler)) {
webservice.setAccessToken(ACCESS_TOKEN);
// when:
assertThrows(MieleWebserviceException.class, () -> {
webservice.logout();
});
}
}
@Test
public void testLogoutInvalidatesAccessTokenWhenOperationFails() throws Exception {
// given:
when(request.send()).thenThrow(TimeoutException.class);
RequestFactory requestFactory = mock(RequestFactory.class);
when(requestFactory.createPostRequest(ENDPOINT_LOGOUT, ACCESS_TOKEN)).thenReturn(request);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, retryStrategy,
new DeviceStateDispatcher(), scheduler)) {
webservice.setAccessToken(ACCESS_TOKEN);
// when:
try {
webservice.logout();
} catch (MieleWebserviceException e) {
}
// then:
assertFalse(webservice.hasAccessToken());
}
}
@Test
public void testRemoveDeviceStateListenerIsDelegatedToDeviceStateDispatcher() throws Exception {
// given:
RequestFactory requestFactory = mock(RequestFactory.class);
DeviceStateDispatcher dispatcher = mock(DeviceStateDispatcher.class);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
dispatcher, scheduler)) {
DeviceStateListener listener = mock(DeviceStateListener.class);
webservice.addDeviceStateListener(listener);
// when:
webservice.removeDeviceStateListener(listener);
// then:
verify(dispatcher).addListener(listener);
verify(dispatcher).removeListener(listener);
verifyNoMoreInteractions(dispatcher);
}
}
@Test
public void testPutPowerStateSendsRequestWithCorrectJsonContentWhenSwitchingTheDeviceOn() throws Exception {
// given:
ContentResponse response = createContentResponseMock(204, "");
when(request.send()).thenReturn(response);
RequestFactory requestFactory = mock(RequestFactory.class);
when(requestFactory.createPutRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN, "{\"powerOn\":true}")).thenReturn(request);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
new DeviceStateDispatcher(), scheduler)) {
webservice.setAccessToken(ACCESS_TOKEN);
// when:
webservice.putPowerState(DEVICE_IDENTIFIER, true);
// then:
verify(request).send();
verifyNoMoreInteractions(request);
}
}
@Test
public void testPutPowerStateSendsRequestWithCorrectJsonContentWhenDeviceIsSwitchedOff() throws Exception {
// given:
ContentResponse response = createContentResponseMock(204, "");
when(request.send()).thenReturn(response);
RequestFactory requestFactory = mock(RequestFactory.class);
when(requestFactory.createPutRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN, "{\"powerOff\":true}"))
.thenReturn(request);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
new DeviceStateDispatcher(), scheduler)) {
webservice.setAccessToken(ACCESS_TOKEN);
// when:
webservice.putPowerState(DEVICE_IDENTIFIER, false);
// then:
verify(request).send();
verifyNoMoreInteractions(request);
}
}
@Test
public void testPutPowerStateThrowsTooManyRequestsExceptionWhenHttpResponseCodeIs429() throws Exception {
// given:
HttpFields responseHeaders = mock(HttpFields.class);
when(responseHeaders.containsKey(anyString())).thenReturn(false);
ContentResponse response = createContentResponseMock(429, "{\"message\":\"Too many requests\"}");
when(response.getHeaders()).thenReturn(responseHeaders);
when(request.send()).thenReturn(response);
RequestFactory requestFactory = mock(RequestFactory.class);
when(requestFactory.createPutRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN, "{\"powerOff\":true}"))
.thenReturn(request);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
new DeviceStateDispatcher(), scheduler)) {
webservice.setAccessToken(ACCESS_TOKEN);
// when:
assertThrows(TooManyRequestsException.class, () -> {
webservice.putPowerState(DEVICE_IDENTIFIER, false);
});
}
}
@Test
public void testPutProgramResultsInARequestWithCorrectJson() throws Exception {
// given:
ContentResponse response = createContentResponseMock(204, "");
when(request.send()).thenReturn(response);
RequestFactory requestFactory = mock(RequestFactory.class);
when(requestFactory.createPutRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN, "{\"programId\":1}")).thenReturn(request);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
new DeviceStateDispatcher(), scheduler)) {
webservice.setAccessToken(ACCESS_TOKEN);
// when:
webservice.putProgram(DEVICE_IDENTIFIER, 1);
// then:
verify(request).send();
verifyNoMoreInteractions(request);
}
}
@Test
public void testDispatchDeviceStateIsDelegatedToDeviceStateDispatcher() throws Exception {
// given:
RequestFactory requestFactory = mock(RequestFactory.class);
DeviceStateDispatcher dispatcher = mock(DeviceStateDispatcher.class);
ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
dispatcher, scheduler)) {
// when:
webservice.dispatchDeviceState(DEVICE_IDENTIFIER);
// then:
verify(dispatcher).dispatchDeviceState(DEVICE_IDENTIFIER);
verifyNoMoreInteractions(dispatcher);
}
}
/**
* {@link RetryStrategy} for testing purposes. No exceptions will be catched.
*
* @author Roland Edelhoff - Initial contribution.
*/
private static class UncatchedRetryStrategy implements RetryStrategy {
@Override
public <@Nullable T> T performRetryableOperation(Supplier<T> operation,
Consumer<Exception> onTransientException) {
return operation.get();
}
@Override
public void performRetryableOperation(Runnable operation, Consumer<Exception> onTransientException) {
operation.run();
}
}
}

View File

@@ -0,0 +1,94 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;
import java.util.Arrays;
import java.util.HashSet;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.mielecloud.internal.webservice.api.json.Device;
import org.openhab.binding.mielecloud.internal.webservice.api.json.DeviceCollection;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class DeviceCacheTest {
private static final String FIRST_DEVICE_IDENTIFIER = "000124430016";
private static final String SECOND_DEVICE_IDENTIFIER = "000124430017";
private static final String THIRD_DEVICE_IDENTIFIER = "400124430017";
private final Device firstDevice = mock(Device.class);
private final Device secondDevice = mock(Device.class);
private final Device thirdDevice = mock(Device.class);
private final DeviceCache deviceCache = new DeviceCache();
@Test
public void testCacheIsEmptyAfterConstruction() {
// then:
assertEquals(0, deviceCache.getDeviceIds().size());
}
@Test
public void testReplaceAllDevicesClearsTheCacheAndPutsAllNewDevicesIntoTheCache() {
// given:
DeviceCollection deviceCollection = mock(DeviceCollection.class);
when(deviceCollection.getDeviceIdentifiers())
.thenReturn(new HashSet<>(Arrays.asList(FIRST_DEVICE_IDENTIFIER, SECOND_DEVICE_IDENTIFIER)));
when(deviceCollection.getDevice(FIRST_DEVICE_IDENTIFIER)).thenReturn(firstDevice);
when(deviceCollection.getDevice(SECOND_DEVICE_IDENTIFIER)).thenReturn(secondDevice);
// when:
deviceCache.replaceAllDevices(deviceCollection);
// then:
assertEquals(new HashSet<>(Arrays.asList(FIRST_DEVICE_IDENTIFIER, SECOND_DEVICE_IDENTIFIER)),
deviceCache.getDeviceIds());
assertEquals(firstDevice, deviceCache.getDevice(FIRST_DEVICE_IDENTIFIER).get());
assertEquals(secondDevice, deviceCache.getDevice(SECOND_DEVICE_IDENTIFIER).get());
}
@Test
public void testReplaceAllDevicesClearsTheCachePriorToCachingThePassedDevices() {
// given:
testReplaceAllDevicesClearsTheCacheAndPutsAllNewDevicesIntoTheCache();
DeviceCollection deviceCollection = mock(DeviceCollection.class);
when(deviceCollection.getDeviceIdentifiers()).thenReturn(new HashSet<>(Arrays.asList(THIRD_DEVICE_IDENTIFIER)));
when(deviceCollection.getDevice(THIRD_DEVICE_IDENTIFIER)).thenReturn(thirdDevice);
// when:
deviceCache.replaceAllDevices(deviceCollection);
// then:
assertEquals(new HashSet<>(Arrays.asList(THIRD_DEVICE_IDENTIFIER)), deviceCache.getDeviceIds());
assertEquals(thirdDevice, deviceCache.getDevice(THIRD_DEVICE_IDENTIFIER).get());
}
@Test
public void testClearClearsTheCachedDevices() {
// given:
testReplaceAllDevicesClearsTheCacheAndPutsAllNewDevicesIntoTheCache();
// when:
deviceCache.clear();
// then:
assertEquals(0, deviceCache.getDeviceIds().size());
}
}

View File

@@ -0,0 +1,257 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.openhab.binding.mielecloud.internal.util.MockUtil.mockDevice;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openhab.binding.mielecloud.internal.webservice.api.ActionsState;
import org.openhab.binding.mielecloud.internal.webservice.api.DeviceState;
import org.openhab.binding.mielecloud.internal.webservice.api.json.Actions;
import org.openhab.binding.mielecloud.internal.webservice.api.json.Device;
import org.openhab.binding.mielecloud.internal.webservice.api.json.DeviceCollection;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class DeviceStateDispatcherTest {
private static final String FIRST_DEVICE_IDENTIFIER = "000124430016";
private static final String SECOND_DEVICE_IDENTIFIER = "000124430017";
private static final String UNKNOWN_DEVICE_IDENTIFIER = "100124430016";
@Nullable
private Device firstDevice;
@Nullable
private Device secondDevice;
@Nullable
private DeviceCollection devices;
private Device getFirstDevice() {
assertNotNull(firstDevice);
return Objects.requireNonNull(firstDevice);
}
private Device getSecondDevice() {
assertNotNull(secondDevice);
return Objects.requireNonNull(secondDevice);
}
private DeviceCollection getDevices() {
assertNotNull(devices);
return Objects.requireNonNull(devices);
}
@BeforeEach
public void setUp() {
firstDevice = mockDevice(FIRST_DEVICE_IDENTIFIER);
secondDevice = mockDevice(SECOND_DEVICE_IDENTIFIER);
devices = mock(DeviceCollection.class);
when(getDevices().getDeviceIdentifiers())
.thenReturn(new HashSet<String>(Arrays.asList(FIRST_DEVICE_IDENTIFIER, SECOND_DEVICE_IDENTIFIER)));
when(getDevices().getDevice(FIRST_DEVICE_IDENTIFIER)).thenReturn(getFirstDevice());
when(getDevices().getDevice(SECOND_DEVICE_IDENTIFIER)).thenReturn(getSecondDevice());
}
@Test
public void testAddListenerDispatchesStateUpdatesToPassedListenerForCachedDevices()
throws InterruptedException, TimeoutException, ExecutionException {
// given:
DeviceStateListener listener = mock(DeviceStateListener.class);
DeviceStateDispatcher dispatcher = new DeviceStateDispatcher();
dispatcher.dispatchDeviceStateUpdates(getDevices());
// when:
dispatcher.addListener(listener);
// then:
verify(listener).onDeviceStateUpdated(new DeviceState(FIRST_DEVICE_IDENTIFIER, firstDevice));
verify(listener).onDeviceStateUpdated(new DeviceState(SECOND_DEVICE_IDENTIFIER, secondDevice));
verifyNoMoreInteractions(listener);
}
@Test
public void testDeviceStateUpdatesAreNotDispatchedToRemovedListeners() {
// given:
DeviceStateListener listener = mock(DeviceStateListener.class);
DeviceStateDispatcher dispatcher = new DeviceStateDispatcher();
dispatcher.addListener(listener);
// when:
dispatcher.removeListener(listener);
dispatcher.dispatchDeviceStateUpdates(getDevices());
// then:
verifyNoMoreInteractions(listener);
}
@Test
public void testClearCachePreventsDeviceStateUpdateDispatchingOnListenerRegistration() {
// given:
DeviceStateListener listener = mock(DeviceStateListener.class);
DeviceStateDispatcher dispatcher = new DeviceStateDispatcher();
dispatcher.dispatchDeviceStateUpdates(getDevices());
// when:
dispatcher.clearCache();
dispatcher.addListener(listener);
// then:
verifyNoMoreInteractions(listener);
}
@Test
public void testDeviceStateUpdatesAreDispatchedToSubscribedListeners() {
// given:
DeviceStateListener listener = mock(DeviceStateListener.class);
DeviceStateDispatcher dispatcher = new DeviceStateDispatcher();
dispatcher.addListener(listener);
// when:
dispatcher.dispatchDeviceStateUpdates(getDevices());
// then:
verify(listener).onDeviceStateUpdated(new DeviceState(FIRST_DEVICE_IDENTIFIER, firstDevice));
verify(listener).onDeviceStateUpdated(new DeviceState(SECOND_DEVICE_IDENTIFIER, secondDevice));
verifyNoMoreInteractions(listener);
}
@Test
public void testRemovalEventsAreDispatchedToSubscribedListeners()
throws InterruptedException, TimeoutException, ExecutionException {
// given:
DeviceStateListener listener = mock(DeviceStateListener.class);
Device deviceWithUnknownIdentifier = mockDevice(UNKNOWN_DEVICE_IDENTIFIER);
DeviceCollection devicesWithUnknownDevice = mock(DeviceCollection.class);
when(devicesWithUnknownDevice.getDeviceIdentifiers())
.thenReturn(new HashSet<String>(Arrays.asList(UNKNOWN_DEVICE_IDENTIFIER)));
when(devicesWithUnknownDevice.getDevice(UNKNOWN_DEVICE_IDENTIFIER)).thenReturn(deviceWithUnknownIdentifier);
DeviceStateDispatcher dispatcher = new DeviceStateDispatcher();
dispatcher.dispatchDeviceStateUpdates(devicesWithUnknownDevice);
dispatcher.clearCache();
dispatcher.addListener(listener);
// when:
dispatcher.dispatchDeviceStateUpdates(getDevices());
// then:
verify(listener).onDeviceRemoved(UNKNOWN_DEVICE_IDENTIFIER);
verify(listener, times(2)).onDeviceStateUpdated(any());
verifyNoMoreInteractions(listener);
}
@Test
public void testRemovalEventsAreDispatchedToSubscribedListenersMatchingAllDeviceIds()
throws InterruptedException, TimeoutException, ExecutionException {
// given:
DeviceStateListener listener = mock(DeviceStateListener.class);
Device deviceWithUnknownIdentifier = mockDevice(UNKNOWN_DEVICE_IDENTIFIER);
DeviceCollection devicesWithUnknownDevice = mock(DeviceCollection.class);
when(devicesWithUnknownDevice.getDeviceIdentifiers())
.thenReturn(new HashSet<String>(Arrays.asList(UNKNOWN_DEVICE_IDENTIFIER)));
when(devicesWithUnknownDevice.getDevice(UNKNOWN_DEVICE_IDENTIFIER)).thenReturn(deviceWithUnknownIdentifier);
DeviceCollection emptyDevices = mock(DeviceCollection.class);
when(emptyDevices.getDeviceIdentifiers()).thenReturn(new HashSet<String>());
DeviceStateDispatcher dispatcher = new DeviceStateDispatcher();
dispatcher.dispatchDeviceStateUpdates(devicesWithUnknownDevice);
dispatcher.clearCache();
dispatcher.addListener(listener);
// when:
dispatcher.dispatchDeviceStateUpdates(emptyDevices);
// then:
verify(listener).onDeviceRemoved(UNKNOWN_DEVICE_IDENTIFIER);
verifyNoMoreInteractions(listener);
}
@Test
public void testDeviceEventDispatchingForSubscribedListenersWithAnyDeviceIdFilter()
throws InterruptedException, TimeoutException, ExecutionException {
// given:
DeviceStateListener listener = mock(DeviceStateListener.class);
DeviceStateDispatcher dispatcher = new DeviceStateDispatcher();
dispatcher.addListener(listener);
// when:
dispatcher.dispatchDeviceStateUpdates(getDevices());
// then:
verify(listener).onDeviceStateUpdated(new DeviceState(FIRST_DEVICE_IDENTIFIER, firstDevice));
verify(listener).onDeviceStateUpdated(new DeviceState(SECOND_DEVICE_IDENTIFIER, secondDevice));
verifyNoMoreInteractions(listener);
}
@Test
public void testActionsEventDispatchingForSubscribedListeners()
throws InterruptedException, TimeoutException, ExecutionException {
// given:
DeviceStateListener listener = mock(DeviceStateListener.class);
Actions actions = mock(Actions.class);
DeviceStateDispatcher dispatcher = new DeviceStateDispatcher();
dispatcher.addListener(listener);
// when:
dispatcher.dispatchActionStateUpdates(FIRST_DEVICE_IDENTIFIER, actions);
// then:
verify(listener).onProcessActionUpdated(new ActionsState(FIRST_DEVICE_IDENTIFIER, actions));
verifyNoMoreInteractions(listener);
}
@Test
public void testDeviceStateDispatcherDispatchesDeviceStatesAndActions() {
// given:
DeviceStateListener listener = mock(DeviceStateListener.class);
Actions actions = mock(Actions.class);
DeviceStateDispatcher dispatcher = new DeviceStateDispatcher();
dispatcher.addListener(listener);
dispatcher.dispatchDeviceStateUpdates(getDevices());
dispatcher.dispatchActionStateUpdates(FIRST_DEVICE_IDENTIFIER, actions);
// when:
dispatcher.dispatchDeviceState(FIRST_DEVICE_IDENTIFIER);
// then:
verify(listener, times(2)).onDeviceStateUpdated(new DeviceState(FIRST_DEVICE_IDENTIFIER, firstDevice));
verify(listener).onDeviceStateUpdated(new DeviceState(SECOND_DEVICE_IDENTIFIER, secondDevice));
verify(listener).onProcessActionUpdated(new ActionsState(FIRST_DEVICE_IDENTIFIER, actions));
verifyNoMoreInteractions(listener);
}
}

View File

@@ -0,0 +1,50 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpFields;
import org.junit.jupiter.api.Test;
import org.openhab.binding.mielecloud.internal.webservice.exception.TooManyRequestsException;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class HttpUtilTest {
@Test
public void whenTheResponseHasARetryAfterHeaderThenItIsParsedAndPassedWithTheException() {
// given:
HttpFields httpFields = mock(HttpFields.class);
when(httpFields.containsKey("Retry-After")).thenReturn(true);
when(httpFields.get("Retry-After")).thenReturn("100");
Response response = mock(Response.class);
when(response.getStatus()).thenReturn(429);
when(response.getReason()).thenReturn("Too many requests!");
when(response.getHeaders()).thenReturn(httpFields);
// when:
try {
HttpUtil.checkHttpSuccess(response);
fail();
} catch (TooManyRequestsException e) {
// then:
assertEquals(100L, e.getSecondsUntilRetry());
}
}
}

View File

@@ -0,0 +1,241 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpMethod;
import org.junit.jupiter.api.Test;
import org.openhab.binding.mielecloud.internal.util.MockUtil;
import org.openhab.binding.mielecloud.internal.webservice.language.LanguageProvider;
import org.openhab.binding.mielecloud.internal.webservice.request.RequestFactoryImpl;
import org.openhab.core.io.net.http.HttpClientFactory;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class RequestFactoryImplTest {
private static final String URL = "https://www.openhab.org/";
private static final String ACCESS_TOKEN = "DE_0123456789abcdef0123456789abcdef";
private static final String JSON_CONTENT = "{ \"update\": 1 }";
private static final String LANGUAGE = "de";
private static final long REQUEST_TIMEOUT = 5;
private static final long EXTENDED_REQUEST_TIMEOUT = 10;
private static final TimeUnit REQUEST_TIMEOUT_UNIT = TimeUnit.SECONDS;
@Nullable
private String contentString;
@Nullable
private String contentType;
private final LanguageProvider defaultLanguageProvider = new LanguageProvider() {
@Override
public Optional<String> getLanguage() {
return Optional.of(LANGUAGE);
}
};
private final LanguageProvider emptyStringLanguageProvider = new LanguageProvider() {
@Override
public Optional<String> getLanguage() {
return Optional.of("");
}
};
private Request getRequestMock() {
Request requestMock = mock(Request.class);
when(requestMock.header(anyString(), anyString())).thenReturn(requestMock);
when(requestMock.timeout(anyLong(), any())).thenReturn(requestMock);
when(requestMock.method(any(HttpMethod.class))).thenReturn(requestMock);
when(requestMock.param(anyString(), anyString())).thenReturn(requestMock);
when(requestMock.content(any())).thenAnswer(i -> {
StringContentProvider provider = i.getArgument(0);
List<Byte> rawData = new ArrayList<Byte>();
provider.forEach(b -> {
b.rewind();
while (b.hasRemaining()) {
rawData.add(b.get());
}
});
byte[] data = new byte[rawData.size()];
for (int j = 0; j < data.length; j++) {
data[j] = rawData.get(j);
}
contentString = new String(data, StandardCharsets.UTF_8);
contentType = provider.getContentType();
return requestMock;
});
return requestMock;
}
private RequestFactoryImpl createRequestFactoryImpl(Request requestMock, LanguageProvider languageProvider) {
HttpClient httpClient = MockUtil.mockHttpClient(URL, requestMock);
HttpClientFactory httpClientFactory = mock(HttpClientFactory.class);
when(httpClientFactory.createHttpClient(anyString())).thenReturn(httpClient);
return new RequestFactoryImpl(httpClientFactory, languageProvider);
}
@Test
public void testCreateGetRequestReturnsRequestWithExpectedHeaders() {
// given:
Request requestMock = getRequestMock();
RequestFactoryImpl requestFactory = createRequestFactoryImpl(requestMock, defaultLanguageProvider);
// when:
Request request = requestFactory.createGetRequest(URL, ACCESS_TOKEN);
// then:
assertEquals(requestMock, request);
verify(request).header("Content-type", "application/json");
verify(request).header("Accept", "*/*");
verify(request).header("Authorization", "Bearer " + ACCESS_TOKEN);
verify(request).timeout(REQUEST_TIMEOUT, REQUEST_TIMEOUT_UNIT);
verify(request).method(HttpMethod.GET);
verify(request).param("language", LANGUAGE);
verifyNoMoreInteractions(request);
}
@Test
public void testCreatePutRequestReturnsRequestWithExpectedHeadersAndContent() {
Request requestMock = getRequestMock();
RequestFactoryImpl requestFactory = createRequestFactoryImpl(requestMock, defaultLanguageProvider);
// when:
Request request = requestFactory.createPutRequest(URL, ACCESS_TOKEN, JSON_CONTENT);
// then:
assertEquals(requestMock, request);
verify(request).header("Content-type", "application/json");
verify(request).header("Accept", "*/*");
verify(request).header("Authorization", "Bearer " + ACCESS_TOKEN);
verify(request).timeout(EXTENDED_REQUEST_TIMEOUT, REQUEST_TIMEOUT_UNIT);
verify(request).method(HttpMethod.PUT);
verify(request).content(any());
verify(request).param("language", LANGUAGE);
assertEquals(JSON_CONTENT, contentString);
assertEquals("application/json", contentType);
verifyNoMoreInteractions(request);
}
@Test
public void testCreatePostRequestReturnsRequestWithExpectedHeaders() {
Request requestMock = getRequestMock();
RequestFactoryImpl requestFactory = createRequestFactoryImpl(requestMock, defaultLanguageProvider);
// when:
Request request = requestFactory.createPostRequest(URL, ACCESS_TOKEN);
// then:
assertEquals(requestMock, request);
verify(request).header("Content-type", "application/json");
verify(request).header("Accept", "*/*");
verify(request).header("Authorization", "Bearer " + ACCESS_TOKEN);
verify(request).timeout(REQUEST_TIMEOUT, REQUEST_TIMEOUT_UNIT);
verify(request).method(HttpMethod.POST);
verify(request).param("language", LANGUAGE);
verifyNoMoreInteractions(request);
}
@Test
public void testCreateRequestWithoutSuppliedLangugeCreatesNoLanguageParameter() {
// given:
Request requestMock = getRequestMock();
RequestFactoryImpl requestFactory = createRequestFactoryImpl(requestMock, new LanguageProvider() {
@Override
public Optional<String> getLanguage() {
return Optional.empty();
}
});
// when:
Request request = requestFactory.createGetRequest(URL, ACCESS_TOKEN);
// then:
assertEquals(requestMock, request);
verify(request).header("Content-type", "application/json");
verify(request).header("Accept", "*/*");
verify(request).header("Authorization", "Bearer " + ACCESS_TOKEN);
verify(request).timeout(REQUEST_TIMEOUT, REQUEST_TIMEOUT_UNIT);
verify(request).method(HttpMethod.GET);
verifyNoMoreInteractions(request);
}
@Test
public void testCreateRequestWithEmptyLanguageCreatesNoLanguageParameter() {
// given:
Request requestMock = getRequestMock();
RequestFactoryImpl requestFactory = createRequestFactoryImpl(requestMock, emptyStringLanguageProvider);
// when:
Request request = requestFactory.createGetRequest(URL, ACCESS_TOKEN);
// then:
assertEquals(requestMock, request);
verify(request).header("Content-type", "application/json");
verify(request).header("Accept", "*/*");
verify(request).header("Authorization", "Bearer " + ACCESS_TOKEN);
verify(request).timeout(REQUEST_TIMEOUT, REQUEST_TIMEOUT_UNIT);
verify(request).method(HttpMethod.GET);
verifyNoMoreInteractions(request);
}
@Test
public void whenAnSseRequestIsCreatedWithoutLanguageThenTheRequiredParametersAreSet() {
Request requestMock = getRequestMock();
RequestFactoryImpl requestFactory = createRequestFactoryImpl(requestMock, emptyStringLanguageProvider);
// when:
Request request = requestFactory.createSseRequest(URL, ACCESS_TOKEN);
// then:
assertEquals(requestMock, request);
verify(request).header("Content-type", "application/json");
verify(request).header("Accept", "text/event-stream");
verify(request).header("Authorization", "Bearer " + ACCESS_TOKEN);
verifyNoMoreInteractions(request);
}
@Test
public void whenAnSseRequestIsCreatedWithLanguageThenTheAcceptLanguageHeaderIsSet() {
Request requestMock = getRequestMock();
RequestFactoryImpl requestFactory = createRequestFactoryImpl(requestMock, defaultLanguageProvider);
// when:
Request request = requestFactory.createSseRequest(URL, ACCESS_TOKEN);
// then:
assertEquals(requestMock, request);
verify(request).header("Content-type", "application/json");
verify(request).header("Accept", "text/event-stream");
verify(request).header("Authorization", "Bearer " + ACCESS_TOKEN);
verify(request).header("Accept-Language", LANGUAGE);
verifyNoMoreInteractions(request);
}
}

View File

@@ -0,0 +1,299 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.api;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.mielecloud.internal.webservice.api.json.Actions;
import org.openhab.binding.mielecloud.internal.webservice.api.json.Light;
import org.openhab.binding.mielecloud.internal.webservice.api.json.ProcessAction;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class ActionsStateTest {
private static final String DEVICE_IDENTIFIER = "003458276345";
@Test
public void testGetDeviceIdentifierReturnsDeviceIdentifier() {
// given:
Actions actions = mock(Actions.class);
ActionsState actionsState = new ActionsState(DEVICE_IDENTIFIER, actions);
// when:
String deviceId = actionsState.getDeviceIdentifier();
// then:
assertEquals(DEVICE_IDENTIFIER, deviceId);
}
@Test
public void testReturnValuesWhenActionsIsNull() {
// given:
ActionsState actionState = new ActionsState(DEVICE_IDENTIFIER, null);
// when:
boolean canBeStarted = actionState.canBeStarted();
boolean canBeStopped = actionState.canBeStopped();
boolean canBePaused = actionState.canBePaused();
boolean canStartSupercooling = actionState.canStartSupercooling();
boolean canStopSupercooling = actionState.canStopSupercooling();
boolean canContolSupercooling = actionState.canContolSupercooling();
boolean canStartSuperfreezing = actionState.canStartSuperfreezing();
boolean canStopSuperfreezing = actionState.canStopSuperfreezing();
boolean canControlSuperfreezing = actionState.canControlSuperfreezing();
boolean canEnableLight = actionState.canEnableLight();
boolean canDisableLight = actionState.canDisableLight();
// then:
assertFalse(canBeStarted);
assertFalse(canBeStopped);
assertFalse(canBePaused);
assertFalse(canStartSupercooling);
assertFalse(canStopSupercooling);
assertFalse(canContolSupercooling);
assertFalse(canStartSuperfreezing);
assertFalse(canStopSuperfreezing);
assertFalse(canControlSuperfreezing);
assertFalse(canEnableLight);
assertFalse(canDisableLight);
}
@Test
public void testReturnValuesWhenProcessActionIsEmpty() {
// given:
Actions actions = mock(Actions.class);
ActionsState actionState = new ActionsState(DEVICE_IDENTIFIER, null);
when(actions.getProcessAction()).thenReturn(Collections.emptyList());
// when:
boolean canBeStarted = actionState.canBeStarted();
boolean canBeStopped = actionState.canBeStopped();
boolean canBePaused = actionState.canBePaused();
boolean canStartSupercooling = actionState.canStartSupercooling();
boolean canStopSupercooling = actionState.canStopSupercooling();
boolean canStartSuperfreezing = actionState.canStartSuperfreezing();
boolean canStopSuperfreezing = actionState.canStopSuperfreezing();
// then:
assertFalse(canBeStarted);
assertFalse(canBeStopped);
assertFalse(canBePaused);
assertFalse(canStartSupercooling);
assertFalse(canStopSupercooling);
assertFalse(canStartSuperfreezing);
assertFalse(canStopSuperfreezing);
}
@Test
public void testReturnValuesWhenLightIsEmpty() {
// given:
Actions actions = mock(Actions.class);
ActionsState actionState = new ActionsState(DEVICE_IDENTIFIER, null);
when(actions.getLight()).thenReturn(Collections.emptyList());
// when:
boolean canEnableLight = actionState.canEnableLight();
boolean canDisableLight = actionState.canDisableLight();
// then:
assertFalse(canEnableLight);
assertFalse(canDisableLight);
}
@Test
public void testReturnValueWhenProcessActionStartIsAvailable() {
// given:
Actions actions = mock(Actions.class);
ActionsState actionState = new ActionsState(DEVICE_IDENTIFIER, actions);
when(actions.getProcessAction()).thenReturn(Collections.singletonList(ProcessAction.START));
// when:
boolean canBeStarted = actionState.canBeStarted();
// then:
assertTrue(canBeStarted);
}
@Test
public void testReturnValueWhenProcessActionStopIsAvailable() {
// given:
Actions actions = mock(Actions.class);
ActionsState actionState = new ActionsState(DEVICE_IDENTIFIER, actions);
when(actions.getProcessAction()).thenReturn(Collections.singletonList(ProcessAction.STOP));
// when:
boolean canBeStopped = actionState.canBeStopped();
// then:
assertTrue(canBeStopped);
}
@Test
public void testReturnValueWhenProcessActionStartSupercoolIsAvailable() {
// given:
Actions actions = mock(Actions.class);
ActionsState actionState = new ActionsState(DEVICE_IDENTIFIER, actions);
when(actions.getProcessAction()).thenReturn(Collections.singletonList(ProcessAction.START_SUPERCOOLING));
// when:
boolean canStartSupercooling = actionState.canStartSupercooling();
boolean canContolSupercooling = actionState.canContolSupercooling();
// then:
assertTrue(canStartSupercooling);
assertTrue(canContolSupercooling);
}
@Test
public void testReturnValueWhenProcessActionStartSuperfreezeIsAvailable() {
// given:
Actions actions = mock(Actions.class);
ActionsState actionState = new ActionsState(DEVICE_IDENTIFIER, actions);
when(actions.getProcessAction()).thenReturn(Collections.singletonList(ProcessAction.START_SUPERFREEZING));
// when:
boolean canStartSuperfreezing = actionState.canStartSuperfreezing();
boolean canControlSuperfreezing = actionState.canControlSuperfreezing();
// then:
assertTrue(canStartSuperfreezing);
assertTrue(canControlSuperfreezing);
}
@Test
public void testReturnValueWhenLightEnableIsAvailable() {
// given:
Actions actions = mock(Actions.class);
ActionsState actionState = new ActionsState(DEVICE_IDENTIFIER, actions);
when(actions.getLight()).thenReturn(Collections.singletonList(Light.ENABLE));
// when:
boolean canEnableLight = actionState.canEnableLight();
// then:
assertTrue(canEnableLight);
}
@Test
public void testReturnValueWhenLightDisableIsAvailable() {
// given:
Actions actions = mock(Actions.class);
ActionsState actionState = new ActionsState(DEVICE_IDENTIFIER, actions);
when(actions.getLight()).thenReturn(Collections.singletonList(Light.DISABLE));
// when:
boolean canDisableLight = actionState.canDisableLight();
// then:
assertTrue(canDisableLight);
}
@Test
public void testCanControlLightReturnsTrueWhenLightCanBeEnabled() {
// given:
Actions actions = mock(Actions.class);
when(actions.getLight()).thenReturn(Collections.singletonList(Light.ENABLE));
ActionsState actionState = new ActionsState(DEVICE_IDENTIFIER, actions);
// when:
boolean canControlLight = actionState.canControlLight();
// then:
assertTrue(canControlLight);
}
@Test
public void testCanControlLightReturnsTrueWhenLightCanBeDisabled() {
// given:
Actions actions = mock(Actions.class);
when(actions.getLight()).thenReturn(Collections.singletonList(Light.DISABLE));
ActionsState actionState = new ActionsState(DEVICE_IDENTIFIER, actions);
// when:
boolean canControlLight = actionState.canControlLight();
// then:
assertTrue(canControlLight);
}
@Test
public void testCanControlLightReturnsTrueWhenLightCanBeEnabledAndDisabled() {
// given:
Actions actions = mock(Actions.class);
when(actions.getLight()).thenReturn(Arrays.asList(Light.ENABLE, Light.DISABLE));
ActionsState actionState = new ActionsState(DEVICE_IDENTIFIER, actions);
// when:
boolean canControlLight = actionState.canControlLight();
// then:
assertTrue(canControlLight);
}
@Test
public void testCanControlLightReturnsFalseWhenNoLightOptionIsAvailable() {
// given:
Actions actions = mock(Actions.class);
when(actions.getLight()).thenReturn(new LinkedList<Light>());
ActionsState actionState = new ActionsState(DEVICE_IDENTIFIER, actions);
// when:
boolean canControlLight = actionState.canControlLight();
// then:
assertFalse(canControlLight);
}
@Test
public void testNoProgramCanBeSetWhenNoProgramIdIsPresent() {
// given:
Actions actions = mock(Actions.class);
when(actions.getProgramId()).thenReturn(Collections.emptyList());
ActionsState actionState = new ActionsState(DEVICE_IDENTIFIER, actions);
// when:
boolean canSetActiveProgram = actionState.canSetActiveProgramId();
// then:
assertFalse(canSetActiveProgram);
}
@Test
public void testProgramIdCanBeSetWhenProgramIdIsPresent() {
// given:
Actions actions = mock(Actions.class);
when(actions.getProgramId()).thenReturn(Collections.singletonList(1));
ActionsState actionState = new ActionsState(DEVICE_IDENTIFIER, actions);
// when:
boolean canSetActiveProgram = actionState.canSetActiveProgramId();
// then:
assertTrue(canSetActiveProgram);
}
}

View File

@@ -0,0 +1,144 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.api;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.Objects;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openhab.binding.mielecloud.internal.webservice.api.json.DeviceType;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class CoolingDeviceTemperatureStateTest {
private static final Integer TEMPERATURE_0 = 8;
private static final Integer TEMPERATURE_1 = -10;
private static final Integer TARGET_TEMPERATURE_0 = 5;
private static final Integer TARGET_TEMPERATURE_1 = -18;
@Nullable
private DeviceState deviceState;
private DeviceState getDeviceState() {
assertNotNull(deviceState);
return Objects.requireNonNull(deviceState);
}
@BeforeEach
public void setUp() {
deviceState = mock(DeviceState.class);
when(getDeviceState().getTemperature(0)).thenReturn(Optional.of(TEMPERATURE_0));
when(getDeviceState().getTemperature(1)).thenReturn(Optional.of(TEMPERATURE_1));
when(getDeviceState().getTargetTemperature(0)).thenReturn(Optional.of(TARGET_TEMPERATURE_0));
when(getDeviceState().getTargetTemperature(1)).thenReturn(Optional.of(TARGET_TEMPERATURE_1));
}
@Test
public void testGetFridgeTemperaturesForFridge() {
// given:
when(getDeviceState().getRawType()).thenReturn(DeviceType.FRIDGE);
CoolingDeviceTemperatureState state = new CoolingDeviceTemperatureState(getDeviceState());
// when:
Integer current = state.getFridgeTemperature().get();
Integer target = state.getFridgeTargetTemperature().get();
// then:
assertEquals(TEMPERATURE_0, current);
assertEquals(TARGET_TEMPERATURE_0, target);
}
@Test
public void testGetFridgeTemperaturesForFridgeFreezerCombination() {
// given:
when(getDeviceState().getRawType()).thenReturn(DeviceType.FRIDGE_FREEZER_COMBINATION);
CoolingDeviceTemperatureState state = new CoolingDeviceTemperatureState(getDeviceState());
// when:
Integer current = state.getFridgeTemperature().get();
Integer target = state.getFridgeTargetTemperature().get();
// then:
assertEquals(TEMPERATURE_0, current);
assertEquals(TARGET_TEMPERATURE_0, target);
}
@Test
public void testGetFridgeTemperaturesForFreezer() {
// given:
when(getDeviceState().getRawType()).thenReturn(DeviceType.FREEZER);
CoolingDeviceTemperatureState state = new CoolingDeviceTemperatureState(getDeviceState());
// when:
Optional<Integer> current = state.getFridgeTemperature();
Optional<Integer> target = state.getFridgeTargetTemperature();
// then:
assertFalse(current.isPresent());
assertFalse(target.isPresent());
}
@Test
public void testGetFreezerTemperaturesForFridge() {
// given:
when(getDeviceState().getRawType()).thenReturn(DeviceType.FRIDGE);
CoolingDeviceTemperatureState state = new CoolingDeviceTemperatureState(getDeviceState());
// when:
Optional<Integer> current = state.getFreezerTemperature();
Optional<Integer> target = state.getFreezerTargetTemperature();
// then:
assertFalse(current.isPresent());
assertFalse(target.isPresent());
}
@Test
public void testGetFreezerTemperaturesForFridgeFreezerCombination() {
// given:
when(getDeviceState().getRawType()).thenReturn(DeviceType.FRIDGE_FREEZER_COMBINATION);
CoolingDeviceTemperatureState state = new CoolingDeviceTemperatureState(getDeviceState());
// when:
Integer current = state.getFreezerTemperature().get();
Integer target = state.getFreezerTargetTemperature().get();
// then:
assertEquals(TEMPERATURE_1, current);
assertEquals(TARGET_TEMPERATURE_1, target);
}
@Test
public void testGetFreezerTemperaturesForFreezer() {
// given:
when(getDeviceState().getRawType()).thenReturn(DeviceType.FREEZER);
CoolingDeviceTemperatureState state = new CoolingDeviceTemperatureState(getDeviceState());
// when:
Integer current = state.getFreezerTemperature().get();
Integer target = state.getFreezerTargetTemperature().get();
// then:
assertEquals(TEMPERATURE_0, current);
assertEquals(TARGET_TEMPERATURE_0, target);
}
}

View File

@@ -0,0 +1,576 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.api;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.mielecloud.internal.webservice.api.json.StateType;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class TransitionStateTest {
private final DeviceState historic = mock(DeviceState.class);
private final DeviceState previous = mock(DeviceState.class);
private final DeviceState next = mock(DeviceState.class);
@Test
public void testHasFinishedChangedReturnsTrueWhenPreviousStateIsNull() {
// given:
when(next.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
TransitionState transitionState = new TransitionState(null, next);
// when:
boolean hasFinishedChanged = transitionState.hasFinishedChanged();
// then:
assertTrue(hasFinishedChanged);
}
@Test
public void testHasFinishedChangedReturnsTrueWhenPreviousStateIsUnknown() {
// given:
when(previous.getStateType()).thenReturn(Optional.empty());
when(previous.isInState(any())).thenCallRealMethod();
when(next.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(next.isInState(any())).thenCallRealMethod();
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
boolean hasFinishedChanged = transitionState.hasFinishedChanged();
// then:
assertTrue(hasFinishedChanged);
}
@Test
public void testHasFinishedChangedReturnsFalseWhenNoStateTransitionOccurred() {
// given:
when(previous.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(next.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
boolean hasFinishedChanged = transitionState.hasFinishedChanged();
// then:
assertFalse(hasFinishedChanged);
}
@Test
public void testHasFinishedChangedReturnsTrueWhenStateChangedFromRunningToEndProgrammed() {
// given:
when(previous.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(previous.isInState(any())).thenCallRealMethod();
when(next.getStateType()).thenReturn(Optional.of(StateType.END_PROGRAMMED));
when(next.isInState(any())).thenCallRealMethod();
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
boolean hasFinishedChanged = transitionState.hasFinishedChanged();
// then:
assertTrue(hasFinishedChanged);
}
@Test
public void testHasFinishedChangedReturnsTrueWhenStateChangedFromRunningToProgrammed() {
// given:
when(previous.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(previous.isInState(any())).thenCallRealMethod();
when(next.getStateType()).thenReturn(Optional.of(StateType.PROGRAMMED));
when(next.isInState(any())).thenCallRealMethod();
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
boolean hasFinishedChanged = transitionState.hasFinishedChanged();
// then:
assertTrue(hasFinishedChanged);
}
@Test
public void testHasFinishedChangedReturnsFalseWhenStateChangedFromRunningToPause() {
// given:
when(previous.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(next.getStateType()).thenReturn(Optional.of(StateType.PAUSE));
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
boolean hasFinishedChanged = transitionState.hasFinishedChanged();
// then:
assertFalse(hasFinishedChanged);
}
@Test
public void testHasFinishedChangedReturnsTrueWhenStateChangedFromProgrammedWaitingToStartToRunning() {
// given:
when(previous.getStateType()).thenReturn(Optional.of(StateType.PROGRAMMED_WAITING_TO_START));
when(previous.isInState(any())).thenCallRealMethod();
when(next.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(next.isInState(any())).thenCallRealMethod();
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
boolean hasFinishedChanged = transitionState.hasFinishedChanged();
// then:
assertTrue(hasFinishedChanged);
}
@Test
public void testHasFinishedChangedReturnsFalseWhenStateRemainsProgrammedWaitingToStart() {
// given:
when(previous.getStateType()).thenReturn(Optional.of(StateType.PROGRAMMED_WAITING_TO_START));
when(next.getStateType()).thenReturn(Optional.of(StateType.PROGRAMMED_WAITING_TO_START));
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
boolean hasFinishedChanged = transitionState.hasFinishedChanged();
// then:
assertFalse(hasFinishedChanged);
}
@Test
public void testHasFinishedChangedReturnsFalseWhenStateChangedFromPauseToRunning() {
// given:
when(previous.getStateType()).thenReturn(Optional.of(StateType.PAUSE));
when(next.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
boolean hasFinishedChanged = transitionState.hasFinishedChanged();
// then:
assertFalse(hasFinishedChanged);
}
@Test
public void testHasFinishedChangedReturnsTrueWhenStateChangedFromEndProgrammedToOff() {
// given:
when(previous.getStateType()).thenReturn(Optional.of(StateType.END_PROGRAMMED));
when(previous.isInState(any())).thenCallRealMethod();
when(next.getStateType()).thenReturn(Optional.of(StateType.OFF));
when(next.isInState(any())).thenCallRealMethod();
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
boolean hasFinishedChanged = transitionState.hasFinishedChanged();
// then:
assertTrue(hasFinishedChanged);
}
@Test
public void testHasFinishedChangedReturnsFalseWhenStateChangedFromRunningToFailure() {
// given:
when(previous.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(next.getStateType()).thenReturn(Optional.of(StateType.FAILURE));
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
boolean hasFinishedChanged = transitionState.hasFinishedChanged();
// then:
assertFalse(hasFinishedChanged);
}
@Test
public void testHasFinishedChangedReturnsFalseWhenStateChangedFromPauseToFailure() {
// given:
when(previous.getStateType()).thenReturn(Optional.of(StateType.PAUSE));
when(next.getStateType()).thenReturn(Optional.of(StateType.FAILURE));
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
boolean hasFinishedChanged = transitionState.hasFinishedChanged();
// then:
assertFalse(hasFinishedChanged);
}
@Test
public void testIsFinishedReturnsTrueWhenStateChangedFromRunningToEndProgrammed() {
// given:
when(previous.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(next.getStateType()).thenReturn(Optional.of(StateType.END_PROGRAMMED));
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
Boolean isFinished = transitionState.isFinished().get();
// then:
assertTrue(isFinished);
}
@Test
public void testIsFinishedReturnsTrueWhenStateChangedFromRunningToProgrammed() {
// given:
when(previous.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(next.getStateType()).thenReturn(Optional.of(StateType.PROGRAMMED));
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
Boolean isFinished = transitionState.isFinished().get();
// then:
assertTrue(isFinished);
}
@Test
public void testIsFinishedReturnsFalseWhenStateChangedFromProgrammedWaitingToStartToRunning() {
// given:
when(previous.getStateType()).thenReturn(Optional.of(StateType.PROGRAMMED_WAITING_TO_START));
when(previous.isInState(any())).thenCallRealMethod();
when(next.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(next.isInState(any())).thenCallRealMethod();
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
Boolean isFinished = transitionState.isFinished().get();
// then:
assertFalse(isFinished);
}
@Test
public void testIsFinishedReturnsFalseWhenStateChangedFromRunningToFailure() {
// given:
when(previous.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(previous.isInState(any())).thenCallRealMethod();
when(next.getStateType()).thenReturn(Optional.of(StateType.FAILURE));
when(next.isInState(any())).thenCallRealMethod();
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
Boolean isFinished = transitionState.isFinished().get();
// then:
assertFalse(isFinished);
}
@Test
public void testIsFinishedReturnsFalseWhenStateChangedFromPauseToFailure() {
// given:
when(previous.getStateType()).thenReturn(Optional.of(StateType.PAUSE));
when(previous.isInState(any())).thenCallRealMethod();
when(next.getStateType()).thenReturn(Optional.of(StateType.FAILURE));
when(next.isInState(any())).thenCallRealMethod();
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
Boolean isFinished = transitionState.isFinished().get();
// then:
assertFalse(isFinished);
}
@Test
public void testIsFinishedReturnsTrueWhenStateChangedFromEndProgrammedToOff() {
// given:
when(previous.getStateType()).thenReturn(Optional.of(StateType.END_PROGRAMMED));
when(previous.isInState(any())).thenCallRealMethod();
when(next.getStateType()).thenReturn(Optional.of(StateType.OFF));
when(next.isInState(any())).thenCallRealMethod();
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
Boolean isFinished = transitionState.isFinished().get();
// then:
assertFalse(isFinished);
}
@Test
public void testIsFinishedReturnsNullWhenPreviousStateIsNull() {
// given:
when(next.getStateType()).thenReturn(Optional.of(StateType.IDLE));
TransitionState transitionState = new TransitionState(null, next);
// when:
Optional<Boolean> isFinished = transitionState.isFinished();
// then:
assertFalse(isFinished.isPresent());
}
@Test
public void testIsFinishedReturnsNullWhenPreviousStateIsUnknown() {
// given:
when(previous.getStateType()).thenReturn(Optional.empty());
when(next.getStateType()).thenReturn(Optional.of(StateType.IDLE));
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
Optional<Boolean> isFinished = transitionState.isFinished();
// then:
assertFalse(isFinished.isPresent());
}
@Test
public void testProgramStartedWithZeroRemainingTimeShowsNoRemainingTimeAndProgress() {
// given:
when(previous.isInState(any())).thenCallRealMethod();
when(previous.getStateType()).thenReturn(Optional.of(StateType.PROGRAMMED_WAITING_TO_START));
when(next.isInState(any())).thenCallRealMethod();
when(next.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(next.getRemainingTime()).thenReturn(Optional.of(0));
when(next.getProgress()).thenReturn(Optional.of(100));
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
Optional<Integer> remainingTime = transitionState.getRemainingTime();
Optional<Integer> progress = transitionState.getProgress();
// then:
assertFalse(remainingTime.isPresent());
assertFalse(progress.isPresent());
}
@Test
public void testProgramStartetdWithRemainingTimeShowsRemainingTimeAndProgress() {
// given:
when(previous.isInState(any())).thenCallRealMethod();
when(previous.getStateType()).thenReturn(Optional.of(StateType.PROGRAMMED_WAITING_TO_START));
when(next.isInState(any())).thenCallRealMethod();
when(next.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(next.getRemainingTime()).thenReturn(Optional.of(2));
when(next.getProgress()).thenReturn(Optional.of(50));
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
int remainingTime = transitionState.getRemainingTime().get();
int progress = transitionState.getProgress().get();
// then:
assertEquals(2, remainingTime);
assertEquals(50, progress);
}
@Test
public void testProgramCountingDownRemainingTimeToZeroShowsRemainingTimeAndProgress() {
// given:
when(previous.isInState(any())).thenCallRealMethod();
when(previous.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(previous.getRemainingTime()).thenReturn(Optional.of(1));
when(next.isInState(any())).thenCallRealMethod();
when(next.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(next.getRemainingTime()).thenReturn(Optional.of(0));
when(next.getProgress()).thenReturn(Optional.of(100));
TransitionState transitionState = new TransitionState(new TransitionState(null, previous), next);
// when:
int remainingTime = transitionState.getRemainingTime().get();
int progress = transitionState.getProgress().get();
// then:
assertEquals(0, remainingTime);
assertEquals(100, progress);
}
@Test
public void testDevicePairedWhileRunningWithZeroRemainingTimeShowsNoRemainingTimeAndProgress() {
// given:
when(next.isInState(any())).thenCallRealMethod();
when(next.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(next.getRemainingTime()).thenReturn(Optional.of(0));
when(next.getProgress()).thenReturn(Optional.of(100));
TransitionState transitionState = new TransitionState(null, next);
// when:
Optional<Integer> remainingTime = transitionState.getRemainingTime();
Optional<Integer> progress = transitionState.getProgress();
// then:
assertFalse(remainingTime.isPresent());
assertFalse(progress.isPresent());
}
@Test
public void testDevicePairedWhileRunningWithRemainingTimeShowsRemainingTimeAndProgress() {
// given:
when(next.isInState(any())).thenCallRealMethod();
when(next.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(next.getRemainingTime()).thenReturn(Optional.of(3));
when(next.getProgress()).thenReturn(Optional.of(80));
TransitionState transitionState = new TransitionState(null, next);
// when:
int remainingTime = transitionState.getRemainingTime().get();
int progress = transitionState.getProgress().get();
// then:
assertEquals(3, remainingTime);
assertEquals(80, progress);
}
@Test
public void testWhenNoRemainingTimeIsSetWhileProgramIsRunningThenNoRemainingTimeAndProgressIsShown() {
// given:
when(historic.isInState(any())).thenCallRealMethod();
when(historic.getStateType()).thenReturn(Optional.of(StateType.PROGRAMMED_WAITING_TO_START));
when(previous.isInState(any())).thenCallRealMethod();
when(previous.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(previous.getRemainingTime()).thenReturn(Optional.of(0));
when(next.isInState(any())).thenCallRealMethod();
when(next.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(next.getRemainingTime()).thenReturn(Optional.of(0));
when(next.getProgress()).thenReturn(Optional.of(100));
TransitionState transitionState = new TransitionState(
new TransitionState(new TransitionState(null, historic), previous), next);
// when:
Optional<Integer> remainingTime = transitionState.getRemainingTime();
Optional<Integer> progress = transitionState.getProgress();
// then:
assertFalse(remainingTime.isPresent());
assertFalse(progress.isPresent());
}
@Test
public void testRemainingTimeIsSetWhileRunningShowsRemainingTimeAndProgress() {
// given:
when(historic.isInState(any())).thenCallRealMethod();
when(historic.getStateType()).thenReturn(Optional.of(StateType.PROGRAMMED_WAITING_TO_START));
when(previous.isInState(any())).thenCallRealMethod();
when(previous.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(previous.getRemainingTime()).thenReturn(Optional.of(0));
when(next.isInState(any())).thenCallRealMethod();
when(next.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(next.getRemainingTime()).thenReturn(Optional.of(100));
when(next.getProgress()).thenReturn(Optional.of(10));
TransitionState transitionState = new TransitionState(
new TransitionState(new TransitionState(null, historic), previous), next);
// when:
int remainingTime = transitionState.getRemainingTime().get();
int progress = transitionState.getProgress().get();
// then:
assertEquals(100, remainingTime);
assertEquals(10, progress);
}
@Test
public void testPreviousProgramDoesNotAffectHandlingOfRemainingTimeAndProgressForNextProgramCase1() {
// given:
DeviceState beforeHistoric = mock(DeviceState.class);
when(beforeHistoric.isInState(any())).thenCallRealMethod();
when(beforeHistoric.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(beforeHistoric.getRemainingTime()).thenReturn(Optional.of(1));
when(historic.isInState(any())).thenCallRealMethod();
when(historic.getStateType()).thenReturn(Optional.of(StateType.END_PROGRAMMED));
when(historic.getRemainingTime()).thenReturn(Optional.of(0));
when(previous.isInState(any())).thenCallRealMethod();
when(previous.getStateType()).thenReturn(Optional.of(StateType.PROGRAMMED_WAITING_TO_START));
when(previous.getRemainingTime()).thenReturn(Optional.of(0));
when(next.isInState(any())).thenCallRealMethod();
when(next.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(next.getRemainingTime()).thenReturn(Optional.of(0));
when(next.getProgress()).thenReturn(Optional.of(100));
TransitionState transitionState = new TransitionState(
new TransitionState(new TransitionState(new TransitionState(null, beforeHistoric), historic), previous),
next);
// when:
Optional<Integer> remainingTime = transitionState.getRemainingTime();
Optional<Integer> progress = transitionState.getProgress();
// then:
assertFalse(remainingTime.isPresent());
assertFalse(progress.isPresent());
}
@Test
public void testPreviousProgramDoesNotAffectHandlingOfRemainingTimeAndProgressForNextProgramCase2() {
// given:
DeviceState beforeHistoric = mock(DeviceState.class);
when(beforeHistoric.isInState(any())).thenCallRealMethod();
when(beforeHistoric.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(beforeHistoric.getRemainingTime()).thenReturn(Optional.of(1));
when(historic.isInState(any())).thenCallRealMethod();
when(historic.getStateType()).thenReturn(Optional.of(StateType.END_PROGRAMMED));
when(historic.getRemainingTime()).thenReturn(Optional.of(0));
when(previous.isInState(any())).thenCallRealMethod();
when(previous.getStateType()).thenReturn(Optional.of(StateType.PROGRAMMED_WAITING_TO_START));
when(previous.getRemainingTime()).thenReturn(Optional.of(0));
when(next.isInState(any())).thenCallRealMethod();
when(next.getStateType()).thenReturn(Optional.of(StateType.RUNNING));
when(next.getRemainingTime()).thenReturn(Optional.of(10));
when(next.getProgress()).thenReturn(Optional.of(60));
TransitionState transitionState = new TransitionState(
new TransitionState(new TransitionState(new TransitionState(null, beforeHistoric), historic), previous),
next);
// when:
int remainingTime = transitionState.getRemainingTime().get();
int progress = transitionState.getProgress().get();
// then:
assertEquals(10, remainingTime);
assertEquals(60, progress);
}
}

View File

@@ -0,0 +1,455 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.api;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.Objects;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.Test;
import org.openhab.binding.mielecloud.internal.webservice.api.json.DeviceType;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class WineStorageDeviceTemperatureStateTest {
private static final Integer TEMPERATURE_0 = 8;
private static final Integer TEMPERATURE_1 = 10;
private static final Integer TEMPERATURE_2 = 12;
private static final Integer TARGET_TEMPERATURE_0 = 5;
private static final Integer TARGET_TEMPERATURE_1 = 9;
private static final Integer TARGET_TEMPERATURE_2 = 11;
@Nullable
private DeviceState deviceState;
private DeviceState getDeviceState() {
assertNotNull(deviceState);
return Objects.requireNonNull(deviceState);
}
private void setUpDeviceStateMock(int numberOfTemperatures) {
deviceState = mock(DeviceState.class);
if (numberOfTemperatures > 0) {
when(getDeviceState().getTemperature(0)).thenReturn(Optional.of(TEMPERATURE_0));
when(getDeviceState().getTargetTemperature(0)).thenReturn(Optional.of(TARGET_TEMPERATURE_0));
} else {
when(getDeviceState().getTemperature(0)).thenReturn(Optional.empty());
when(getDeviceState().getTargetTemperature(0)).thenReturn(Optional.empty());
}
if (numberOfTemperatures > 1) {
when(getDeviceState().getTemperature(1)).thenReturn(Optional.of(TEMPERATURE_1));
when(getDeviceState().getTargetTemperature(1)).thenReturn(Optional.of(TARGET_TEMPERATURE_1));
} else {
when(getDeviceState().getTemperature(1)).thenReturn(Optional.empty());
when(getDeviceState().getTargetTemperature(1)).thenReturn(Optional.empty());
}
if (numberOfTemperatures > 2) {
when(getDeviceState().getTemperature(2)).thenReturn(Optional.of(TEMPERATURE_2));
when(getDeviceState().getTargetTemperature(2)).thenReturn(Optional.of(TARGET_TEMPERATURE_2));
} else {
when(getDeviceState().getTemperature(2)).thenReturn(Optional.empty());
when(getDeviceState().getTargetTemperature(2)).thenReturn(Optional.empty());
}
}
@Test
public void testGetTemperaturesForWineCabinetWithThreeCompartments() {
// given:
setUpDeviceStateMock(3);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Optional<Integer> temperature = state.getTemperature();
Optional<Integer> targetTemperature = state.getTargetTemperature();
// then:
assertFalse(temperature.isPresent());
assertFalse(targetTemperature.isPresent());
}
@Test
public void testGetTemperaturesForWineCabinetWithTwoCompartments() {
// given:
setUpDeviceStateMock(2);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Optional<Integer> temperature = state.getTemperature();
Optional<Integer> targetTemperature = state.getTargetTemperature();
// then:
assertFalse(temperature.isPresent());
assertFalse(targetTemperature.isPresent());
}
@Test
public void testGetTemperaturesForWineCabinetWithOneCompartment() {
// given:
setUpDeviceStateMock(1);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Integer temperature = state.getTemperature().get();
Integer targetTemperature = state.getTargetTemperature().get();
// then:
assertEquals(TEMPERATURE_0, temperature);
assertEquals(TARGET_TEMPERATURE_0, targetTemperature);
}
@Test
public void testGetTemperaturesForWineCabinetFreezerCombination() {
// given:
setUpDeviceStateMock(2);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET_FREEZER_COMBINATION);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Optional<Integer> temperature = state.getTemperature();
Optional<Integer> targetTemperature = state.getTargetTemperature();
// then:
assertFalse(temperature.isPresent());
assertFalse(targetTemperature.isPresent());
}
@Test
public void testGetTemperaturesForOtherDeviceWithOneTemperature() {
// given:
setUpDeviceStateMock(1);
when(getDeviceState().getRawType()).thenReturn(DeviceType.OVEN);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Optional<Integer> temperature = state.getTemperature();
Optional<Integer> targetTemperature = state.getTargetTemperature();
// then:
assertFalse(temperature.isPresent());
assertFalse(targetTemperature.isPresent());
}
@Test
public void testGetTemperaturesWhenNoTemperaturesAreAvailable() {
// given:
setUpDeviceStateMock(0);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Optional<Integer> temperature = state.getTemperature();
Optional<Integer> targetTemperature = state.getTargetTemperature();
// then:
assertFalse(temperature.isPresent());
assertFalse(targetTemperature.isPresent());
}
@Test
public void testGetTopTemperaturesForWineCabinetWithThreeCompartments() {
// given:
setUpDeviceStateMock(3);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Integer temperature = state.getTopTemperature().get();
Integer targetTemperature = state.getTopTargetTemperature().get();
// then:
assertEquals(TEMPERATURE_0, temperature);
assertEquals(TARGET_TEMPERATURE_0, targetTemperature);
}
@Test
public void testGetTopTemperaturesForWineCabinetWithTwoCompartments() {
// given:
setUpDeviceStateMock(2);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Integer temperature = state.getTopTemperature().get();
Integer targetTemperature = state.getTopTargetTemperature().get();
// then:
assertEquals(TEMPERATURE_0, temperature);
assertEquals(TARGET_TEMPERATURE_0, targetTemperature);
}
@Test
public void testGetTopTemperaturesForWineCabinetWithOneCompartment() {
// given:
setUpDeviceStateMock(1);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Optional<Integer> temperature = state.getTopTemperature();
Optional<Integer> targetTemperature = state.getTopTargetTemperature();
// then:
assertFalse(temperature.isPresent());
assertFalse(targetTemperature.isPresent());
}
@Test
public void testGetTopTemperaturesForWineCabinetFreezerCombination() {
// given:
setUpDeviceStateMock(2);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET_FREEZER_COMBINATION);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Integer temperature = state.getTopTemperature().get();
Integer targetTemperature = state.getTopTargetTemperature().get();
// then:
assertEquals(TEMPERATURE_0, temperature);
assertEquals(TARGET_TEMPERATURE_0, targetTemperature);
}
@Test
public void testGetTopTemperaturesForOtherDeviceWithTwoTemperatures() {
// given:
setUpDeviceStateMock(2);
when(getDeviceState().getRawType()).thenReturn(DeviceType.OVEN);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Optional<Integer> temperature = state.getTopTemperature();
Optional<Integer> targetTemperature = state.getTopTargetTemperature();
// then:
assertFalse(temperature.isPresent());
assertFalse(targetTemperature.isPresent());
}
@Test
public void testGetTopTemperaturesWhenNoTemperaturesAreAvailable() {
// given:
setUpDeviceStateMock(0);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Optional<Integer> temperature = state.getTopTemperature();
Optional<Integer> targetTemperature = state.getTopTargetTemperature();
// then:
assertFalse(temperature.isPresent());
assertFalse(targetTemperature.isPresent());
}
@Test
public void testGetMiddleTemperaturesForWineCabinetWithThreeCompartments() {
// given:
setUpDeviceStateMock(3);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Integer temperature = state.getMiddleTemperature().get();
Integer targetTemperature = state.getMiddleTargetTemperature().get();
// then:
assertEquals(TEMPERATURE_1, temperature);
assertEquals(TARGET_TEMPERATURE_1, targetTemperature);
}
@Test
public void testGetMiddleTemperaturesForWineCabinetWithTwoCompartments() {
// given:
setUpDeviceStateMock(2);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Optional<Integer> temperature = state.getMiddleTemperature();
Optional<Integer> targetTemperature = state.getMiddleTargetTemperature();
// then:
assertFalse(temperature.isPresent());
assertFalse(targetTemperature.isPresent());
}
@Test
public void testGetMiddleTemperaturesForWineCabinetWithOneCompartment() {
// given:
setUpDeviceStateMock(1);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Optional<Integer> temperature = state.getMiddleTemperature();
Optional<Integer> targetTemperature = state.getMiddleTargetTemperature();
// then:
assertFalse(temperature.isPresent());
assertFalse(targetTemperature.isPresent());
}
@Test
public void testGetMiddleTemperaturesForWineCabinetFreezerCombination() {
// given:
setUpDeviceStateMock(2);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET_FREEZER_COMBINATION);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Optional<Integer> temperature = state.getMiddleTemperature();
Optional<Integer> targetTemperature = state.getMiddleTargetTemperature();
// then:
assertFalse(temperature.isPresent());
assertFalse(targetTemperature.isPresent());
}
@Test
public void testGetMiddleTemperaturesForOtherDeviceWithTwoTemperatures() {
// given:
setUpDeviceStateMock(2);
when(getDeviceState().getRawType()).thenReturn(DeviceType.OVEN);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Optional<Integer> temperature = state.getMiddleTemperature();
Optional<Integer> targetTemperature = state.getMiddleTargetTemperature();
// then:
assertFalse(temperature.isPresent());
assertFalse(targetTemperature.isPresent());
}
@Test
public void testGetMiddleTemperaturesWhenNoTemperaturesAreAvailable() {
// given:
setUpDeviceStateMock(0);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Optional<Integer> temperature = state.getMiddleTemperature();
Optional<Integer> targetTemperature = state.getMiddleTargetTemperature();
// then:
assertFalse(temperature.isPresent());
assertFalse(targetTemperature.isPresent());
}
@Test
public void testGetBottomTemperaturesForWineCabinetWithThreeCompartments() {
// given:
setUpDeviceStateMock(3);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Integer temperature = state.getBottomTemperature().get();
Integer targetTemperature = state.getBottomTargetTemperature().get();
// then:
assertEquals(TEMPERATURE_2, temperature);
assertEquals(TARGET_TEMPERATURE_2, targetTemperature);
}
@Test
public void testGetBottomTemperaturesForWineCabinetWithTwoCompartments() {
// given:
setUpDeviceStateMock(2);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Integer temperature = state.getBottomTemperature().get();
Integer targetTemperature = state.getBottomTargetTemperature().get();
// then:
assertEquals(TEMPERATURE_1, temperature);
assertEquals(TARGET_TEMPERATURE_1, targetTemperature);
}
@Test
public void testGetBottomTemperaturesForWineCabinetWithOneCompartment() {
// given:
setUpDeviceStateMock(1);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Optional<Integer> temperature = state.getBottomTemperature();
Optional<Integer> targetTemperature = state.getBottomTargetTemperature();
// then:
assertFalse(temperature.isPresent());
assertFalse(targetTemperature.isPresent());
}
@Test
public void testGetBottomTemperaturesForWineCabinetFreezerCombination() {
// given:
setUpDeviceStateMock(2);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET_FREEZER_COMBINATION);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Integer temperature = state.getBottomTemperature().get();
Integer targetTemperature = state.getBottomTargetTemperature().get();
// then:
assertEquals(TEMPERATURE_1, temperature);
assertEquals(TARGET_TEMPERATURE_1, targetTemperature);
}
@Test
public void testGetBottomTemperaturesForOtherDeviceWithTwoTemperatures() {
// given:
setUpDeviceStateMock(2);
when(getDeviceState().getRawType()).thenReturn(DeviceType.OVEN);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Optional<Integer> temperature = state.getBottomTemperature();
Optional<Integer> targetTemperature = state.getBottomTargetTemperature();
// then:
assertFalse(temperature.isPresent());
assertFalse(targetTemperature.isPresent());
}
@Test
public void testGetBottomTemperaturesWhenNoTemperaturesAreAvailable() {
// given:
setUpDeviceStateMock(0);
when(getDeviceState().getRawType()).thenReturn(DeviceType.WINE_CABINET);
WineStorageDeviceTemperatureState state = new WineStorageDeviceTemperatureState(getDeviceState());
// when:
Optional<Integer> temperature = state.getBottomTemperature();
Optional<Integer> targetTemperature = state.getBottomTargetTemperature();
// then:
assertFalse(temperature.isPresent());
assertFalse(targetTemperature.isPresent());
}
}

View File

@@ -0,0 +1,103 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.api.json;
import static org.junit.jupiter.api.Assertions.*;
import java.io.IOException;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import com.google.gson.Gson;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class ActionsTest {
@Test
public void testNullProcessActionInJsonIsConvertedToEmptyList() throws IOException {
// given:
String json = "{ \"processAction\": null, \"light\": [1], \"startTime\": [ [0, 0],[23,59] ] }";
// when:
Actions actions = new Gson().fromJson(json, Actions.class);
// then:
assertNotNull(actions.getProcessAction());
assertTrue(actions.getProcessAction().isEmpty());
}
@Test
public void testNullLightInJsonIsConvertedToEmptyList() throws IOException {
// given:
String json = "{ \"processAction\": [1], \"light\": null, \"startTime\": [ [0, 0],[23,59] ] }";
// when:
Actions actions = new Gson().fromJson(json, Actions.class);
// then:
assertNotNull(actions.getLight());
assertTrue(actions.getLight().isEmpty());
}
@Test
public void testNullStartTimeInJsonIsReturnedAsNull() throws IOException {
// given:
String json = "{ \"processAction\": [1], \"light\": [1], \"startTime\": null }";
// when:
Actions actions = new Gson().fromJson(json, Actions.class);
// then:
assertFalse(actions.getStartTime().isPresent());
}
@Test
public void testIdListIsEmptyWhenProgramIdFieldIsMissing() {
// given:
String json = "{ \"processAction\": [1] }";
// when:
Actions actions = new Gson().fromJson(json, Actions.class);
// then:
assertTrue(actions.getProgramId().isEmpty());
}
@Test
public void testIdListIsEmptyWhenProgramIdFieldIsNull() {
// given:
String json = "{ \"programId\": null }";
// when:
Actions actions = new Gson().fromJson(json, Actions.class);
// then:
assertTrue(actions.getProgramId().isEmpty());
}
@Test
public void testIdListContainsEntriesWhenProgramIdFieldIsPresent() {
// given:
String json = "{ \"programId\": [1,2,3,4] }";
// when:
Actions actions = new Gson().fromJson(json, Actions.class);
// then:
assertEquals(Arrays.asList(1, 2, 3, 4), actions.getProgramId());
}
}

View File

@@ -0,0 +1,290 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.api.json;
import static org.junit.jupiter.api.Assertions.*;
import static org.openhab.binding.mielecloud.internal.util.ResourceUtil.getResourceAsString;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
/**
* @author Björn Lange - Initial contribution
* @author Benjamin Bolte - Add plate step
*/
@NonNullByDefault
public class DeviceCollectionTest {
@Test
public void testCreateDeviceCollection() throws IOException {
// given:
String json = getResourceAsString(
"/org/openhab/binding/mielecloud/internal/webservice/api/json/deviceCollection.json");
// when:
DeviceCollection collection = DeviceCollection.fromJson(json);
// then:
assertEquals(1, collection.getDeviceIdentifiers().size());
Device device = collection.getDevice(collection.getDeviceIdentifiers().iterator().next());
Ident ident = device.getIdent().get();
Type type = ident.getType().get();
assertEquals("Devicetype", type.getKeyLocalized().get());
assertEquals(DeviceType.HOOD, type.getValueRaw());
assertEquals("Ventilation Hood", type.getValueLocalized().get());
assertEquals("My Hood", ident.getDeviceName().get());
DeviceIdentLabel deviceIdentLabel = ident.getDeviceIdentLabel().get();
assertEquals("000124430017", deviceIdentLabel.getFabNumber().get());
assertEquals("00", deviceIdentLabel.getFabIndex().get());
assertEquals("DA-6996", deviceIdentLabel.getTechType().get());
assertEquals("10101010", deviceIdentLabel.getMatNumber().get());
assertEquals(Arrays.asList("4164", "20380", "25226"), deviceIdentLabel.getSwids());
XkmIdentLabel xkmIdentLabel = ident.getXkmIdentLabel().get();
assertEquals("EK039W", xkmIdentLabel.getTechType().get());
assertEquals("02.31", xkmIdentLabel.getReleaseVersion().get());
State state = device.getState().get();
Status status = state.getStatus().get();
assertEquals(Integer.valueOf(StateType.RUNNING.getCode()), status.getValueRaw().get());
assertEquals("In use", status.getValueLocalized().get());
assertEquals("State", status.getKeyLocalized().get());
ProgramType programType = state.getProgramType().get();
assertEquals(Integer.valueOf(0), programType.getValueRaw().get());
assertEquals("", programType.getValueLocalized().get());
assertEquals("Programme", programType.getKeyLocalized().get());
ProgramPhase programPhase = state.getProgramPhase().get();
assertEquals(Integer.valueOf(4609), programPhase.getValueRaw().get());
assertEquals("", programPhase.getValueLocalized().get());
assertEquals("Phase", programPhase.getKeyLocalized().get());
assertEquals(Arrays.asList(0, 0), state.getRemainingTime().get());
assertEquals(Arrays.asList(0, 0), state.getStartTime().get());
assertEquals(1, state.getTargetTemperature().size());
Temperature targetTemperature = state.getTargetTemperature().get(0);
assertNotNull(targetTemperature);
assertEquals(Integer.valueOf(-32768), targetTemperature.getValueRaw().get());
assertFalse(targetTemperature.getValueLocalized().isPresent());
assertEquals("Celsius", targetTemperature.getUnit().get());
assertEquals(3, state.getTemperature().size());
Temperature temperature0 = state.getTemperature().get(0);
assertNotNull(temperature0);
assertEquals(Integer.valueOf(-32768), temperature0.getValueRaw().get());
assertFalse(temperature0.getValueLocalized().isPresent());
assertEquals("Celsius", temperature0.getUnit().get());
Temperature temperature1 = state.getTemperature().get(1);
assertNotNull(temperature1);
assertEquals(Integer.valueOf(-32768), temperature1.getValueRaw().get());
assertFalse(temperature1.getValueLocalized().isPresent());
assertEquals("Celsius", temperature1.getUnit().get());
Temperature temperature2 = state.getTemperature().get(2);
assertNotNull(temperature2);
assertEquals(Integer.valueOf(-32768), temperature2.getValueRaw().get());
assertFalse(temperature2.getValueLocalized().isPresent());
assertEquals("Celsius", temperature2.getUnit().get());
assertEquals(false, state.getSignalInfo().get());
assertEquals(false, state.getSignalFailure().get());
assertEquals(false, state.getSignalDoor().get());
RemoteEnable remoteEnable = state.getRemoteEnable().get();
assertEquals(false, remoteEnable.getFullRemoteControl().get());
assertEquals(false, remoteEnable.getSmartGrid().get());
assertEquals(Light.ENABLE, state.getLight());
assertEquals(new ArrayList<Object>(), state.getElapsedTime().get());
SpinningSpeed spinningSpeed = state.getSpinningSpeed().get();
assertEquals(Integer.valueOf(1200), spinningSpeed.getValueRaw().get());
assertEquals("1200", spinningSpeed.getValueLocalized().get());
assertEquals("rpm", spinningSpeed.getUnit().get());
DryingStep dryingStep = state.getDryingStep().get();
assertFalse(dryingStep.getValueRaw().isPresent());
assertEquals("", dryingStep.getValueLocalized().get());
assertEquals("Drying level", dryingStep.getKeyLocalized().get());
VentilationStep ventilationStep = state.getVentilationStep().get();
assertEquals(Integer.valueOf(2), ventilationStep.getValueRaw().get());
assertEquals("2", ventilationStep.getValueLocalized().get());
assertEquals("Power Level", ventilationStep.getKeyLocalized().get());
List<PlateStep> plateStep = state.getPlateStep();
assertEquals(4, plateStep.size());
assertEquals(Integer.valueOf(0), plateStep.get(0).getValueRaw().get());
assertEquals("0", plateStep.get(0).getValueLocalized().get());
assertEquals("Plate Step", plateStep.get(0).getKeyLocalized().get());
assertEquals(Integer.valueOf(1), plateStep.get(1).getValueRaw().get());
assertEquals("1", plateStep.get(1).getValueLocalized().get());
assertEquals("Plate Step", plateStep.get(1).getKeyLocalized().get());
assertEquals(Integer.valueOf(2), plateStep.get(2).getValueRaw().get());
assertEquals("1.", plateStep.get(2).getValueLocalized().get());
assertEquals("Plate Step", plateStep.get(2).getKeyLocalized().get());
assertEquals(Integer.valueOf(3), plateStep.get(3).getValueRaw().get());
assertEquals("2", plateStep.get(3).getValueLocalized().get());
assertEquals("Plate Step", plateStep.get(3).getKeyLocalized().get());
assertEquals(Integer.valueOf(20), state.getBatteryLevel().get());
}
@Test
public void testCreateDeviceCollectionFromInvalidJsonThrowsMieleSyntaxException() throws IOException {
// given:
String invalidJson = getResourceAsString(
"/org/openhab/binding/mielecloud/internal/webservice/api/json/invalidDeviceCollection.json");
// when:
assertThrows(MieleSyntaxException.class, () -> {
DeviceCollection.fromJson(invalidJson);
});
}
@Test
public void testCreateDeviceCollectionWithLargeProgramID() throws IOException {
// given:
String json = getResourceAsString(
"/org/openhab/binding/mielecloud/internal/webservice/api/json/deviceCollectionWithLargeProgramID.json");
// when:
DeviceCollection collection = DeviceCollection.fromJson(json);
// then:
assertEquals(1, collection.getDeviceIdentifiers().size());
Device device = collection.getDevice(collection.getDeviceIdentifiers().iterator().next());
Ident ident = device.getIdent().get();
Type type = ident.getType().get();
assertEquals("Devicetype", type.getKeyLocalized().get());
assertEquals(DeviceType.UNKNOWN, type.getValueRaw());
assertEquals("", type.getValueLocalized().get());
assertEquals("Some Devicename", ident.getDeviceName().get());
DeviceIdentLabel deviceIdentLabel = ident.getDeviceIdentLabel().get();
assertEquals("", deviceIdentLabel.getFabNumber().get());
assertEquals("", deviceIdentLabel.getFabIndex().get());
assertEquals("", deviceIdentLabel.getTechType().get());
assertEquals("", deviceIdentLabel.getMatNumber().get());
assertEquals(Arrays.asList(), deviceIdentLabel.getSwids());
XkmIdentLabel xkmIdentLabel = ident.getXkmIdentLabel().get();
assertEquals("", xkmIdentLabel.getTechType().get());
assertEquals("", xkmIdentLabel.getReleaseVersion().get());
State state = device.getState().get();
ProgramId programId = state.getProgramId().get();
assertEquals(Long.valueOf(2499805184L), programId.getValueRaw().get());
assertEquals("", programId.getValueLocalized().get());
assertEquals("Program Id", programId.getKeyLocalized().get());
Status status = state.getStatus().get();
assertEquals(Integer.valueOf(StateType.RUNNING.getCode()), status.getValueRaw().get());
assertEquals("In use", status.getValueLocalized().get());
assertEquals("State", status.getKeyLocalized().get());
ProgramType programType = state.getProgramType().get();
assertEquals(Integer.valueOf(0), programType.getValueRaw().get());
assertEquals("Operation mode", programType.getValueLocalized().get());
assertEquals("Program type", programType.getKeyLocalized().get());
ProgramPhase programPhase = state.getProgramPhase().get();
assertEquals(Integer.valueOf(0), programPhase.getValueRaw().get());
assertEquals("", programPhase.getValueLocalized().get());
assertEquals("Phase", programPhase.getKeyLocalized().get());
assertEquals(Arrays.asList(0, 0), state.getRemainingTime().get());
assertEquals(Arrays.asList(0, 0), state.getStartTime().get());
assertTrue(state.getTargetTemperature().isEmpty());
assertTrue(state.getTemperature().isEmpty());
assertEquals(false, state.getSignalInfo().get());
assertEquals(false, state.getSignalFailure().get());
assertEquals(false, state.getSignalDoor().get());
RemoteEnable remoteEnable = state.getRemoteEnable().get();
assertEquals(true, remoteEnable.getFullRemoteControl().get());
assertEquals(false, remoteEnable.getSmartGrid().get());
assertEquals(Light.NOT_SUPPORTED, state.getLight());
assertEquals(new ArrayList<Object>(), state.getElapsedTime().get());
DryingStep dryingStep = state.getDryingStep().get();
assertFalse(dryingStep.getValueRaw().isPresent());
assertEquals("", dryingStep.getValueLocalized().get());
assertEquals("Drying level", dryingStep.getKeyLocalized().get());
VentilationStep ventilationStep = state.getVentilationStep().get();
assertFalse(ventilationStep.getValueRaw().isPresent());
assertEquals("", ventilationStep.getValueLocalized().get());
assertEquals("Power Level", ventilationStep.getKeyLocalized().get());
List<PlateStep> plateStep = state.getPlateStep();
assertEquals(0, plateStep.size());
}
@Test
public void testCreateDeviceCollectionWithSpinningSpeedObject() throws IOException {
// given:
String json = getResourceAsString(
"/org/openhab/binding/mielecloud/internal/webservice/api/json/deviceCollectionWithSpinningSpeedObject.json");
// when:
DeviceCollection collection = DeviceCollection.fromJson(json);
// then:
assertEquals(1, collection.getDeviceIdentifiers().size());
Device device = collection.getDevice(collection.getDeviceIdentifiers().iterator().next());
State state = device.getState().get();
SpinningSpeed spinningSpeed = state.getSpinningSpeed().get();
assertNotNull(spinningSpeed);
assertEquals(Integer.valueOf(1600), spinningSpeed.getValueRaw().get());
assertEquals("1600", spinningSpeed.getValueLocalized().get());
assertEquals("U/min", spinningSpeed.getUnit().get());
}
@Test
public void testCreateDeviceCollectionWithFloatingPointTemperature() throws IOException {
// given:
String json = getResourceAsString(
"/org/openhab/binding/mielecloud/internal/webservice/api/json/deviceCollectionWithFloatingPointTargetTemperature.json");
// when:
DeviceCollection collection = DeviceCollection.fromJson(json);
// then:
assertEquals(1, collection.getDeviceIdentifiers().size());
Device device = collection.getDevice(collection.getDeviceIdentifiers().iterator().next());
State state = device.getState().get();
List<Temperature> targetTemperatures = state.getTargetTemperature();
assertEquals(1, targetTemperatures.size());
Temperature targetTemperature = targetTemperatures.get(0);
assertEquals(Integer.valueOf(80), targetTemperature.getValueRaw().get());
assertEquals(Integer.valueOf(0), targetTemperature.getValueLocalized().get());
assertEquals("Celsius", targetTemperature.getUnit().get());
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.api.json;
import static org.junit.jupiter.api.Assertions.*;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import com.google.gson.Gson;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class DeviceIdentLabelTest {
@Test
public void testNullSwidsInJsonAreConvertedToEmptyList() throws IOException {
// given:
String json = "{ \"swids\": null }";
// when:
DeviceIdentLabel deviceIdentLabel = new Gson().fromJson(json, DeviceIdentLabel.class);
// then:
assertNotNull(deviceIdentLabel.getSwids());
assertTrue(deviceIdentLabel.getSwids().isEmpty());
}
}

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.api.json;
import static org.junit.jupiter.api.Assertions.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class ErrorMessageTest {
@Test
public void testErrorMessageCanBeCreated() {
// given:
String json = "{\"message\": \"Unauthorized\"}";
// when:
ErrorMessage errorMessage = ErrorMessage.fromJson(json);
// then:
assertEquals("Unauthorized", errorMessage.getMessage().get());
}
@Test
public void testErrorMessageCreationThrowsMieleSyntaxExceptionWhenJsonIsInvalid() {
// given:
String json = "\"message\": \"Unauthorized}";
// when:
assertThrows(MieleSyntaxException.class, () -> {
ErrorMessage.fromJson(json);
});
}
}

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.api.json;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class LightTest {
@Test
public void testFromNullId() {
// when:
Light light = Light.fromId(null);
// then:
assertEquals(Light.UNKNOWN, light);
}
@Test
public void testFromNotSupportedId() {
// when:
Light light = Light.fromId(0);
// then:
assertEquals(Light.NOT_SUPPORTED, light);
}
@Test
public void testFromNotSupportedAlternativeId() {
// when:
Light light = Light.fromId(255);
// then:
assertEquals(Light.NOT_SUPPORTED, light);
}
@Test
public void testFromEnabledId() {
// when:
Light light = Light.fromId(1);
// then:
assertEquals(Light.ENABLE, light);
}
@Test
public void testFromDisabledId() {
// when:
Light light = Light.fromId(2);
// then:
assertEquals(Light.DISABLE, light);
}
}

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.api.json;
import static org.junit.jupiter.api.Assertions.*;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import com.google.gson.Gson;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class StateTest {
@Test
public void testNullRemainingTimeInJsonCausesRemainingTimeListToBeNull() throws IOException {
// given:
String json = "{ \"remainingTime\": null, \"startTime\": [0, 0], \"targetTemperature\": [{}], \"temperature\": [{}], \"elapsedTime\": [0, 0] }";
// when:
State state = new Gson().fromJson(json, State.class);
// then:
assertFalse(state.getRemainingTime().isPresent());
}
@Test
public void testNullStartTimeInJsonCausesStartTimeListToBeNull() throws IOException {
// given:
String json = "{ \"remainingTime\": [0, 0], \"startTime\": null, \"targetTemperature\": [{}], \"temperature\": [{}], \"elapsedTime\": [0, 0] }";
// when:
State state = new Gson().fromJson(json, State.class);
// then:
assertFalse(state.getStartTime().isPresent());
}
@Test
public void testNullElapsedTimeInJsonCausesElapsedTimeListToBeNull() throws IOException {
// given:
String json = "{ \"remainingTime\": [0, 0], \"startTime\": [0, 0], \"targetTemperature\": [{}], \"temperature\": [{}], \"elapsedTime\": null }";
// when:
State state = new Gson().fromJson(json, State.class);
// then:
assertFalse(state.getElapsedTime().isPresent());
}
@Test
public void testNullTargetTemperatureInJsonIsConvertedToEmptyList() throws IOException {
// given:
String json = "{ \"remainingTime\": [0, 0], \"startTime\": [0, 0], \"targetTemperature\": null, \"temperature\": [{}], \"elapsedTime\": [0, 0] }";
// when:
State state = new Gson().fromJson(json, State.class);
// then:
assertNotNull(state.getTargetTemperature());
assertTrue(state.getTargetTemperature().isEmpty());
}
@Test
public void testNullTemperatureInJsonIsConvertedToEmptyList() throws IOException {
// given:
String json = "{ \"remainingTime\": [0, 0], \"startTime\": [0, 0], \"targetTemperature\": [{}], \"temperature\": null, \"elapsedTime\": [0, 0] }";
// when:
State state = new Gson().fromJson(json, State.class);
// then:
assertNotNull(state.getTemperature());
assertTrue(state.getTemperature().isEmpty());
}
}

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.api.json;
import static org.junit.jupiter.api.Assertions.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import com.google.gson.Gson;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class StatusTest {
@Test
public void testParseStatusWithUnknownRawValue() {
// given:
String json = "{ \"key_localized\": \"State\", \"value_raw\": 99, \"value_localized\": \"Booting\" }";
// when:
Status status = new Gson().fromJson(json, Status.class);
// then:
assertNotNull(status);
assertEquals("State", status.getKeyLocalized().get());
assertEquals(Integer.valueOf(99), status.getValueRaw().get());
assertEquals("Booting", status.getValueLocalized().get());
}
@Test
public void testParseStatusWithKnownRawValue() {
// given:
String json = "{ \"key_localized\": \"State\", \"value_raw\": 1, \"value_localized\": \"Off\" }";
// when:
Status status = new Gson().fromJson(json, Status.class);
// then:
assertNotNull(status);
assertEquals("State", status.getKeyLocalized().get());
assertEquals(Integer.valueOf(StateType.OFF.getCode()), status.getValueRaw().get());
assertEquals("Off", status.getValueLocalized().get());
}
}

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.api.json;
import static org.junit.jupiter.api.Assertions.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import com.google.gson.Gson;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class TypeTest {
@Test
public void testParseTypeWithUnknownRawValue() {
// given:
String json = "{ \"key_localized\": \"Devicetype\", \"value_raw\": 99, \"value_localized\": \"Car Vaccuum Robot\" }";
// when:
Type type = new Gson().fromJson(json, Type.class);
// then:
assertNotNull(type);
assertEquals("Devicetype", type.getKeyLocalized().get());
assertEquals(DeviceType.UNKNOWN, type.getValueRaw());
assertEquals("Car Vaccuum Robot", type.getValueLocalized().get());
}
@Test
public void testParseTypeWithKnownRawValue() {
// given:
String json = "{ \"key_localized\": \"Devicetype\", \"value_raw\": 1, \"value_localized\": \"Washing Machine\" }";
// when:
Type type = new Gson().fromJson(json, Type.class);
// then:
assertNotNull(type);
assertEquals("Devicetype", type.getKeyLocalized().get());
assertEquals(DeviceType.WASHING_MACHINE, type.getValueRaw());
assertEquals("Washing Machine", type.getValueLocalized().get());
}
}

View File

@@ -0,0 +1,108 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.exception;
import static org.junit.jupiter.api.Assertions.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class TooManyRequestsExceptionTest {
@Test
public void testHasRetryAfterHintReturnsFalseWhenNoRetryAfterWasPassedToConstructor() {
// given:
TooManyRequestsException exception = new TooManyRequestsException("", null);
// when:
boolean result = exception.hasRetryAfterHint();
// then:
assertFalse(result);
}
@Test
public void testHasRetryAfterHintReturnsTrueWhenRetryAfterWasPassedToConstructor() {
// given:
TooManyRequestsException exception = new TooManyRequestsException("", "25");
// when:
boolean result = exception.hasRetryAfterHint();
// then:
assertTrue(result);
}
@Test
public void testGetSecondsUntilRetryReturnsMinusOneWhenNoRetryAfterHintIsPresent() {
// given:
TooManyRequestsException exception = new TooManyRequestsException("", null);
// when:
long result = exception.getSecondsUntilRetry();
// then:
assertEquals(-1L, result);
}
@Test
public void testGetSecondsUntilRetryParsesNumber() {
// given:
TooManyRequestsException exception = new TooManyRequestsException("", "30");
// when:
long result = exception.getSecondsUntilRetry();
// then:
assertEquals(30L, result);
}
@Test
public void testGetSecondsUntilRetryParsesDate() {
// given:
TooManyRequestsException exception = new TooManyRequestsException("", "Thu, 12 Jan 5015 15:02:30 GMT");
// when:
long result = exception.getSecondsUntilRetry();
// then:
assertNotEquals(0L, result);
}
@Test
public void testGetSecondsUntilRetryParsesDateFromThePast() {
// given:
TooManyRequestsException exception = new TooManyRequestsException("", "Wed, 21 Oct 2015 07:28:00 GMT");
// when:
long result = exception.getSecondsUntilRetry();
// then:
assertEquals(0L, result);
}
@Test
public void testGetSecondsUntilRetryReturnsMinusOneWhenDateCannotBeParsed() {
// given:
TooManyRequestsException exception = new TooManyRequestsException("", "50 Minutes");
// when:
long result = exception.getSecondsUntilRetry();
// then:
assertEquals(-1L, result);
}
}

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.language;
import static org.junit.jupiter.api.Assertions.*;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class CombiningLanguageProviderTest {
private static final Optional<String> PRIORITIZED_LANGUAGE = Optional.of("de");
private static final Optional<String> FALLBACK_LANGUAGE = Optional.of("en");
private static final LanguageProvider PRIORITIZED_PROVIDER = new LanguageProvider() {
@Override
public Optional<String> getLanguage() {
return PRIORITIZED_LANGUAGE;
}
};
private static final LanguageProvider FALLBACK_PROVIDER = new LanguageProvider() {
@Override
public Optional<String> getLanguage() {
return FALLBACK_LANGUAGE;
}
};
private static final LanguageProvider NULL_PROVIDER = new LanguageProvider() {
@Override
public Optional<String> getLanguage() {
return Optional.empty();
}
};
@Test
public void testPrioritizedLanguageProviderIsUsed() {
// given:
LanguageProvider provider = new CombiningLanguageProvider(PRIORITIZED_PROVIDER, FALLBACK_PROVIDER);
// when:
Optional<String> language = provider.getLanguage();
// then:
assertEquals(PRIORITIZED_LANGUAGE, language);
}
@Test
public void testFallbackProviderIsUsedWhenPrioritizedProviderIsNull() {
// given:
LanguageProvider provider = new CombiningLanguageProvider(null, FALLBACK_PROVIDER);
// when:
Optional<String> language = provider.getLanguage();
// then:
assertEquals(FALLBACK_LANGUAGE, language);
}
@Test
public void testFallbackProviderIsUsedWhenPrioritizedProviderProvidesNull() {
// given:
LanguageProvider provider = new CombiningLanguageProvider(NULL_PROVIDER, FALLBACK_PROVIDER);
// when:
Optional<String> language = provider.getLanguage();
// then:
assertEquals(FALLBACK_LANGUAGE, language);
}
@Test
public void testProvidesNullWhenBothProvidersAreNull() {
// given:
LanguageProvider provider = new CombiningLanguageProvider(null, null);
// when:
Optional<String> language = provider.getLanguage();
// then:
assertFalse(language.isPresent());
}
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.language;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;
import java.util.Locale;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.core.i18n.LocaleProvider;
/**
* @author Björn Lange - Initial Contribution
*/
@NonNullByDefault
public class OpenHabLanguageProviderTest {
@Test
public void whenTheLocaleIsSetToEnglishThenTheLanguageCodeIsEn() {
// given:
LocaleProvider localeProvider = mock(LocaleProvider.class);
when(localeProvider.getLocale()).thenReturn(Locale.ENGLISH);
LanguageProvider languageProvider = new OpenHabLanguageProvider(localeProvider);
// when:
Optional<String> language = languageProvider.getLanguage();
// then:
assertEquals(Optional.of("en"), language);
}
@Test
public void whenTheLocaleIsSetToGermanThenTheLanguageCodeIsDe() {
// given:
LocaleProvider localeProvider = mock(LocaleProvider.class);
when(localeProvider.getLocale()).thenReturn(Locale.GERMAN);
LanguageProvider languageProvider = new OpenHabLanguageProvider(localeProvider);
// when:
Optional<String> language = languageProvider.getLanguage();
// then:
assertEquals(Optional.of("de"), language);
}
@Test
public void whenTheLocaleIsSetToGermanyThenTheLanguageCodeIsDe() {
// given:
LocaleProvider localeProvider = mock(LocaleProvider.class);
when(localeProvider.getLocale()).thenReturn(Locale.GERMANY);
LanguageProvider languageProvider = new OpenHabLanguageProvider(localeProvider);
// when:
Optional<String> language = languageProvider.getLanguage();
// then:
assertEquals(Optional.of("de"), language);
}
}

View File

@@ -0,0 +1,203 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.retry;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.mielecloud.internal.MieleCloudBindingTestConstants;
import org.openhab.binding.mielecloud.internal.auth.OAuthException;
import org.openhab.binding.mielecloud.internal.auth.OAuthTokenRefresher;
import org.openhab.binding.mielecloud.internal.webservice.exception.AuthorizationFailedException;
import org.openhab.binding.mielecloud.internal.webservice.exception.MieleWebserviceException;
/**
* @author Björn Lange - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
@NonNullByDefault
public class AuthorizationFailedRetryStrategyTest {
private static final String TEST_STRING = "Some Test String";
@Mock
@Nullable
private Supplier<@Nullable String> operationWithReturnValue;
@Mock
@Nullable
private Consumer<Exception> onException;
@Mock
@Nullable
private Runnable operation;
private final OAuthTokenRefresher refresher = mock(OAuthTokenRefresher.class);
private Supplier<@Nullable String> getOperationWithReturnValue() {
assertNotNull(operationWithReturnValue);
return Objects.requireNonNull(operationWithReturnValue);
}
private Consumer<Exception> getOnException() {
assertNotNull(onException);
return Objects.requireNonNull(onException);
}
private Runnable getOperation() {
assertNotNull(operation);
return Objects.requireNonNull(operation);
}
@Test
public void testPerformRetryableOperationWithReturnValueInvokesOperation() {
// given:
when(getOperationWithReturnValue().get()).thenReturn(TEST_STRING);
AuthorizationFailedRetryStrategy retryStrategy = new AuthorizationFailedRetryStrategy(refresher,
MieleCloudBindingTestConstants.SERVICE_HANDLE);
// when:
String result = retryStrategy.performRetryableOperation(getOperationWithReturnValue(), getOnException());
// then:
assertEquals(TEST_STRING, result);
}
@Test
public void testPerformRetryableOperationWithReturnValueInvokesRefreshTokenAndRetriesOperation() {
// given:
when(getOperationWithReturnValue().get()).thenThrow(AuthorizationFailedException.class).thenReturn(TEST_STRING);
AuthorizationFailedRetryStrategy retryStrategy = new AuthorizationFailedRetryStrategy(refresher,
MieleCloudBindingTestConstants.SERVICE_HANDLE);
// when:
String result = retryStrategy.performRetryableOperation(getOperationWithReturnValue(), getOnException());
// then:
assertEquals(TEST_STRING, result);
verify(getOnException()).accept(any());
verify(refresher).refreshToken(MieleCloudBindingTestConstants.SERVICE_HANDLE);
verifyNoMoreInteractions(getOnException(), refresher);
}
@Test
public void testPerformRetryableOperationWithReturnValueThrowsMieleWebserviceExceptionWhenRetryingTheOperationFails() {
// given:
when(getOperationWithReturnValue().get()).thenThrow(AuthorizationFailedException.class);
AuthorizationFailedRetryStrategy retryStrategy = new AuthorizationFailedRetryStrategy(refresher,
MieleCloudBindingTestConstants.SERVICE_HANDLE);
assertThrows(MieleWebserviceException.class, () -> {
try {
// when:
retryStrategy.performRetryableOperation(getOperationWithReturnValue(), getOnException());
} catch (Exception e) {
// then:
verify(getOnException()).accept(any());
verify(refresher).refreshToken(MieleCloudBindingTestConstants.SERVICE_HANDLE);
verifyNoMoreInteractions(getOnException(), refresher);
throw e;
}
});
}
@Test
public void testPerformRetryableOperationInvokesOperation() {
// given:
AuthorizationFailedRetryStrategy retryStrategy = new AuthorizationFailedRetryStrategy(refresher,
MieleCloudBindingTestConstants.SERVICE_HANDLE);
// when:
retryStrategy.performRetryableOperation(getOperation(), getOnException());
// then:
verify(getOperation()).run();
verifyNoMoreInteractions(getOperation());
}
@Test
public void testPerformRetryableOperationInvokesRefreshTokenAndRetriesOperation() {
// given:
doThrow(AuthorizationFailedException.class).doNothing().when(getOperation()).run();
AuthorizationFailedRetryStrategy retryStrategy = new AuthorizationFailedRetryStrategy(refresher,
MieleCloudBindingTestConstants.SERVICE_HANDLE);
// when:
retryStrategy.performRetryableOperation(getOperation(), getOnException());
// then:
verify(getOnException()).accept(any());
verify(refresher).refreshToken(MieleCloudBindingTestConstants.SERVICE_HANDLE);
verify(getOperation(), times(2)).run();
verifyNoMoreInteractions(getOnException(), refresher, getOperation());
}
@Test
public void testPerformRetryableOperationThrowsMieleWebserviceExceptionWhenRetryingTheOperationFails() {
// given:
doThrow(AuthorizationFailedException.class).when(getOperation()).run();
AuthorizationFailedRetryStrategy retryStrategy = new AuthorizationFailedRetryStrategy(refresher,
MieleCloudBindingTestConstants.SERVICE_HANDLE);
assertThrows(MieleWebserviceException.class, () -> {
try {
// when:
retryStrategy.performRetryableOperation(getOperation(), getOnException());
} catch (Exception e) {
// then:
verify(getOnException()).accept(any());
verify(refresher).refreshToken(MieleCloudBindingTestConstants.SERVICE_HANDLE);
verify(getOperation(), times(2)).run();
verifyNoMoreInteractions(getOnException(), refresher, getOperation());
throw e;
}
});
}
@Test
public void testPerformRetryableOperationThrowsMieleWebserviceExceptionWhenTokenRefreshingFails() {
// given:
doThrow(AuthorizationFailedException.class).when(getOperation()).run();
doThrow(OAuthException.class).when(refresher).refreshToken(MieleCloudBindingTestConstants.SERVICE_HANDLE);
AuthorizationFailedRetryStrategy retryStrategy = new AuthorizationFailedRetryStrategy(refresher,
MieleCloudBindingTestConstants.SERVICE_HANDLE);
assertThrows(MieleWebserviceException.class, () -> {
try {
// when:
retryStrategy.performRetryableOperation(getOperation(), getOnException());
} catch (Exception e) {
// then:
verify(getOnException()).accept(any());
verify(refresher).refreshToken(MieleCloudBindingTestConstants.SERVICE_HANDLE);
verify(getOperation()).run();
verifyNoMoreInteractions(getOnException(), refresher, getOperation());
throw e;
}
});
}
}

View File

@@ -0,0 +1,220 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.retry;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.mielecloud.internal.webservice.exception.MieleWebserviceException;
import org.openhab.binding.mielecloud.internal.webservice.exception.MieleWebserviceTransientException;
/**
* @author Björn Lange - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
@NonNullByDefault
public class NTimesRetryStrategyTest {
private static final int SUCCESSFUL_RETURN_VALUE = 42;
@Mock
@Nullable
private Supplier<@Nullable Integer> operation;
@Mock
@Nullable
private Consumer<Exception> onTransientException;
private Supplier<@Nullable Integer> getOperation() {
assertNotNull(operation);
return Objects.requireNonNull(operation);
}
private Consumer<Exception> getOnTransientException() {
assertNotNull(onTransientException);
return Objects.requireNonNull(onTransientException);
}
@Test
public void testConstructorThrowsIllegalArgumentExceptionIfNumberOfRetriesIsSmallerThanZero() {
// when:
assertThrows(IllegalArgumentException.class, () -> {
new NTimesRetryStrategy(-1);
});
}
@Test
public void testSuccessfulOperationReturnsCorrectValue() {
// given:
when(getOperation().get()).thenReturn(SUCCESSFUL_RETURN_VALUE);
NTimesRetryStrategy retryStrategy = new NTimesRetryStrategy(1);
// when:
Integer result = retryStrategy.performRetryableOperation(getOperation(), getOnTransientException());
// then:
assertEquals(Integer.valueOf(SUCCESSFUL_RETURN_VALUE), result);
verifyNoMoreInteractions(onTransientException);
}
@Test
public void testFailingOperationReturnsCorrectValueOnRetry() {
// given:
when(getOperation().get()).thenThrow(MieleWebserviceTransientException.class)
.thenReturn(SUCCESSFUL_RETURN_VALUE);
NTimesRetryStrategy retryStrategy = new NTimesRetryStrategy(1);
// when:
Integer result = retryStrategy.performRetryableOperation(getOperation(), getOnTransientException());
// then:
assertEquals(Integer.valueOf(SUCCESSFUL_RETURN_VALUE), result);
verify(getOnTransientException()).accept(any());
verifyNoMoreInteractions(onTransientException);
}
@Test
public void testFailingOperationReturnsCorrectValueOnSecondRetry() {
// given:
when(getOperation().get()).thenThrow(MieleWebserviceTransientException.class)
.thenThrow(MieleWebserviceTransientException.class).thenReturn(SUCCESSFUL_RETURN_VALUE);
NTimesRetryStrategy retryStrategy = new NTimesRetryStrategy(2);
// when:
Integer result = retryStrategy.performRetryableOperation(getOperation(), getOnTransientException());
// then:
assertEquals(Integer.valueOf(SUCCESSFUL_RETURN_VALUE), result);
verify(getOnTransientException(), times(2)).accept(any());
verifyNoMoreInteractions(onTransientException);
}
@Test
public void testAlwaysFailingOperationThrowsMieleWebserviceException() {
// given:
when(getOperation().get()).thenThrow(MieleWebserviceTransientException.class);
NTimesRetryStrategy retryStrategy = new NTimesRetryStrategy(1);
// when:
try {
retryStrategy.performRetryableOperation(getOperation(), getOnTransientException());
fail();
return;
} catch (MieleWebserviceException e) {
}
// then:
verify(getOnTransientException()).accept(any());
verifyNoMoreInteractions(onTransientException);
}
@Test
public void testNullReturnValueDoesNotCauseMultipleRetries() {
// given:
when(getOperation().get()).thenReturn(null);
NTimesRetryStrategy retryStrategy = new NTimesRetryStrategy(1);
// when:
retryStrategy.performRetryableOperation(getOperation(), getOnTransientException());
// then:
verifyNoInteractions(getOnTransientException());
}
@Test
public void testSuccessfulOperation() {
// given:
Runnable operation = mock(Runnable.class);
doNothing().when(operation).run();
NTimesRetryStrategy retryStrategy = new NTimesRetryStrategy(1);
// when:
retryStrategy.performRetryableOperation(operation, getOnTransientException());
// then:
verify(operation).run();
verifyNoInteractions(getOnTransientException());
}
@Test
public void testFailingOperationCausesRetry() {
// given:
Runnable operation = mock(Runnable.class);
doThrow(MieleWebserviceTransientException.class).doNothing().when(operation).run();
NTimesRetryStrategy retryStrategy = new NTimesRetryStrategy(1);
// when:
retryStrategy.performRetryableOperation(operation, getOnTransientException());
// then:
verify(getOnTransientException()).accept(any());
verify(operation, times(2)).run();
verifyNoMoreInteractions(getOnTransientException());
}
@Test
public void testTwoTimesFailingOperationCausesTwoRetries() {
// given:
Runnable operation = mock(Runnable.class);
doThrow(MieleWebserviceTransientException.class).doThrow(MieleWebserviceTransientException.class).doNothing()
.when(operation).run();
NTimesRetryStrategy retryStrategy = new NTimesRetryStrategy(2);
// when:
retryStrategy.performRetryableOperation(operation, getOnTransientException());
// then:
verify(getOnTransientException(), times(2)).accept(any());
verify(operation, times(3)).run();
verifyNoMoreInteractions(getOnTransientException());
}
@Test
public void testAlwaysFailingRunnableOperationThrowsMieleWebserviceException() {
// given:
Runnable operation = mock(Runnable.class);
doThrow(MieleWebserviceTransientException.class).when(operation).run();
NTimesRetryStrategy retryStrategy = new NTimesRetryStrategy(1);
// when:
try {
retryStrategy.performRetryableOperation(operation, getOnTransientException());
fail();
return;
} catch (MieleWebserviceException e) {
}
// then:
verify(getOnTransientException()).accept(any());
verifyNoMoreInteractions(getOnTransientException());
}
}

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.retry;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.stubbing.Answer;
import org.openhab.binding.mielecloud.internal.util.MockUtil;
/**
* @author Björn Lange - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
@NonNullByDefault
public class RetryStrategyCombinerTest {
private static final String STRING_CONSTANT = "Some String";
private final RetryStrategy first = mock(RetryStrategy.class);
private final RetryStrategy second = mock(RetryStrategy.class);
@Mock
@Nullable
private Supplier<@Nullable String> supplier;
@Mock
@Nullable
private Consumer<Exception> consumer;
private Supplier<@Nullable String> getSupplier() {
assertNotNull(supplier);
return Objects.requireNonNull(supplier);
}
private Consumer<Exception> getConsumer() {
assertNotNull(consumer);
return Objects.requireNonNull(consumer);
}
@SuppressWarnings("unchecked")
@Test
public void testPerformRetryableOperationInvokesRetryStrategiesInCorrectOrder() {
// given:
when(first.<@Nullable String> performRetryableOperation(any(Supplier.class), any()))
.thenAnswer(new Answer<@Nullable String>() {
@Override
@Nullable
public String answer(@Nullable InvocationOnMock invocation) throws Throwable {
Supplier<String> inner = MockUtil.requireNonNull(invocation).getArgument(0);
return inner.get();
}
});
when(second.<@Nullable String> performRetryableOperation(any(Supplier.class), any()))
.thenAnswer(new Answer<@Nullable String>() {
@Override
@Nullable
public String answer(@Nullable InvocationOnMock invocation) throws Throwable {
Supplier<String> inner = MockUtil.requireNonNull(invocation).getArgument(0);
return inner.get();
}
});
when(getSupplier().get()).thenReturn(STRING_CONSTANT);
RetryStrategyCombiner combiner = new RetryStrategyCombiner(first, second);
// when:
String result = combiner.performRetryableOperation(getSupplier(), getConsumer());
// then:
assertEquals(STRING_CONSTANT, result);
verify(first).performRetryableOperation(any(Supplier.class), eq(getConsumer()));
verify(second).performRetryableOperation(any(Supplier.class), eq(getConsumer()));
verify(getSupplier()).get();
verifyNoMoreInteractions(first, second, getSupplier());
verifyNoInteractions(getConsumer());
}
}

View File

@@ -0,0 +1,202 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.sse;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.Random;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class ExponentialBackoffWithJitterTest {
private static final long RETRY_INTERVAL = 2;
private static final long ALTERNATIVE_RETRY_INTERVAL = 50;
private static final long MINIMUM_WAIT_TIME = 1;
private static final long ALTERNATIVE_MINIMUM_WAIT_TIME = 2;
private static final long MAXIMUM_WAIT_TIME = 100;
private static final long ALTERNATIVE_MAXIMUM_WAIT_TIME = 150;
@Test
public void whenMinimumWaitTimeIsSmallerThanZeroThenAnIllegalArgumentExceptionIsThrown() {
// when:
assertThrows(IllegalArgumentException.class, () -> {
new ExponentialBackoffWithJitter(-MINIMUM_WAIT_TIME, MAXIMUM_WAIT_TIME, RETRY_INTERVAL);
});
}
@Test
public void whenMaximumWaitTimeIsSmallerThanZeroThenAnIllegalArgumentExceptionIsThrown() {
// when:
assertThrows(IllegalArgumentException.class, () -> {
new ExponentialBackoffWithJitter(MINIMUM_WAIT_TIME, -MAXIMUM_WAIT_TIME, RETRY_INTERVAL);
});
}
@Test
public void whenRetryIntervalIsSmallerThanZeroThenAnIllegalArgumentExceptionIsThrown() {
// when:
assertThrows(IllegalArgumentException.class, () -> {
new ExponentialBackoffWithJitter(MINIMUM_WAIT_TIME, MAXIMUM_WAIT_TIME, -RETRY_INTERVAL);
});
}
@Test
public void whenMinimumWaitTimeIsLargerThanMaximumWaitTimeThenAnIllegalArgumentExceptionIsThrown() {
// when:
assertThrows(IllegalArgumentException.class, () -> {
new ExponentialBackoffWithJitter(MAXIMUM_WAIT_TIME, MINIMUM_WAIT_TIME, RETRY_INTERVAL);
});
}
@Test
public void whenRetryIntervalIsLargerThanMaximumWaitTimeThenAnIllegalArgumentExceptionIsThrown() {
// when:
assertThrows(IllegalArgumentException.class, () -> {
new ExponentialBackoffWithJitter(MINIMUM_WAIT_TIME, RETRY_INTERVAL, MAXIMUM_WAIT_TIME);
});
}
@Test
public void whenTheNumberOfFailedAttemptsIsNegativeThenZeroIsAssumedInstead() {
// given:
Random random = mock(Random.class);
when(random.nextLong()).thenReturn(RETRY_INTERVAL);
ExponentialBackoffWithJitter backoffStrategy = new ExponentialBackoffWithJitter(MINIMUM_WAIT_TIME,
MAXIMUM_WAIT_TIME, RETRY_INTERVAL, random);
// when:
long result = backoffStrategy.getSecondsUntilRetry(-10);
// then:
assertEquals(MINIMUM_WAIT_TIME + RETRY_INTERVAL, result);
}
@Test
public void whenThereIsNoFailedAttemptThenTheMaximalResultIsMinimumWaitTimePlusRetryInterval() {
// given:
Random random = mock(Random.class);
when(random.nextLong()).thenReturn(RETRY_INTERVAL);
ExponentialBackoffWithJitter backoffStrategy = new ExponentialBackoffWithJitter(MINIMUM_WAIT_TIME,
MAXIMUM_WAIT_TIME, RETRY_INTERVAL, random);
// when:
long result = backoffStrategy.getSecondsUntilRetry(0);
// then:
assertEquals(MINIMUM_WAIT_TIME + RETRY_INTERVAL, result);
}
@Test
public void whenThereIsOneFailedAttemptThenTheMaximalResultIsMinimumWaitTimePlusTwiceTheRetryInterval() {
// given:
Random random = mock(Random.class);
when(random.nextLong()).thenReturn(RETRY_INTERVAL * 2);
ExponentialBackoffWithJitter backoffStrategy = new ExponentialBackoffWithJitter(MINIMUM_WAIT_TIME,
MAXIMUM_WAIT_TIME, RETRY_INTERVAL, random);
// when:
long result = backoffStrategy.getSecondsUntilRetry(1);
// then:
assertEquals(MINIMUM_WAIT_TIME + RETRY_INTERVAL * 2, result);
}
@Test
public void whenThereAreTwoFailedAttemptsThenTheMaximalResultIsMinimumWaitTimePlusFourTimesTheRetryInterval() {
// given:
Random random = mock(Random.class);
when(random.nextLong()).thenReturn(RETRY_INTERVAL * 4);
ExponentialBackoffWithJitter backoffStrategy = new ExponentialBackoffWithJitter(MINIMUM_WAIT_TIME,
MAXIMUM_WAIT_TIME, RETRY_INTERVAL, random);
// when:
long result = backoffStrategy.getSecondsUntilRetry(2);
// then:
assertEquals(MINIMUM_WAIT_TIME + RETRY_INTERVAL * 4, result);
}
@Test
public void whenThereAreTwoFailedAttemptsThenTheMinimalResultIsTheMinimumWaitTime() {
// given:
Random random = mock(Random.class);
when(random.nextLong()).thenReturn(0L);
ExponentialBackoffWithJitter backoffStrategy = new ExponentialBackoffWithJitter(MINIMUM_WAIT_TIME,
MAXIMUM_WAIT_TIME, RETRY_INTERVAL, random);
// when:
long result = backoffStrategy.getSecondsUntilRetry(2);
// then:
assertEquals(MINIMUM_WAIT_TIME, result);
}
@Test
public void whenTheDrawnRandomValueIsNegativeThenItIsProjectedToAPositiveValue() {
// given:
Random random = mock(Random.class);
when(random.nextLong()).thenReturn(-RETRY_INTERVAL * 4 - 1);
ExponentialBackoffWithJitter backoffStrategy = new ExponentialBackoffWithJitter(MINIMUM_WAIT_TIME,
MAXIMUM_WAIT_TIME, RETRY_INTERVAL, random);
// when:
long result = backoffStrategy.getSecondsUntilRetry(2);
// then:
assertEquals(MINIMUM_WAIT_TIME, result);
}
@Test
public void whenTheResultWouldBeLargerThanTheMaximumThenItIsCappedToTheMaximum() {
// given:
Random random = mock(Random.class);
when(random.nextLong()).thenReturn(MAXIMUM_WAIT_TIME - ALTERNATIVE_MINIMUM_WAIT_TIME);
ExponentialBackoffWithJitter backoffStrategy = new ExponentialBackoffWithJitter(ALTERNATIVE_MINIMUM_WAIT_TIME,
MAXIMUM_WAIT_TIME, ALTERNATIVE_RETRY_INTERVAL, random);
// when:
long result = backoffStrategy.getSecondsUntilRetry(2);
// then:
assertEquals(MAXIMUM_WAIT_TIME, result);
}
@Test
public void whenTheResultWouldBeLargerThanTheAlternativeMaximumThenItIsCappedToTheAlternativeMaximum() {
// given:
Random random = mock(Random.class);
when(random.nextLong()).thenReturn(ALTERNATIVE_MAXIMUM_WAIT_TIME - ALTERNATIVE_MINIMUM_WAIT_TIME);
ExponentialBackoffWithJitter backoffStrategy = new ExponentialBackoffWithJitter(ALTERNATIVE_MINIMUM_WAIT_TIME,
ALTERNATIVE_MAXIMUM_WAIT_TIME, ALTERNATIVE_RETRY_INTERVAL, random);
// when:
long result = backoffStrategy.getSecondsUntilRetry(2);
// then:
assertEquals(ALTERNATIVE_MAXIMUM_WAIT_TIME, result);
}
}

View File

@@ -0,0 +1,600 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.sse;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.openhab.binding.mielecloud.internal.util.ReflectionUtil.*;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
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;
import org.eclipse.jetty.client.api.Response.HeadersListener;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http.HttpFields;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.openhab.binding.mielecloud.internal.webservice.ConnectionError;
import org.openhab.binding.mielecloud.internal.webservice.exception.MieleWebserviceDisconnectSseException;
import org.openhab.binding.mielecloud.internal.webservice.retry.AuthorizationFailedRetryStrategy;
/**
* @author Björn Lange - Initial contribution
*/
@NonNullByDefault
public class SseConnectionTest {
private final String URL = "https://openhab.org/";
@Nullable
private Request request;
@Nullable
private SseRequestFactory sseRequestFactory;
@Nullable
private ScheduledExecutorService scheduler;
@Nullable
private BackoffStrategy backoffStrategy;
@Nullable
private SseListener sseListener;
@Nullable
private SseConnection sseConnection;
@Nullable
private HeadersListener registeredHeadersListener;
@Nullable
private CompleteListener registeredCompleteListener;
private SseRequestFactory mockSseRequestFactory(@Nullable Request request) {
SseRequestFactory factory = mock(SseRequestFactory.class);
when(factory.createSseRequest(URL)).thenReturn(request);
return factory;
}
private ScheduledExecutorService mockScheduler() {
return mock(ScheduledExecutorService.class);
}
private Request mockRequest() {
Request request = mock(Request.class);
when(request.onResponseHeaders(any())).thenAnswer(invocation -> {
registeredHeadersListener = invocation.getArgument(0);
return request;
});
when(request.onComplete(any())).thenAnswer(invocation -> {
registeredCompleteListener = invocation.getArgument(0);
return request;
});
when(request.idleTimeout(anyLong(), any())).thenReturn(request);
when(request.timeout(anyLong(), any())).thenReturn(request);
return request;
}
private BackoffStrategy mockBackoffStrategy() {
BackoffStrategy backoffStrategy = mock(BackoffStrategy.class);
when(backoffStrategy.getSecondsUntilRetry(anyInt())).thenReturn(10L);
when(backoffStrategy.getMinimumSecondsUntilRetry()).thenReturn(5L);
when(backoffStrategy.getMaximumSecondsUntilRetry()).thenReturn(3600L);
return backoffStrategy;
}
private void setUpRunningConnection() {
request = mockRequest();
sseRequestFactory = mockSseRequestFactory(request);
scheduler = mockScheduler();
backoffStrategy = mockBackoffStrategy();
sseConnection = new SseConnection(URL, getMockedSseRequestFactory(), getMockedScheduler(),
getMockedBackoffStrategy());
sseListener = mock(SseListener.class);
getSseConnection().addSseListener(getMockedSseListener());
getSseConnection().connect();
getRegisteredHeadersListener().onHeaders(null);
}
private Request getMockedRequest() {
Request request = this.request;
assertNotNull(request);
return Objects.requireNonNull(request);
}
private SseRequestFactory getMockedSseRequestFactory() {
SseRequestFactory sseRequestFactory = this.sseRequestFactory;
assertNotNull(sseRequestFactory);
return Objects.requireNonNull(sseRequestFactory);
}
private ScheduledExecutorService getMockedScheduler() {
ScheduledExecutorService scheduler = this.scheduler;
assertNotNull(scheduler);
return Objects.requireNonNull(scheduler);
}
private BackoffStrategy getMockedBackoffStrategy() {
BackoffStrategy backoffStrategy = this.backoffStrategy;
assertNotNull(backoffStrategy);
return Objects.requireNonNull(backoffStrategy);
}
private SseListener getMockedSseListener() {
SseListener sseListener = this.sseListener;
assertNotNull(sseListener);
return Objects.requireNonNull(sseListener);
}
private SseConnection getSseConnection() {
SseConnection sseConnection = this.sseConnection;
assertNotNull(sseConnection);
return Objects.requireNonNull(sseConnection);
}
private HeadersListener getRegisteredHeadersListener() {
HeadersListener headersListener = registeredHeadersListener;
assertNotNull(headersListener);
return Objects.requireNonNull(headersListener);
}
private CompleteListener getRegisteredCompleteListener() {
CompleteListener completeListener = registeredCompleteListener;
assertNotNull(completeListener);
return Objects.requireNonNull(completeListener);
}
@Test
public void whenSseConnectionIsConnectedThenTheConnectionRequestIsMade() throws Exception {
// given:
Request request = mockRequest();
SseRequestFactory sseRequestFactory = mockSseRequestFactory(request);
ScheduledExecutorService scheduler = mockScheduler();
SseConnection sseConnection = new SseConnection(URL, sseRequestFactory, scheduler);
// when:
sseConnection.connect();
// then:
verify(request).send(any());
}
@Test
public void whenSseConnectionIsConnectedButNoRequestIsCreatedThenOnlyTheDesiredConnectionStateChanges()
throws Exception {
// given:
SseRequestFactory sseRequestFactory = mockSseRequestFactory(null);
ScheduledExecutorService scheduler = mockScheduler();
SseConnection sseConnection = new SseConnection(URL, sseRequestFactory, scheduler);
// when:
sseConnection.connect();
// then:
assertTrue(((Boolean) getPrivate(sseConnection, "active")).booleanValue());
}
@Test
public void whenHeadersAreReceivedAfterTheSseConnectionWasConnectedThenTheEventStreamParserIsScheduled()
throws Exception {
// given:
Request request = mockRequest();
SseRequestFactory sseRequestFactory = mockSseRequestFactory(request);
ScheduledExecutorService scheduler = mockScheduler();
SseConnection sseConnection = new SseConnection(URL, sseRequestFactory, scheduler);
sseConnection.connect();
HeadersListener headersListener = registeredHeadersListener;
assertNotNull(headersListener);
// when:
headersListener.onHeaders(null);
// then:
verify(scheduler).schedule(ArgumentMatchers.<Runnable> any(), anyLong(), any());
}
@Test
public void whenTheSseStreamIsClosedWithATimeoutThenAReconnectIsScheduledAndTheListenersAreNotified()
throws Exception {
// given:
setUpRunningConnection();
// when:
invokePrivate(getSseConnection(), "onSseStreamClosed", new Class[] { Throwable.class }, new TimeoutException());
// then:
verify(getMockedScheduler(), times(2)).schedule(ArgumentMatchers.<Runnable> any(), anyLong(), any());
verify(getMockedSseListener()).onConnectionError(ConnectionError.TIMEOUT, 0);
verify(getMockedBackoffStrategy()).getSecondsUntilRetry(anyInt());
}
@Test
public void whenTheSseStreamIsClosedDueToAJetty401ErrorThenNoReconnectIsScheduledAndATokenRefreshIsRequested()
throws Exception {
// given:
setUpRunningConnection();
// when:
invokePrivate(getSseConnection(), "onSseStreamClosed", new Class[] { Throwable.class }, new RuntimeException(
AuthorizationFailedRetryStrategy.JETTY_401_HEADER_BODY_MISMATCH_EXCEPTION_MESSAGE));
// then:
verify(getMockedScheduler()).schedule(ArgumentMatchers.<Runnable> any(), anyLong(), any());
verifyNoMoreInteractions(getMockedScheduler());
verify(getMockedSseListener()).onConnectionError(ConnectionError.AUTHORIZATION_FAILED, 0);
}
@Test
public void whenTheSseStreamIsClosedWithADifferentExceptionThanATimeoutThenAReconnectIsScheduledAndTheListenersAreNotified()
throws Exception {
// given:
setUpRunningConnection();
// when:
invokePrivate(getSseConnection(), "onSseStreamClosed", new Class[] { Throwable.class },
new IllegalStateException());
// then:
verify(getMockedScheduler(), times(2)).schedule(ArgumentMatchers.<Runnable> any(), anyLong(), any());
verify(getMockedSseListener()).onConnectionError(ConnectionError.SSE_STREAM_ENDED, 0);
verify(getMockedBackoffStrategy()).getSecondsUntilRetry(anyInt());
}
@Test
public void whenTheSseRequestCompletesWithoutResultThenAReconnectIsScheduledAndTheListenersAreNotified()
throws Exception {
// given:
setUpRunningConnection();
// when:
getRegisteredCompleteListener().onComplete(null);
// then:
verify(getMockedScheduler(), times(2)).schedule(ArgumentMatchers.<Runnable> any(), anyLong(), any());
verify(getMockedSseListener()).onConnectionError(ConnectionError.SSE_STREAM_ENDED, 0);
verify(getMockedBackoffStrategy()).getSecondsUntilRetry(anyInt());
}
@Test
public void whenTheSseRequestCompletesWithoutResponseThenAReconnectIsScheduledAndTheListenersAreNotified()
throws Exception {
// given:
setUpRunningConnection();
Result result = mock(Result.class);
// when:
getRegisteredCompleteListener().onComplete(result);
// then:
verify(getMockedScheduler(), times(2)).schedule(ArgumentMatchers.<Runnable> any(), anyLong(), any());
verify(getMockedSseListener()).onConnectionError(ConnectionError.SSE_STREAM_ENDED, 0);
verify(getMockedBackoffStrategy()).getSecondsUntilRetry(anyInt());
}
@Test
public void whenTheSseRequestCompletesWithASuccessfulResponseThenAReconnectIsScheduledAndTheListenersAreNotified()
throws Exception {
// given:
setUpRunningConnection();
Response response = mock(Response.class);
when(response.getStatus()).thenReturn(200);
Result result = mock(Result.class);
when(result.getResponse()).thenReturn(response);
// when:
getRegisteredCompleteListener().onComplete(result);
// then:
verify(getMockedScheduler(), times(2)).schedule(ArgumentMatchers.<Runnable> any(), anyLong(), any());
verify(getMockedSseListener()).onConnectionError(ConnectionError.SSE_STREAM_ENDED, 0);
verify(getMockedBackoffStrategy()).getSecondsUntilRetry(anyInt());
}
@Test
public void whenTheSseRequestCompletesWithAnAuthorizationFailedResponseThenTheListenersAreNotified()
throws Exception {
// given:
setUpRunningConnection();
Response response = mock(Response.class);
when(response.getStatus()).thenReturn(401);
Result result = mock(Result.class);
when(result.getResponse()).thenReturn(response);
// when:
getRegisteredCompleteListener().onComplete(result);
// then:
verify(getMockedScheduler()).schedule(ArgumentMatchers.<Runnable> any(), anyLong(), any());
verify(getMockedSseListener()).onConnectionError(ConnectionError.AUTHORIZATION_FAILED, 0);
}
@Test
public void whenTheSseRequestCompletesWithATooManyRequestsResponseWithoutRetryAfterHeaderThenAReconnectIsScheduledAccordingToTheBackoffStrategyAndTheListenersAreNotified()
throws Exception {
// given:
setUpRunningConnection();
Response response = mock(Response.class);
when(response.getStatus()).thenReturn(429);
when(response.getHeaders()).thenReturn(new HttpFields());
Result result = mock(Result.class);
when(result.getResponse()).thenReturn(response);
// when:
getRegisteredCompleteListener().onComplete(result);
// then:
verify(getMockedScheduler(), times(2)).schedule(ArgumentMatchers.<Runnable> any(), anyLong(), any());
verify(getMockedScheduler()).schedule(ArgumentMatchers.<Runnable> any(), eq(10L), eq(TimeUnit.SECONDS));
verify(getMockedSseListener()).onConnectionError(ConnectionError.TOO_MANY_RERQUESTS, 0);
verify(getMockedBackoffStrategy()).getSecondsUntilRetry(anyInt());
}
@Test
public void whenTheSseRequestCompletesWithATooManyRequestsResponseWithRetryAfterHeaderThenAReconnectIsScheduledAndTheListenersAreNotified()
throws Exception {
// given:
setUpRunningConnection();
Response response = mock(Response.class);
when(response.getStatus()).thenReturn(429);
HttpFields httpFields = new HttpFields();
httpFields.add("Retry-After", "3600");
when(response.getHeaders()).thenReturn(httpFields);
Result result = mock(Result.class);
when(result.getResponse()).thenReturn(response);
// when:
getRegisteredCompleteListener().onComplete(result);
// then:
verify(getMockedScheduler()).schedule(ArgumentMatchers.<Runnable> any(), eq(3600L), eq(TimeUnit.SECONDS));
verify(getMockedSseListener()).onConnectionError(ConnectionError.TOO_MANY_RERQUESTS, 0);
}
@Test
public void whenTheSseRequestCompletesWithATooManyRequestsResponseWithRetryAfterHeaderWithTooLowValueThenAReconnectIsScheduledWithTheMinimumWaitTime()
throws Exception {
// given:
setUpRunningConnection();
Response response = mock(Response.class);
when(response.getStatus()).thenReturn(429);
HttpFields httpFields = new HttpFields();
httpFields.add("Retry-After", "1");
when(response.getHeaders()).thenReturn(httpFields);
Result result = mock(Result.class);
when(result.getResponse()).thenReturn(response);
// when:
getRegisteredCompleteListener().onComplete(result);
// then:
verify(getMockedScheduler()).schedule(ArgumentMatchers.<Runnable> any(), eq(5L), eq(TimeUnit.SECONDS));
verify(getMockedSseListener()).onConnectionError(ConnectionError.TOO_MANY_RERQUESTS, 0);
}
@Test
public void whenTheSseRequestCompletesWithATooManyRequestsResponseWithRetryAfterHeaderWithTooHighValueThenAReconnectIsScheduledWithTheMaximumWaitTime()
throws Exception {
// given:
setUpRunningConnection();
Response response = mock(Response.class);
when(response.getStatus()).thenReturn(429);
HttpFields httpFields = new HttpFields();
httpFields.add("Retry-After", "3601");
when(response.getHeaders()).thenReturn(httpFields);
Result result = mock(Result.class);
when(result.getResponse()).thenReturn(response);
// when:
getRegisteredCompleteListener().onComplete(result);
// then:
verify(getMockedScheduler()).schedule(ArgumentMatchers.<Runnable> any(), eq(3600L), eq(TimeUnit.SECONDS));
verify(getMockedSseListener()).onConnectionError(ConnectionError.TOO_MANY_RERQUESTS, 0);
}
@Test
public void whenTheSseRequestCompletesWithAnInternalServerErrorResponseThenAReconnectIsScheduledAndTheListenersAreNotified()
throws Exception {
// given:
setUpRunningConnection();
Response response = mock(Response.class);
when(response.getStatus()).thenReturn(500);
Result result = mock(Result.class);
when(result.getResponse()).thenReturn(response);
// when:
getRegisteredCompleteListener().onComplete(result);
// then:
verify(getMockedScheduler(), times(2)).schedule(ArgumentMatchers.<Runnable> any(), anyLong(), any());
verify(getMockedSseListener()).onConnectionError(ConnectionError.SERVER_ERROR, 0);
}
@Test
public void whenTheSseRequestCompletesWithAnInternalServerErrorResponseMultipleTimesThenTheConnectionFailedCounterIsIncrementedEachTime()
throws Exception {
// given:
setUpRunningConnection();
Response response = mock(Response.class);
when(response.getStatus()).thenReturn(500);
Result result = mock(Result.class);
when(result.getResponse()).thenReturn(response);
// when:
getRegisteredCompleteListener().onComplete(result);
getRegisteredCompleteListener().onComplete(result);
// then:
verify(getMockedSseListener()).onConnectionError(ConnectionError.SERVER_ERROR, 0);
verify(getMockedSseListener()).onConnectionError(ConnectionError.SERVER_ERROR, 1);
}
@Test
public void whenTheSseRequestCompletesWithAnUnknownErrorResponseThenAReconnectIsScheduledAndTheListenersAreNotified()
throws Exception {
// given:
setUpRunningConnection();
Response response = mock(Response.class);
when(response.getStatus()).thenReturn(600);
Result result = mock(Result.class);
when(result.getResponse()).thenReturn(response);
// when:
getRegisteredCompleteListener().onComplete(result);
// then:
verify(getMockedScheduler(), times(2)).schedule(ArgumentMatchers.<Runnable> any(), anyLong(), any());
verify(getMockedSseListener()).onConnectionError(ConnectionError.OTHER_HTTP_ERROR, 0);
verify(getMockedBackoffStrategy()).getSecondsUntilRetry(anyInt());
}
@Test
public void whenAServerSentEventIsReceivedThenItIsForwardedToTheListenersAndTheFailedConnectionCounterIsReset()
throws Exception {
// given:
Request request = mockRequest();
SseRequestFactory sseRequestFactory = mockSseRequestFactory(request);
ScheduledExecutorService scheduler = mockScheduler();
BackoffStrategy backoffStrategy = mock(BackoffStrategy.class);
when(backoffStrategy.getSecondsUntilRetry(anyInt())).thenReturn(10L);
SseConnection sseConnection = new SseConnection(URL, sseRequestFactory, scheduler, backoffStrategy);
SseListener sseListener = mock(SseListener.class);
sseConnection.addSseListener(sseListener);
setPrivate(sseConnection, "failedConnectionAttempts", 10);
sseConnection.connect();
HeadersListener headersListener = registeredHeadersListener;
assertNotNull(headersListener);
headersListener.onHeaders(null);
ServerSentEvent serverSentEvent = new ServerSentEvent("ping", "ping");
// when:
invokePrivate(sseConnection, "onServerSentEvent", serverSentEvent);
// then:
verify(sseListener).onServerSentEvent(serverSentEvent);
assertEquals(0, (int) getPrivate(sseConnection, "failedConnectionAttempts"));
}
@Test
public void whenTheSseStreamIsDisconnectedThenTheRunningRequestIsAborted() throws Exception {
// given:
setUpRunningConnection();
// when:
getSseConnection().disconnect();
// then:
verify(getMockedRequest()).abort(any());
assertNull(getPrivate(getSseConnection(), "sseRequest"));
}
@Test
public void whenTheSseStreamIsDisconnectedThenTheConnectionIsClosedAndNoReconnectIsScheduledAndTheListenersAreNotNotified()
throws Exception {
// given:
setUpRunningConnection();
// when:
getSseConnection().disconnect();
invokePrivate(getSseConnection(), "onSseStreamClosed", new Class[] { Throwable.class },
new MieleWebserviceDisconnectSseException());
// then:
verify(getMockedScheduler()).schedule(ArgumentMatchers.<Runnable> any(), anyLong(), any());
verifyNoMoreInteractions(getMockedScheduler());
verifyNoInteractions(getMockedSseListener());
}
@Test
public void whenAPendingReconnectAttemptIsPerformedAfterTheSseConnectionWasDisconnectedThenTheConnectionIsNotRestored()
throws Exception {
// given:
setUpRunningConnection();
getSseConnection().disconnect();
// when:
invokePrivate(getSseConnection(), "connectInternal");
// then:
verify(getMockedScheduler()).schedule(ArgumentMatchers.<Runnable> any(), anyLong(), any());
verifyNoMoreInteractions(getMockedScheduler());
verifyNoInteractions(getMockedSseListener());
}
@Test
public void whenTheSseConnectionIsConnectedMultipleTimesWithoutDisconnectingThenOnlyTheFirstConnectResultsInAnConnectionAttempt()
throws Exception {
// given:
Request request = mockRequest();
SseRequestFactory sseRequestFactory = mockSseRequestFactory(request);
ScheduledExecutorService scheduler = mockScheduler();
SseConnection sseConnection = new SseConnection(URL, sseRequestFactory, scheduler);
sseConnection.connect();
// when:
sseConnection.connect();
// then:
verify(request, times(1)).onResponseHeaders(any());
}
@Test
public void whenTheSseConnectionIsDisconnectedMultipleTimesWithoutConnectingAgainThenOnlyTheFirstDisconnectIsPerformed()
throws Exception {
// given:
Request request = mockRequest();
SseRequestFactory sseRequestFactory = mockSseRequestFactory(request);
ScheduledExecutorService scheduler = mockScheduler();
SseConnection sseConnection = new SseConnection(URL, sseRequestFactory, scheduler);
sseConnection.connect();
sseConnection.disconnect();
// when:
sseConnection.disconnect();
// then:
verify(request, times(1)).abort(any());
}
}

View File

@@ -0,0 +1,182 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mielecloud.internal.webservice.sse;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.mielecloud.internal.webservice.exception.MieleWebserviceDisconnectSseException;
/**
* @author Björn Lange - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
@NonNullByDefault
public class SseStreamParserTest {
@Mock
@NonNullByDefault({})
private Consumer<ServerSentEvent> serverSentEventCallback;
@Mock
@NonNullByDefault({})
private Consumer<@Nullable Throwable> streamClosedCallback;
private InputStream getInputStreamReadingUtf8Data(String data) {
return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
}
@Test
public void whenNoEventIsProvidedThenTheStreamClosedCallbackIsInvoked() {
// given:
InputStream inputStream = getInputStreamReadingUtf8Data("");
SseStreamParser parser = new SseStreamParser(inputStream, serverSentEventCallback, streamClosedCallback);
// when:
parser.parseAndDispatchEvents();
// then:
verify(streamClosedCallback).accept(null);
verifyNoMoreInteractions(streamClosedCallback);
verifyNoInteractions(serverSentEventCallback);
}
@Test
public void whenNoEventAndOnlyWhitespaceIsProvidedThenTheStreamClosedCallbackIsInvoked() {
// given:
InputStream inputStream = getInputStreamReadingUtf8Data("\r\n");
SseStreamParser parser = new SseStreamParser(inputStream, serverSentEventCallback, streamClosedCallback);
// when:
parser.parseAndDispatchEvents();
// then:
verify(streamClosedCallback).accept(null);
verifyNoMoreInteractions(streamClosedCallback);
verifyNoInteractions(serverSentEventCallback);
}
@Test
public void whenAnEventIsProvidedThenItIsPassedToTheCallback() {
// given:
InputStream inputStream = getInputStreamReadingUtf8Data("event: ping\r\ndata: pong\r\n");
SseStreamParser parser = new SseStreamParser(inputStream, serverSentEventCallback, streamClosedCallback);
// when:
parser.parseAndDispatchEvents();
// then:
verify(streamClosedCallback).accept(null);
verify(serverSentEventCallback).accept(new ServerSentEvent("ping", "pong"));
verifyNoMoreInteractions(streamClosedCallback, serverSentEventCallback);
}
@Test
public void whenALineWithInvalidKeyIsProvidedThenItIsIgnored() {
// given:
InputStream inputStream = getInputStreamReadingUtf8Data("name: ping\r\n");
SseStreamParser parser = new SseStreamParser(inputStream, serverSentEventCallback, streamClosedCallback);
// when:
parser.parseAndDispatchEvents();
// then:
verify(streamClosedCallback).accept(null);
verifyNoMoreInteractions(streamClosedCallback);
verifyNoInteractions(serverSentEventCallback);
}
@Test
public void whenDataWithoutEventIsProvidedThenItIsIgnored() {
// given:
InputStream inputStream = getInputStreamReadingUtf8Data("data: ping\r\n");
SseStreamParser parser = new SseStreamParser(inputStream, serverSentEventCallback, streamClosedCallback);
// when:
parser.parseAndDispatchEvents();
// then:
verify(streamClosedCallback).accept(null);
verifyNoMoreInteractions(streamClosedCallback);
verifyNoInteractions(serverSentEventCallback);
}
@Test
public void whenTheEventStreamBreaksThenTheStreamClosedCallbackIsNotifiedWithTheCause() throws IOException {
// given:
InputStream inputStream = mock(InputStream.class);
TimeoutException timeoutException = new TimeoutException();
when(inputStream.read(any(), anyInt(), anyInt())).thenThrow(new IOException(timeoutException));
SseStreamParser parser = new SseStreamParser(inputStream, serverSentEventCallback, streamClosedCallback);
// when:
parser.parseAndDispatchEvents();
// then:
verify(streamClosedCallback).accept(timeoutException);
verifyNoMoreInteractions(streamClosedCallback);
verifyNoInteractions(serverSentEventCallback);
}
@Test
public void whenTheEventStreamBreaksBecauseOfAnSseDisconnectThenTheStreamCloseCallbackIsNotNotifiedToPreventSseReconnect()
throws IOException {
// given:
InputStream inputStream = mock(InputStream.class);
when(inputStream.read(any(), anyInt(), anyInt()))
.thenThrow(new IOException(new MieleWebserviceDisconnectSseException()));
SseStreamParser parser = new SseStreamParser(inputStream, serverSentEventCallback, streamClosedCallback);
// when:
parser.parseAndDispatchEvents();
// then:
verifyNoInteractions(streamClosedCallback, serverSentEventCallback);
}
@Test
public void whenTheEventStreamBreaksAndTheResourceCleanupFailsThenItIsIgnored() throws IOException {
// given:
InputStream inputStream = mock(InputStream.class);
when(inputStream.read(any(), anyInt(), anyInt()))
.thenThrow(new IOException(new MieleWebserviceDisconnectSseException()));
doThrow(new IOException()).when(inputStream).close();
SseStreamParser parser = new SseStreamParser(inputStream, serverSentEventCallback, streamClosedCallback);
// when:
parser.parseAndDispatchEvents();
// then:
verifyNoInteractions(streamClosedCallback, serverSentEventCallback);
}
}

View File

@@ -0,0 +1,123 @@
{
"000124430017": {
"ident": {
"type": {
"key_localized": "Devicetype",
"value_raw": 18,
"value_localized": "Ventilation Hood"
},
"deviceName": "My Hood",
"deviceIdentLabel": {
"fabNumber": "000124430017",
"fabIndex": "00",
"techType": "DA-6996",
"matNumber": "10101010",
"swids": [
"4164",
"20380",
"25226"
]
},
"xkmIdentLabel": {
"techType": "EK039W",
"releaseVersion": "02.31"
}
},
"state": {
"status": {
"value_raw": 5,
"value_localized": "In use",
"key_localized": "State"
},
"programType": {
"value_raw": 0,
"value_localized": "",
"key_localized": "Programme"
},
"programPhase": {
"value_raw": 4609,
"value_localized": "",
"key_localized": "Phase"
},
"remainingTime": [
0,
0
],
"startTime": [
0,
0
],
"targetTemperature": [
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
}
],
"temperature": [
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
},
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
},
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
}
],
"signalInfo": false,
"signalFailure": false,
"signalDoor": false,
"remoteEnable": {
"fullRemoteControl": false,
"smartGrid": false
},
"light": 1,
"elapsedTime": [],
"spinningSpeed": {
"value_raw" : 1200,
"value_localized" : 1200,
"unit" : "rpm"
},
"dryingStep": {
"value_raw": null,
"value_localized": "",
"key_localized": "Drying level"
},
"ventilationStep": {
"value_raw": 2,
"value_localized": "2",
"key_localized": "Power Level"
},
"plateStep": [
{
"value_raw": 0,
"value_localized": "0",
"key_localized": "Plate Step"
},
{
"value_raw": 1,
"value_localized": "1",
"key_localized": "Plate Step"
},
{
"value_raw": 2,
"value_localized": "1.",
"key_localized": "Plate Step"
},
{
"value_raw": 3,
"value_localized": "2",
"key_localized": "Plate Step"
}
],
"batteryLevel": 20
}
}
}

View File

@@ -0,0 +1,13 @@
{
"000091465021": {
"state": {
"targetTemperature": [
{
"value_raw": 80,
"value_localized": 0.8,
"unit": "Celsius"
}
]
}
}
}

View File

@@ -0,0 +1,73 @@
{
"mac-00124B000AE539D6": {
"ident": {
"type": {
"key_localized": "Devicetype",
"value_raw": 100,
"value_localized": ""
},
"deviceName": "Some Devicename",
"deviceIdentLabel": {
"fabNumber": "",
"fabIndex": "",
"techType": "",
"matNumber": "",
"swids": []
},
"xkmIdentLabel": {
"techType": "",
"releaseVersion": ""
}
},
"state": {
"ProgramID": {
"value_raw": 2499805184,
"value_localized": "",
"key_localized": "Program Id"
},
"status": {
"value_raw": 5,
"value_localized": "In use",
"key_localized": "State"
},
"programType": {
"value_raw": 0,
"value_localized": "Operation mode",
"key_localized": "Program type"
},
"programPhase": {
"value_raw": 0,
"value_localized": "",
"key_localized": "Phase"
},
"remainingTime": [
0,
0
],
"startTime": [
0,
0
],
"targetTemperature": [],
"signalInfo": false,
"signalFailure": false,
"signalDoor": false,
"remoteEnable": {
"fullRemoteControl": true,
"smartGrid": false
},
"light": 0,
"elapsedTime": [],
"dryingStep": {
"value_raw": null,
"value_localized": "",
"key_localized": "Drying level"
},
"ventilationStep": {
"value_raw": null,
"value_localized": "",
"key_localized": "Power Level"
}
}
}
}

View File

@@ -0,0 +1,11 @@
{
"000124430017": {
"state": {
"spinningSpeed": {
"value_raw" : 1600,
"value_localized" : 1600,
"unit" : "U/min"
}
}
}
}

View File

@@ -0,0 +1,73 @@
{
"000124430017": {
"ident": {
"type": {
"key_localized": "Devicetype",
10",
"swids": [
"4164",
"20380",
"25226"
]
},
"xkmIdentLabel": {
"techType": "EK039W",
"releaseVersion": "02.31"
}
},
"state": {
"status": {
"value_raw": 5,
"value_localized": "In use",
"key_localized": "State"
},
"programType": {
"value_raw": 0,
"value_localized": "",
"key_localized": "Programme"
},
"programPhase": {
"value_raw": 4609,
"value_localized": "",
"key_localized": "Phase"
},
"remainingTime": [
0,
0
],
"startTime": [
0,
0
],
"targetT
"unit": "Celsius"
},
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
}
],
"signalInfo": false,
"signalFailure": false,
"signalDoor": false,
"remoteEnable": {
"fullRemoteControl": false,
"smartGrid": false
},
"light": 1,
"elapsedTime": [],
"dryingStep": {
"value_raw": null,
"value_localized": "",
"key_localized": "Drying level"
},
"ventilationStep": {
"value_raw": 2,
"value_localized": "2",
"key_localized": "Power Level"
}
}
},
"$$ref": "#/components/examples/devicesExample"
}