2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.mielecloud.internal.webservice;
15 import static org.junit.jupiter.api.Assertions.*;
16 import static org.mockito.ArgumentMatchers.*;
17 import static org.mockito.Mockito.*;
18 import static org.openhab.binding.mielecloud.internal.util.ReflectionUtil.getPrivate;
20 import java.util.concurrent.ExecutionException;
21 import java.util.concurrent.ScheduledExecutorService;
22 import java.util.concurrent.TimeoutException;
23 import java.util.function.Consumer;
24 import java.util.function.Supplier;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.eclipse.jetty.client.api.ContentResponse;
29 import org.eclipse.jetty.client.api.Request;
30 import org.eclipse.jetty.http.HttpFields;
31 import org.junit.jupiter.api.Test;
32 import org.openhab.binding.mielecloud.internal.MieleCloudBindingTestConstants;
33 import org.openhab.binding.mielecloud.internal.auth.OAuthTokenRefresher;
34 import org.openhab.binding.mielecloud.internal.util.MockUtil;
35 import org.openhab.binding.mielecloud.internal.webservice.api.json.ProcessAction;
36 import org.openhab.binding.mielecloud.internal.webservice.exception.AuthorizationFailedException;
37 import org.openhab.binding.mielecloud.internal.webservice.exception.MieleWebserviceException;
38 import org.openhab.binding.mielecloud.internal.webservice.exception.MieleWebserviceTransientException;
39 import org.openhab.binding.mielecloud.internal.webservice.exception.TooManyRequestsException;
40 import org.openhab.binding.mielecloud.internal.webservice.language.LanguageProvider;
41 import org.openhab.binding.mielecloud.internal.webservice.request.RequestFactory;
42 import org.openhab.binding.mielecloud.internal.webservice.retry.AuthorizationFailedRetryStrategy;
43 import org.openhab.binding.mielecloud.internal.webservice.retry.NTimesRetryStrategy;
44 import org.openhab.binding.mielecloud.internal.webservice.retry.RetryStrategy;
45 import org.openhab.binding.mielecloud.internal.webservice.retry.RetryStrategyCombiner;
46 import org.openhab.core.io.net.http.HttpClientFactory;
49 * @author Björn Lange - Initial contribution
52 public class DefaultMieleWebserviceTest {
53 private static final String MESSAGE_INTERNAL_SERVER_ERROR = "{\"message\": \"Internal Server Error\"}";
54 private static final String MESSAGE_SERVICE_UNAVAILABLE = "{\"message\": \"unavailable\"}";
55 private static final String MESSAGE_INVALID_JSON = "{\"abc123: \"äfgh\"}";
57 private static final String DEVICE_IDENTIFIER = "000124430016";
59 private static final String SERVER_ADDRESS = "https://api.mcs3.miele.com";
60 private static final String ENDPOINT_DEVICES = SERVER_ADDRESS + "/v1/devices/";
61 private static final String ENDPOINT_ACTIONS = ENDPOINT_DEVICES + DEVICE_IDENTIFIER + "/actions";
62 private static final String ENDPOINT_LOGOUT = SERVER_ADDRESS + "/thirdparty/logout";
64 private static final String ACCESS_TOKEN = "DE_0123456789abcdef0123456789abcdef";
66 private final RetryStrategy retryStrategy = new UncatchedRetryStrategy();
67 private final Request request = mock(Request.class);
70 public void testDefaultRetryStrategyIsCombinationOfOneTimeRetryStrategyAndAuthorizationFailedStrategy()
73 HttpClientFactory httpClientFactory = mock(HttpClientFactory.class);
74 when(httpClientFactory.createHttpClient(anyString())).thenReturn(MockUtil.mockHttpClient());
75 LanguageProvider languageProvider = mock(LanguageProvider.class);
76 OAuthTokenRefresher tokenRefresher = mock(OAuthTokenRefresher.class);
77 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
80 DefaultMieleWebservice webservice = new DefaultMieleWebservice(MieleWebserviceConfiguration.builder()
81 .withHttpClientFactory(httpClientFactory).withLanguageProvider(languageProvider)
82 .withTokenRefresher(tokenRefresher).withServiceHandle(MieleCloudBindingTestConstants.SERVICE_HANDLE)
83 .withScheduler(scheduler).build());
86 RetryStrategy retryStrategy = getPrivate(webservice, "retryStrategy");
87 assertTrue(retryStrategy instanceof RetryStrategyCombiner);
89 RetryStrategy first = getPrivate(retryStrategy, "first");
90 assertTrue(first instanceof NTimesRetryStrategy);
91 int numberOfRetries = getPrivate(first, "numberOfRetries");
92 assertEquals(1, numberOfRetries);
94 RetryStrategy second = getPrivate(retryStrategy, "second");
95 assertTrue(second instanceof AuthorizationFailedRetryStrategy);
96 OAuthTokenRefresher internalTokenRefresher = getPrivate(second, "tokenRefresher");
97 assertEquals(tokenRefresher, internalTokenRefresher);
100 private ContentResponse createContentResponseMock(int errorCode, String content) {
101 ContentResponse response = mock(ContentResponse.class);
102 when(response.getStatus()).thenReturn(errorCode);
103 when(response.getContentAsString()).thenReturn(content);
107 private void performFetchActions() throws Exception {
108 RequestFactory requestFactory = mock(RequestFactory.class);
109 when(requestFactory.createGetRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN)).thenReturn(request);
111 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
113 try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, retryStrategy,
114 new DeviceStateDispatcher(), scheduler)) {
115 webservice.setAccessToken(ACCESS_TOKEN);
117 webservice.fetchActions(DEVICE_IDENTIFIER);
121 private void performFetchActionsExpectingFailure(ConnectionError expectedError) throws Exception {
123 performFetchActions();
124 } catch (MieleWebserviceException e) {
125 assertEquals(expectedError, e.getConnectionError());
127 } catch (MieleWebserviceTransientException e) {
128 assertEquals(expectedError, e.getConnectionError());
134 public void testTimeoutExceptionWhilePerformingFetchActionsRequest() throws Exception {
136 when(request.send()).thenThrow(TimeoutException.class);
139 assertThrows(MieleWebserviceTransientException.class, () -> {
140 performFetchActionsExpectingFailure(ConnectionError.TIMEOUT);
145 public void test500InternalServerErrorWhilePerformingFetchActionsRequest() throws Exception {
147 ContentResponse contentResponse = createContentResponseMock(500, MESSAGE_INTERNAL_SERVER_ERROR);
148 when(request.send()).thenReturn(contentResponse);
151 assertThrows(MieleWebserviceTransientException.class, () -> {
152 performFetchActionsExpectingFailure(ConnectionError.SERVER_ERROR);
157 public void test503ServiceUnavailableWhilePerformingFetchActionsRequest() throws Exception {
159 ContentResponse contentResponse = createContentResponseMock(503, MESSAGE_SERVICE_UNAVAILABLE);
160 when(request.send()).thenReturn(contentResponse);
163 assertThrows(MieleWebserviceTransientException.class, () -> {
164 performFetchActionsExpectingFailure(ConnectionError.SERVICE_UNAVAILABLE);
169 public void testInvalidJsonWhilePerformingFetchActionsRequest() throws Exception {
171 ContentResponse contentResponse = createContentResponseMock(200, MESSAGE_INVALID_JSON);
172 when(request.send()).thenReturn(contentResponse);
175 assertThrows(MieleWebserviceTransientException.class, () -> {
176 performFetchActionsExpectingFailure(ConnectionError.RESPONSE_MALFORMED);
181 public void testInterruptedExceptionWhilePerformingFetchActionsRequest() throws Exception {
183 when(request.send()).thenThrow(InterruptedException.class);
186 assertThrows(MieleWebserviceException.class, () -> {
187 performFetchActionsExpectingFailure(ConnectionError.REQUEST_INTERRUPTED);
192 public void testExecutionExceptionWhilePerformingFetchActionsRequest() throws Exception {
194 when(request.send()).thenThrow(ExecutionException.class);
197 assertThrows(MieleWebserviceException.class, () -> {
198 performFetchActionsExpectingFailure(ConnectionError.REQUEST_EXECUTION_FAILED);
203 public void test400BadRequestWhilePerformingFetchActionsRequest() throws Exception {
205 ContentResponse response = createContentResponseMock(400, "{\"message\": \"grant_type is invalid\"}");
206 when(request.send()).thenReturn(response);
209 assertThrows(MieleWebserviceException.class, () -> {
210 performFetchActionsExpectingFailure(ConnectionError.OTHER_HTTP_ERROR);
215 public void test401UnauthorizedWhilePerformingFetchActionsRequest() throws Exception {
217 ContentResponse response = createContentResponseMock(401, "{\"message\": \"Unauthorized\"}");
218 when(request.send()).thenReturn(response);
221 assertThrows(AuthorizationFailedException.class, () -> {
222 performFetchActions();
227 public void test404NotFoundWhilePerformingFetchActionsRequest() throws Exception {
229 ContentResponse response = createContentResponseMock(404, "{\"message\": \"Not found\"}");
230 when(request.send()).thenReturn(response);
233 assertThrows(MieleWebserviceException.class, () -> {
234 performFetchActionsExpectingFailure(ConnectionError.OTHER_HTTP_ERROR);
239 public void test405MethodNotAllowedWhilePerformingFetchActionsRequest() throws Exception {
241 ContentResponse response = createContentResponseMock(405, "{\"message\": \"HTTP 405 Method Not Allowed\"}");
242 when(request.send()).thenReturn(response);
245 assertThrows(MieleWebserviceException.class, () -> {
246 performFetchActionsExpectingFailure(ConnectionError.OTHER_HTTP_ERROR);
251 public void test429TooManyRequestsWhilePerformingFetchActionsRequest() throws Exception {
253 HttpFields headerFields = mock(HttpFields.class);
254 when(headerFields.containsKey(anyString())).thenReturn(false);
256 ContentResponse response = createContentResponseMock(429, "{\"message\": \"Too Many Requests\"}");
257 when(response.getHeaders()).thenReturn(headerFields);
259 when(request.send()).thenReturn(response);
262 assertThrows(TooManyRequestsException.class, () -> {
263 performFetchActions();
268 public void test502BadGatewayWhilePerforminggFetchActionsRequest() throws Exception {
270 ContentResponse response = createContentResponseMock(502, "{\"message\": \"Bad Gateway\"}");
271 when(request.send()).thenReturn(response);
274 assertThrows(MieleWebserviceException.class, () -> {
275 performFetchActionsExpectingFailure(ConnectionError.OTHER_HTTP_ERROR);
280 public void testMalformatedBodyWhilePerforminggFetchActionsRequest() throws Exception {
282 ContentResponse response = createContentResponseMock(502, "{\"message \"Bad Gateway\"}");
283 when(request.send()).thenReturn(response);
286 assertThrows(MieleWebserviceException.class, () -> {
287 performFetchActionsExpectingFailure(ConnectionError.OTHER_HTTP_ERROR);
291 private void fillRequestMockWithDefaultContent() throws InterruptedException, TimeoutException, ExecutionException {
292 ContentResponse response = createContentResponseMock(200,
293 "{\"000124430016\":{\"ident\": {\"deviceName\": \"MyFancyHood\", \"deviceIdentLabel\": {\"fabNumber\": \"000124430016\"}}}}");
294 when(request.send()).thenReturn(response);
298 public void testAddDeviceStateListenerIsDelegatedToDeviceStateDispatcher() throws Exception {
300 RequestFactory requestFactory = mock(RequestFactory.class);
301 DeviceStateDispatcher dispatcher = mock(DeviceStateDispatcher.class);
302 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
304 try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
305 dispatcher, scheduler)) {
306 DeviceStateListener listener = mock(DeviceStateListener.class);
309 webservice.addDeviceStateListener(listener);
312 verify(dispatcher).addListener(listener);
313 verifyNoMoreInteractions(dispatcher);
318 public void testFetchActionsDelegatesDeviceStateListenerDispatchingToDeviceStateDispatcher() throws Exception {
320 fillRequestMockWithDefaultContent();
322 RequestFactory requestFactory = mock(RequestFactory.class);
323 when(requestFactory.createGetRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN)).thenReturn(request);
325 DeviceStateDispatcher dispatcher = mock(DeviceStateDispatcher.class);
327 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
329 try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, retryStrategy, dispatcher,
331 webservice.setAccessToken(ACCESS_TOKEN);
334 webservice.fetchActions(DEVICE_IDENTIFIER);
337 verify(dispatcher).dispatchActionStateUpdates(any(), any());
338 verifyNoMoreInteractions(dispatcher);
343 public void testFetchActionsThrowsMieleWebserviceTransientExceptionWhenRequestContentIsMalformatted()
346 ContentResponse response = createContentResponseMock(200, "{\"}");
347 when(request.send()).thenReturn(response);
349 RequestFactory requestFactory = mock(RequestFactory.class);
350 when(requestFactory.createGetRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN)).thenReturn(request);
352 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
354 try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, retryStrategy,
355 new DeviceStateDispatcher(), scheduler)) {
356 webservice.setAccessToken(ACCESS_TOKEN);
359 assertThrows(MieleWebserviceTransientException.class, () -> {
360 webservice.fetchActions(DEVICE_IDENTIFIER);
366 public void testPutProcessActionSendsRequestWithCorrectJsonContent() throws Exception {
368 ContentResponse response = createContentResponseMock(204, "");
369 when(request.send()).thenReturn(response);
371 RequestFactory requestFactory = mock(RequestFactory.class);
372 when(requestFactory.createPutRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN, "{\"processAction\":1}"))
373 .thenReturn(request);
375 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
377 try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
378 new DeviceStateDispatcher(), scheduler)) {
379 webservice.setAccessToken(ACCESS_TOKEN);
382 webservice.putProcessAction(DEVICE_IDENTIFIER, ProcessAction.START);
385 verify(request).send();
386 verifyNoMoreInteractions(request);
391 public void testPutProcessActionThrowsIllegalArgumentExceptionWhenProcessActionIsUnknown() throws Exception {
393 RequestFactory requestFactory = mock(RequestFactory.class);
395 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
397 try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, retryStrategy,
398 new DeviceStateDispatcher(), scheduler)) {
401 assertThrows(IllegalArgumentException.class, () -> {
402 webservice.putProcessAction(DEVICE_IDENTIFIER, ProcessAction.UNKNOWN);
408 public void testPutProcessActionThrowsTooManyRequestsExceptionWhenHttpResponseCodeIs429() throws Exception {
410 HttpFields responseHeaders = mock(HttpFields.class);
411 when(responseHeaders.containsKey(anyString())).thenReturn(false);
413 ContentResponse response = createContentResponseMock(429, "{\"message\":\"Too many requests\"}");
414 when(response.getHeaders()).thenReturn(responseHeaders);
416 when(request.send()).thenReturn(response);
418 RequestFactory requestFactory = mock(RequestFactory.class);
419 when(requestFactory.createPutRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN, "{\"processAction\":1}"))
420 .thenReturn(request);
422 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
424 try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
425 new DeviceStateDispatcher(), scheduler)) {
426 webservice.setAccessToken(ACCESS_TOKEN);
429 assertThrows(TooManyRequestsException.class, () -> {
430 webservice.putProcessAction(DEVICE_IDENTIFIER, ProcessAction.START);
436 public void testPutLightSendsRequestWithCorrectJsonContentWhenTurningTheLightOn() throws Exception {
438 ContentResponse response = createContentResponseMock(204, "");
439 when(request.send()).thenReturn(response);
441 RequestFactory requestFactory = mock(RequestFactory.class);
442 when(requestFactory.createPutRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN, "{\"light\":1}")).thenReturn(request);
444 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
446 try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
447 new DeviceStateDispatcher(), scheduler)) {
448 webservice.setAccessToken(ACCESS_TOKEN);
451 webservice.putLight(DEVICE_IDENTIFIER, true);
454 verify(request).send();
455 verifyNoMoreInteractions(request);
460 public void testPutLightSendsRequestWithCorrectJsonContentWhenTurningTheLightOff() throws Exception {
462 ContentResponse response = createContentResponseMock(204, "");
463 when(request.send()).thenReturn(response);
465 RequestFactory requestFactory = mock(RequestFactory.class);
466 when(requestFactory.createPutRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN, "{\"light\":2}")).thenReturn(request);
468 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
470 try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
471 new DeviceStateDispatcher(), scheduler)) {
472 webservice.setAccessToken(ACCESS_TOKEN);
475 webservice.putLight(DEVICE_IDENTIFIER, false);
478 verify(request).send();
479 verifyNoMoreInteractions(request);
484 public void testPutLightThrowsTooManyRequestsExceptionWhenHttpResponseCodeIs429() throws Exception {
486 HttpFields responseHeaders = mock(HttpFields.class);
487 when(responseHeaders.containsKey(anyString())).thenReturn(false);
489 ContentResponse response = createContentResponseMock(429, "{\"message\":\"Too many requests\"}");
490 when(response.getHeaders()).thenReturn(responseHeaders);
492 when(request.send()).thenReturn(response);
494 RequestFactory requestFactory = mock(RequestFactory.class);
495 when(requestFactory.createPutRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN, "{\"light\":2}")).thenReturn(request);
497 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
499 try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
500 new DeviceStateDispatcher(), scheduler)) {
501 webservice.setAccessToken(ACCESS_TOKEN);
504 assertThrows(TooManyRequestsException.class, () -> {
505 webservice.putLight(DEVICE_IDENTIFIER, false);
511 public void testLogoutInvalidatesAccessTokenOnSuccess() throws Exception {
513 ContentResponse response = createContentResponseMock(204, "");
514 when(request.send()).thenReturn(response);
516 RequestFactory requestFactory = mock(RequestFactory.class);
517 when(requestFactory.createPostRequest(ENDPOINT_LOGOUT, ACCESS_TOKEN)).thenReturn(request);
519 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
521 try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, retryStrategy,
522 new DeviceStateDispatcher(), scheduler)) {
523 webservice.setAccessToken(ACCESS_TOKEN);
529 assertFalse(webservice.hasAccessToken());
530 verify(request).send();
531 verifyNoMoreInteractions(request);
536 public void testLogoutThrowsMieleWebserviceExceptionWhenMieleWebserviceTransientExceptionIsThrownInternally()
539 when(request.send()).thenThrow(TimeoutException.class);
541 RequestFactory requestFactory = mock(RequestFactory.class);
542 when(requestFactory.createPostRequest(ENDPOINT_LOGOUT, ACCESS_TOKEN)).thenReturn(request);
544 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
546 try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, retryStrategy,
547 new DeviceStateDispatcher(), scheduler)) {
548 webservice.setAccessToken(ACCESS_TOKEN);
551 assertThrows(MieleWebserviceException.class, () -> {
558 public void testLogoutInvalidatesAccessTokenWhenOperationFails() throws Exception {
560 when(request.send()).thenThrow(TimeoutException.class);
562 RequestFactory requestFactory = mock(RequestFactory.class);
563 when(requestFactory.createPostRequest(ENDPOINT_LOGOUT, ACCESS_TOKEN)).thenReturn(request);
565 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
567 try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, retryStrategy,
568 new DeviceStateDispatcher(), scheduler)) {
569 webservice.setAccessToken(ACCESS_TOKEN);
574 } catch (MieleWebserviceException e) {
578 assertFalse(webservice.hasAccessToken());
583 public void testRemoveDeviceStateListenerIsDelegatedToDeviceStateDispatcher() throws Exception {
585 RequestFactory requestFactory = mock(RequestFactory.class);
587 DeviceStateDispatcher dispatcher = mock(DeviceStateDispatcher.class);
589 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
591 try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
592 dispatcher, scheduler)) {
593 DeviceStateListener listener = mock(DeviceStateListener.class);
594 webservice.addDeviceStateListener(listener);
597 webservice.removeDeviceStateListener(listener);
600 verify(dispatcher).addListener(listener);
601 verify(dispatcher).removeListener(listener);
602 verifyNoMoreInteractions(dispatcher);
607 public void testPutPowerStateSendsRequestWithCorrectJsonContentWhenSwitchingTheDeviceOn() throws Exception {
609 ContentResponse response = createContentResponseMock(204, "");
610 when(request.send()).thenReturn(response);
612 RequestFactory requestFactory = mock(RequestFactory.class);
613 when(requestFactory.createPutRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN, "{\"powerOn\":true}")).thenReturn(request);
615 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
617 try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
618 new DeviceStateDispatcher(), scheduler)) {
619 webservice.setAccessToken(ACCESS_TOKEN);
622 webservice.putPowerState(DEVICE_IDENTIFIER, true);
625 verify(request).send();
626 verifyNoMoreInteractions(request);
631 public void testPutPowerStateSendsRequestWithCorrectJsonContentWhenDeviceIsSwitchedOff() throws Exception {
633 ContentResponse response = createContentResponseMock(204, "");
634 when(request.send()).thenReturn(response);
636 RequestFactory requestFactory = mock(RequestFactory.class);
637 when(requestFactory.createPutRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN, "{\"powerOff\":true}"))
638 .thenReturn(request);
640 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
642 try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
643 new DeviceStateDispatcher(), scheduler)) {
644 webservice.setAccessToken(ACCESS_TOKEN);
647 webservice.putPowerState(DEVICE_IDENTIFIER, false);
650 verify(request).send();
651 verifyNoMoreInteractions(request);
656 public void testPutPowerStateThrowsTooManyRequestsExceptionWhenHttpResponseCodeIs429() throws Exception {
658 HttpFields responseHeaders = mock(HttpFields.class);
659 when(responseHeaders.containsKey(anyString())).thenReturn(false);
661 ContentResponse response = createContentResponseMock(429, "{\"message\":\"Too many requests\"}");
662 when(response.getHeaders()).thenReturn(responseHeaders);
664 when(request.send()).thenReturn(response);
666 RequestFactory requestFactory = mock(RequestFactory.class);
667 when(requestFactory.createPutRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN, "{\"powerOff\":true}"))
668 .thenReturn(request);
670 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
672 try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
673 new DeviceStateDispatcher(), scheduler)) {
674 webservice.setAccessToken(ACCESS_TOKEN);
677 assertThrows(TooManyRequestsException.class, () -> {
678 webservice.putPowerState(DEVICE_IDENTIFIER, false);
684 public void testPutProgramResultsInARequestWithCorrectJson() throws Exception {
686 ContentResponse response = createContentResponseMock(204, "");
687 when(request.send()).thenReturn(response);
688 RequestFactory requestFactory = mock(RequestFactory.class);
689 when(requestFactory.createPutRequest(ENDPOINT_ACTIONS, ACCESS_TOKEN, "{\"programId\":1}")).thenReturn(request);
691 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
693 try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
694 new DeviceStateDispatcher(), scheduler)) {
695 webservice.setAccessToken(ACCESS_TOKEN);
698 webservice.putProgram(DEVICE_IDENTIFIER, 1);
701 verify(request).send();
702 verifyNoMoreInteractions(request);
707 public void testDispatchDeviceStateIsDelegatedToDeviceStateDispatcher() throws Exception {
709 RequestFactory requestFactory = mock(RequestFactory.class);
710 DeviceStateDispatcher dispatcher = mock(DeviceStateDispatcher.class);
711 ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class);
713 try (DefaultMieleWebservice webservice = new DefaultMieleWebservice(requestFactory, new NTimesRetryStrategy(0),
714 dispatcher, scheduler)) {
716 webservice.dispatchDeviceState(DEVICE_IDENTIFIER);
719 verify(dispatcher).dispatchDeviceState(DEVICE_IDENTIFIER);
720 verifyNoMoreInteractions(dispatcher);
725 * {@link RetryStrategy} for testing purposes. No exceptions will be catched.
727 * @author Roland Edelhoff - Initial contribution.
729 private static class UncatchedRetryStrategy implements RetryStrategy {
732 public <@Nullable T> T performRetryableOperation(Supplier<T> operation,
733 Consumer<Exception> onTransientException) {
734 return operation.get();
738 public void performRetryableOperation(Runnable operation, Consumer<Exception> onTransientException) {