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.hdpowerview;
15 import static org.junit.jupiter.api.Assertions.*;
16 import static org.openhab.binding.hdpowerview.internal.api.ActuatorClass.*;
17 import static org.openhab.binding.hdpowerview.internal.api.CoordinateSystem.*;
19 import java.io.IOException;
20 import java.nio.file.Files;
21 import java.nio.file.Paths;
22 import java.util.List;
23 import java.util.regex.Pattern;
24 import java.util.stream.Collectors;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.eclipse.jetty.client.HttpClient;
29 import org.junit.jupiter.api.Test;
30 import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets;
31 import org.openhab.binding.hdpowerview.internal.HubMaintenanceException;
32 import org.openhab.binding.hdpowerview.internal.HubProcessingException;
33 import org.openhab.binding.hdpowerview.internal.api.CoordinateSystem;
34 import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
35 import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections;
36 import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections.SceneCollection;
37 import org.openhab.binding.hdpowerview.internal.api.responses.Scenes;
38 import org.openhab.binding.hdpowerview.internal.api.responses.Scenes.Scene;
39 import org.openhab.binding.hdpowerview.internal.api.responses.Shade;
40 import org.openhab.binding.hdpowerview.internal.api.responses.Shades;
41 import org.openhab.binding.hdpowerview.internal.api.responses.Shades.ShadeData;
42 import org.openhab.core.library.types.PercentType;
43 import org.openhab.core.types.State;
44 import org.openhab.core.types.UnDefType;
46 import com.google.gson.Gson;
47 import com.google.gson.JsonParseException;
50 * Unit tests for HD PowerView binding
52 * @author Andrew Fiddian-Green - Initial contribution
53 * @author Jacob Laursen - Add support for scene groups
56 public class HDPowerViewJUnitTests {
58 private static final Pattern VALID_IP_V4_ADDRESS = Pattern
59 .compile("\\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.|$)){4}\\b");
62 * load a test JSON string from a file
64 private String loadJson(String fileName) {
66 return Files.readAllLines(Paths.get(String.format("src/test/resources/%s.json", fileName))).stream()
67 .collect(Collectors.joining());
68 } catch (IOException e) {
75 * Run a series of ONLINE tests on the communication with a hub
77 * @param hubIPAddress must be a valid hub IP address to run the
78 * tests on; or an INVALID IP address to
80 * @param allowShadeMovementCommands set to true if you accept that the tests
81 * shall physically move the shades
84 public void testOnlineCommunication() {
86 * NOTE: in order to actually run these tests you must have a hub physically
87 * available, and its IP address must be correctly configured in the
88 * "hubIPAddress" string constant e.g. "192.168.1.123"
90 String hubIPAddress = "192.168.1.xxx";
93 * NOTE: set allowShadeMovementCommands = true if you accept physically moving
94 * the shades during these tests
96 boolean allowShadeMovementCommands = false;
98 if (VALID_IP_V4_ADDRESS.matcher(hubIPAddress).matches()) {
99 // ==== initialize stuff ====
100 HttpClient client = new HttpClient();
101 assertNotNull(client);
103 // ==== start the client ====
106 assertTrue(client.isStarted());
107 } catch (Exception e) {
108 fail(e.getMessage());
111 HDPowerViewWebTargets webTargets = new HDPowerViewWebTargets(client, hubIPAddress);
112 assertNotNull(webTargets);
114 // ==== exercise some code ====
119 test = ShadePosition.create(ZERO_IS_CLOSED, 0);
121 pos = test.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
122 assertEquals(PercentType.class, pos.getClass());
123 assertEquals(0, ((PercentType) pos).intValue());
124 pos = test.getState(PRIMARY_ACTUATOR, VANE_COORDS);
125 assertTrue(UnDefType.UNDEF.equals(pos));
127 // shade fully down (method 1)
128 test = ShadePosition.create(ZERO_IS_CLOSED, 100);
130 pos = test.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
131 assertEquals(PercentType.class, pos.getClass());
132 assertEquals(100, ((PercentType) pos).intValue());
133 pos = test.getState(PRIMARY_ACTUATOR, VANE_COORDS);
134 assertEquals(PercentType.class, pos.getClass());
135 assertEquals(0, ((PercentType) pos).intValue());
137 // shade fully down (method 2)
138 test = ShadePosition.create(VANE_COORDS, 0);
140 pos = test.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
141 assertEquals(PercentType.class, pos.getClass());
142 assertEquals(100, ((PercentType) pos).intValue());
143 pos = test.getState(PRIMARY_ACTUATOR, VANE_COORDS);
144 assertEquals(PercentType.class, pos.getClass());
145 assertEquals(0, ((PercentType) pos).intValue());
147 // shade fully down (method 2) and vane fully open
148 test = ShadePosition.create(VANE_COORDS, 100);
150 pos = test.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
151 assertEquals(PercentType.class, pos.getClass());
152 assertEquals(100, ((PercentType) pos).intValue());
153 pos = test.getState(PRIMARY_ACTUATOR, VANE_COORDS);
154 assertEquals(PercentType.class, pos.getClass());
155 assertEquals(100, ((PercentType) pos).intValue());
159 ShadePosition shadePos = null;
161 Shades shadesX = null;
163 // ==== get all shades ====
165 shadesX = webTargets.getShades();
166 assertNotNull(shadesX);
168 List<ShadeData> shadesData = shadesX.shadeData;
169 assertNotNull(shadesData);
170 assertTrue(!shadesData.isEmpty());
173 shadeData = shadesData.get(0);
174 assertNotNull(shadeData);
175 assertTrue(shadeData.getName().length() > 0);
176 shadePos = shadeData.positions;
177 assertNotNull(shadePos);
179 ShadeData shadeZero = shadesData.get(0);
180 assertNotNull(shadeZero);
181 shadeId = shadeZero.id;
182 assertNotEquals(0, shadeId);
184 for (ShadeData shadexData : shadesData) {
185 String shadeName = shadexData.getName();
186 assertNotNull(shadeName);
188 } catch (JsonParseException | HubProcessingException | HubMaintenanceException e) {
189 fail(e.getMessage());
192 // ==== get all scenes ====
195 Scenes scenes = webTargets.getScenes();
196 assertNotNull(scenes);
198 List<Scene> scenesData = scenes.sceneData;
199 assertNotNull(scenesData);
200 assertTrue(!scenesData.isEmpty());
202 Scene sceneZero = scenesData.get(0);
203 assertNotNull(sceneZero);
204 sceneId = sceneZero.id;
205 assertTrue(sceneId > 0);
207 for (Scene scene : scenesData) {
208 String sceneName = scene.getName();
209 assertNotNull(sceneName);
211 } catch (JsonParseException | HubProcessingException | HubMaintenanceException e) {
212 fail(e.getMessage());
215 // ==== refresh a specific shade ====
219 assertNotEquals(0, shadeId);
220 shade = webTargets.refreshShadePosition(shadeId);
221 assertNotNull(shade);
222 } catch (HubProcessingException | HubMaintenanceException e) {
223 fail(e.getMessage());
226 // ==== move a specific shade ====
228 assertNotEquals(0, shadeId);
229 assertNotNull(shade);
231 ShadeData shadeData = shade.shade;
232 assertNotNull(shadeData);
233 ShadePosition positions = shadeData.positions;
234 assertNotNull(positions);
235 CoordinateSystem coordSys = positions.getCoordinateSystem(PRIMARY_ACTUATOR);
236 assertNotNull(coordSys);
238 pos = positions.getState(PRIMARY_ACTUATOR, coordSys);
239 assertEquals(PercentType.class, pos.getClass());
241 pos = positions.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
242 assertEquals(PercentType.class, pos.getClass());
244 int position = ((PercentType) pos).intValue();
245 position = position + ((position <= 10) ? 5 : -5);
247 ShadePosition newPos = ShadePosition.create(ZERO_IS_CLOSED, position);
248 assertNotNull(newPos);
250 if (allowShadeMovementCommands) {
251 webTargets.moveShade(shadeId, newPos);
253 } catch (HubProcessingException | HubMaintenanceException e) {
254 fail(e.getMessage());
257 // ==== activate a specific scene ====
258 if (allowShadeMovementCommands) {
260 assertNotNull(sceneId);
261 webTargets.activateScene(sceneId);
262 } catch (HubProcessingException | HubMaintenanceException e) {
263 fail(e.getMessage());
267 // ==== test stop command ====
268 if (allowShadeMovementCommands) {
270 assertNotNull(sceneId);
271 webTargets.stopShade(shadeId);
272 } catch (HubProcessingException | HubMaintenanceException e) {
273 fail(e.getMessage());
277 // ==== stop the client ====
278 if (client.isRunning()) {
281 } catch (Exception e) {
282 fail(e.getMessage());
289 * Test generic JSON shades response
292 public void shadeResponseIsParsedCorrectly() throws JsonParseException {
293 final Gson gson = new Gson();
296 String json = loadJson("shades");
297 assertNotEquals("", json);
298 shades = gson.fromJson(json, Shades.class);
299 assertNotNull(shades);
303 * Test generic JSON scene response
306 public void sceneResponseIsParsedCorrectly() throws JsonParseException {
307 final Gson gson = new Gson();
308 String json = loadJson("scenes");
309 assertNotEquals("", json);
312 Scenes scenes = gson.fromJson(json, Scenes.class);
313 assertNotNull(scenes);
316 List<Scene> sceneData = scenes.sceneData;
317 assertNotNull(sceneData);
319 assertEquals(4, sceneData.size());
321 Scene scene = sceneData.get(0);
322 assertEquals("Door Open", scene.getName());
323 assertEquals(18097, scene.id);
327 * Test generic JSON scene collection response
330 public void sceneCollectionResponseIsParsedCorrectly() throws JsonParseException {
331 final Gson gson = new Gson();
332 String json = loadJson("sceneCollections");
333 assertNotEquals("", json);
336 SceneCollections sceneCollections = gson.fromJson(json, SceneCollections.class);
337 assertNotNull(sceneCollections);
339 List<SceneCollection> sceneCollectionData = sceneCollections.sceneCollectionData;
340 assertNotNull(sceneCollectionData);
342 assertEquals(1, sceneCollectionData.size());
344 SceneCollection sceneCollection = sceneCollectionData.get(0);
345 assertEquals("Børn op", sceneCollection.getName());
346 assertEquals(27119, sceneCollection.id);
350 * Test the JSON parsing for a duette top down bottom up shade
353 public void duetteTopDownBottomUpShadeIsParsedCorrectly() throws JsonParseException {
354 final Gson gson = new Gson();
355 String json = loadJson("duette");
356 assertNotEquals("", json);
359 Shades shades = gson.fromJson(json, Shades.class);
360 assertNotNull(shades);
362 List<ShadeData> shadesData = shades.shadeData;
363 assertNotNull(shadesData);
365 assertEquals(1, shadesData.size());
367 ShadeData shadeData = shadesData.get(0);
368 assertNotNull(shadeData);
370 assertEquals("Gardin 1", shadeData.getName());
371 assertEquals(63778, shadeData.id);
373 ShadePosition shadePos = shadeData.positions;
374 assertNotNull(shadePos);
375 assertEquals(ZERO_IS_CLOSED, shadePos.getCoordinateSystem(PRIMARY_ACTUATOR));
377 State pos = shadePos.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
378 assertEquals(PercentType.class, pos.getClass());
379 assertEquals(59, ((PercentType) pos).intValue());
381 pos = shadePos.getState(SECONDARY_ACTUATOR, ZERO_IS_OPEN);
382 assertEquals(PercentType.class, pos.getClass());
383 assertEquals(35, ((PercentType) pos).intValue());
385 pos = shadePos.getState(PRIMARY_ACTUATOR, VANE_COORDS);
386 assertEquals(UnDefType.class, pos.getClass());
388 assertEquals(3, shadeData.batteryStatus);
390 assertEquals(4, shadeData.signalStrength);