2 * Copyright (c) 2010-2024 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.salus.internal.rest;
15 import static org.assertj.core.api.Assertions.*;
16 import static org.mockito.ArgumentMatchers.any;
17 import static org.mockito.ArgumentMatchers.anyString;
18 import static org.mockito.ArgumentMatchers.endsWith;
19 import static org.mockito.ArgumentMatchers.eq;
20 import static org.mockito.Mockito.mock;
21 import static org.mockito.Mockito.when;
23 import java.time.Clock;
24 import java.util.ArrayList;
25 import java.util.Optional;
27 import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.junit.jupiter.api.DisplayName;
30 import org.junit.jupiter.api.Test;
33 * @author Martin GrzeĊlowski - Initial contribution
35 @SuppressWarnings("DataFlowIssue")
37 public class SalusApiTest {
39 // Find devices returns sorted set of devices
41 @DisplayName("Find devices returns sorted set of devices")
42 public void testFindDevicesReturnsSortedSetOfDevices() throws Exception {
44 var username = "correct_username";
45 var password = "correct_password".toCharArray();
46 var baseUrl = "https://example.com";
47 var restClient = mock(RestClient.class);
48 var mapper = mock(GsonMapper.class);
49 var clock = Clock.systemDefaultZone();
51 var authToken = new AuthToken("access_token", "refresh_token", 3600L, "role");
52 var response = "devices_json";
53 when(restClient.get(anyString(), any())).thenReturn(response);
55 var devices = new ArrayList<Device>();
56 when(mapper.parseDevices(anyString())).thenReturn(devices);
58 var salusApi = new SalusApi(username, password, baseUrl, restClient, mapper, clock);
59 setAuthToken(salusApi, restClient, mapper, authToken);
62 var result = salusApi.findDevices();
65 assertThat(result).containsExactlyInAnyOrderElementsOf(devices);
68 // Find device properties returns sorted set of device properties
70 @DisplayName("Find device properties returns sorted set of device properties")
71 public void testFindDevicePropertiesReturnsSortedSetOfDeviceProperties() throws Exception {
73 var username = "correct_username";
74 var password = "correct_password".toCharArray();
75 var baseUrl = "https://example.com";
76 var restClient = mock(RestClient.class);
77 var mapper = mock(GsonMapper.class);
78 var clock = Clock.systemDefaultZone();
80 var authToken = new AuthToken("access_token", "refresh_token", 3600L, "role");
81 var response = "device_properties_json";
82 when(restClient.get(anyString(), any())).thenReturn(response);
84 var deviceProperties = new ArrayList<DeviceProperty<?>>();
85 when(mapper.parseDeviceProperties(anyString())).thenReturn(deviceProperties);
87 var salusApi = new SalusApi(username, password, baseUrl, restClient, mapper, clock);
88 setAuthToken(salusApi, restClient, mapper, authToken);
91 var result = salusApi.findDeviceProperties("dsn");
94 assertThat(result).containsExactlyInAnyOrderElementsOf(deviceProperties);
97 // Set value for property returns OK response with datapoint value
99 @DisplayName("Set value for property returns OK response with datapoint value")
100 public void testSetValueForPropertyReturnsOkResponseWithDatapointValue() throws Exception {
102 var username = "correct_username";
103 var password = "correct_password".toCharArray();
104 var baseUrl = "https://example.com";
105 var restClient = mock(RestClient.class);
106 var mapper = mock(GsonMapper.class);
107 var clock = Clock.systemDefaultZone();
109 var authToken = new AuthToken("access_token", "refresh_token", 3600L, "role");
110 var response = "datapoint_value_json";
111 when(restClient.post(anyString(), any(), any())).thenReturn(response);
113 var datapointValue = new Object();
114 when(mapper.datapointValue(anyString())).thenReturn(Optional.of(datapointValue));
116 var salusApi = new SalusApi(username, password, baseUrl, restClient, mapper, clock);
117 setAuthToken(salusApi, restClient, mapper, authToken);
120 var result = salusApi.setValueForProperty("dsn", "property_name", "value");
123 assertThat(result).isEqualTo(datapointValue);
126 // Login with incorrect credentials throws HttpUnauthorizedException
128 @DisplayName("Login with incorrect credentials throws HttpUnauthorizedException")
129 public void testLoginWithIncorrectCredentialsThrowsHttpUnauthorizedException() throws Exception {
131 var username = "incorrect_username";
132 var password = "incorrect_password".toCharArray();
133 var baseUrl = "https://example.com";
134 var restClient = mock(RestClient.class);
135 var mapper = mock(GsonMapper.class);
136 var clock = Clock.systemDefaultZone();
138 when(restClient.post(anyString(), any(), any()))
139 .thenThrow(new HttpSalusApiException(401, "unauthorized_error_json"));
141 var salusApi = new SalusApi(username, password, baseUrl, restClient, mapper, clock);
144 ThrowingCallable findDevicesResponse = salusApi::findDevices;
147 assertThatThrownBy(findDevicesResponse).isInstanceOf(HttpSalusApiException.class)
148 .hasMessage("HTTP Error 401: unauthorized_error_json");
151 // Find devices with invalid auth token throws HttpUnauthorizedException
153 @DisplayName("Find devices with invalid auth token throws HttpUnauthorizedException")
154 public void testFindDevicesWithInvalidAuthTokenThrowsHttpUnauthorizedException() throws Exception {
156 var username = "correct_username";
157 var password = "correct_password".toCharArray();
158 var baseUrl = "https://example.com";
159 var restClient = mock(RestClient.class);
160 var mapper = mock(GsonMapper.class);
161 var clock = Clock.systemDefaultZone();
163 var authToken = new AuthToken("access_token", "refresh_token", 3600L, "role");
164 when(restClient.get(anyString(), any())).thenThrow(new HttpSalusApiException(401, "unauthorized_error_json"));
166 var salusApi = new SalusApi(username, password, baseUrl, restClient, mapper, clock);
167 setAuthToken(salusApi, restClient, mapper, authToken);
170 ThrowingCallable objectApiResponse = salusApi::findDevices;
173 assertThatThrownBy(objectApiResponse).isInstanceOf(HttpSalusApiException.class)
174 .hasMessage("HTTP Error 401: unauthorized_error_json");
177 // Find device properties with invalid auth token throws HttpUnauthorizedException
179 @DisplayName("Find device properties with invalid auth token throws HttpUnauthorizedException")
180 public void testFindDevicePropertiesWithInvalidAuthTokenThrowsHttpUnauthorizedException() throws Exception {
182 var username = "correct_username";
183 var password = "correct_password".toCharArray();
184 var baseUrl = "https://example.com";
185 var restClient = mock(RestClient.class);
186 var mapper = mock(GsonMapper.class);
187 var clock = Clock.systemDefaultZone();
189 var authToken = new AuthToken("access_token", "refresh_token", 3600L, "role");
190 when(restClient.get(anyString(), any())).thenThrow(new HttpSalusApiException(401, "unauthorized_error_json"));
192 var salusApi = new SalusApi(username, password, baseUrl, restClient, mapper, clock);
193 setAuthToken(salusApi, restClient, mapper, authToken);
196 ThrowingCallable objectApiResponse = () -> salusApi.findDeviceProperties("dsn");
199 assertThatThrownBy(objectApiResponse).isInstanceOf(HttpSalusApiException.class)
200 .hasMessage("HTTP Error 401: unauthorized_error_json");
203 // Set value for property with invalid auth token throws HttpUnauthorizedException
205 @DisplayName("Set value for property with invalid auth token throws HttpUnauthorizedException")
206 public void testSetValueForPropertyWithInvalidAuthTokenThrowsHttpUnauthorizedException() throws Exception {
208 var username = "correct_username";
209 var password = "correct_password".toCharArray();
210 var baseUrl = "https://example.com";
211 var restClient = mock(RestClient.class);
212 var mapper = mock(GsonMapper.class);
213 var clock = Clock.systemDefaultZone();
215 var authToken = new AuthToken("access_token", "refresh_token", 3600L, "role");
216 when(restClient.post(anyString(), any(), any()))
217 .thenThrow(new HttpSalusApiException(401, "unauthorized_error_json"));
219 var salusApi = new SalusApi(username, password, baseUrl, restClient, mapper, clock);
222 ThrowingCallable objectApiResponse = () -> salusApi.setValueForProperty("dsn", "property_name", "value");
226 assertThatThrownBy(objectApiResponse).isInstanceOf(HttpSalusApiException.class)
227 .hasMessage("HTTP Error 401: unauthorized_error_json");
230 // Find device properties with invalid DSN returns ApiResponse with error
232 @DisplayName("Find device properties with invalid DSN returns ApiResponse with error")
233 public void testFindDevicePropertiesWithInvalidDsnReturnsApiResponseWithError() throws Exception {
235 var username = "correct_username";
236 var password = "correct_password".toCharArray();
237 var baseUrl = "https://example.com";
238 var restClient = mock(RestClient.class);
239 var mapper = mock(GsonMapper.class);
240 var clock = Clock.systemDefaultZone();
242 var authToken = new AuthToken("access_token", "refresh_token", 3600L, "role");
243 when(restClient.get(anyString(), any())).thenThrow(new HttpSalusApiException(404, "not found"));
245 var salusApi = new SalusApi(username, password, baseUrl, restClient, mapper, clock);
246 setAuthToken(salusApi, restClient, mapper, authToken);
249 ThrowingCallable result = () -> salusApi.findDeviceProperties("invalid_dsn");
252 assertThatThrownBy(result).isInstanceOf(HttpSalusApiException.class).hasMessage("HTTP Error 404: not found");
255 // Login with incorrect credentials 3 times throws HttpForbiddenException
257 @DisplayName("Login with incorrect credentials 3 times throws HttpForbiddenException")
258 public void testLoginWithIncorrectCredentials3TimesThrowsHttpForbiddenException() throws Exception {
260 var username = "incorrect_username";
261 var password = "incorrect_password".toCharArray();
262 var baseUrl = "https://example.com";
263 var restClient = mock(RestClient.class);
264 var mapper = mock(GsonMapper.class);
265 var clock = Clock.systemDefaultZone();
267 when(restClient.post(anyString(), any(), any()))
268 .thenThrow(new HttpSalusApiException(403, "forbidden_error_json"));
270 var salusApi = new SalusApi(username, password, baseUrl, restClient, mapper, clock);
273 ThrowingCallable findDevicesResponse = salusApi::findDevices;
276 assertThatThrownBy(findDevicesResponse).isInstanceOf(HttpSalusApiException.class)
277 .hasMessage("HTTP Error 403: forbidden_error_json");
280 private void setAuthToken(SalusApi salusApi, RestClient restClient, GsonMapper mapper, AuthToken authToken)
281 throws SalusApiException {
282 var username = "correct_username";
283 var password = "correct_password".toCharArray();
284 var inputBody = "login_param_json";
285 when(mapper.loginParam(username, password)).thenReturn(inputBody);
286 var authTokenJson = "auth_token";
287 when(mapper.authToken(authTokenJson)).thenReturn(authToken);
289 when(restClient.post(endsWith("/users/sign_in.json"), eq(new RestClient.Content(inputBody, "application/json")),
290 any())).thenReturn(authTokenJson);