2 * Copyright (c) 2010-2023 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.webthing.internal.link;
15 import static org.junit.jupiter.api.Assertions.*;
16 import static org.mockito.Mockito.*;
19 import java.nio.file.Files;
20 import java.nio.file.Paths;
22 import java.util.Objects;
23 import java.util.concurrent.ConcurrentHashMap;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.junit.jupiter.api.Test;
27 import org.openhab.binding.webthing.internal.ChannelHandler;
28 import org.openhab.binding.webthing.internal.channel.Channels;
29 import org.openhab.binding.webthing.internal.client.Mocks;
30 import org.openhab.binding.webthing.internal.client.WebthingTest;
31 import org.openhab.binding.webthing.internal.client.dto.PropertyStatusMessage;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.HSBType;
34 import org.openhab.core.library.types.OnOffType;
35 import org.openhab.core.library.types.OpenClosedType;
36 import org.openhab.core.library.types.PercentType;
37 import org.openhab.core.library.types.StringType;
38 import org.openhab.core.thing.ChannelUID;
39 import org.openhab.core.thing.ThingUID;
40 import org.openhab.core.types.Command;
41 import org.openhab.core.types.State;
43 import com.google.gson.Gson;
48 * Please consider that changes of 'ItemType<->PropertyType mapping' validated by this test
49 * will break the compatibility to former releases.
51 * @author Gregor Roth - Initial contribution
54 public class WebthingChannelLinkTest {
55 private final Gson gson = new Gson();
58 public void testChannelToProperty() throws Exception {
59 var httpClient = mock(org.eclipse.jetty.client.HttpClient.class);
60 var request = Mocks.mockRequest(null, load("/awning_response.json"));
61 when(httpClient.newRequest(URI.create("http://example.org:8090/0"))).thenReturn(request);
63 var request2 = Mocks.mockRequest("{\"target_position\":10}", load("/awning_property.json"));
64 when(httpClient.newRequest(URI.create("http://example.org:8090/0/properties/target_position")))
65 .thenReturn(request2);
67 var thingUID = new ThingUID("webthing", "anwing");
68 var channelUID = Channels.createChannelUID(thingUID, "target_position");
70 var webthing = WebthingTest.createTestWebthing("http://example.org:8090/0", httpClient);
71 var channel = Channels.createChannel(thingUID, "target_position",
72 Objects.requireNonNull(webthing.getPropertyDescription("target_position")));
74 var testWebthingThingHandler = new TestWebthingThingHandler();
75 ChannelToPropertyLink.establish(testWebthingThingHandler, channel, webthing, "target_position");
77 testWebthingThingHandler.listeners.get(channelUID).onItemStateChanged(channelUID, new DecimalType(10));
81 public void testChannelToPropertyServerError() throws Exception {
82 var httpClient = mock(org.eclipse.jetty.client.HttpClient.class);
83 var request = Mocks.mockRequest(null, load("/awning_response.json"));
84 when(httpClient.newRequest(URI.create("http://example.org:8090/0"))).thenReturn(request);
86 var request2 = Mocks.mockRequest("{\"target_position\":130}", load("/awning_property.json"), 200, 500);
87 when(httpClient.newRequest(URI.create("http://example.org:8090/0/properties/target_position")))
88 .thenReturn(request2);
90 var thingUID = new ThingUID("webthing", "anwing");
91 var channelUID = Channels.createChannelUID(thingUID, "target_position");
93 var webthing = WebthingTest.createTestWebthing("http://example.org:8090/0", httpClient);
94 var channel = Channels.createChannel(thingUID, "target_position",
95 Objects.requireNonNull(webthing.getPropertyDescription("target_position")));
97 var testWebthingThingHandler = new TestWebthingThingHandler();
98 ChannelToPropertyLink.establish(testWebthingThingHandler, channel, webthing, "target_position");
100 testWebthingThingHandler.listeners.get(channelUID).onItemStateChanged(channelUID, new DecimalType(130));
104 public void testPropertyToChannel() throws Exception {
105 var httpClient = mock(org.eclipse.jetty.client.HttpClient.class);
106 var request = Mocks.mockRequest(null, load("/awning_response.json"));
107 when(httpClient.newRequest(URI.create("http://example.org:8090/0"))).thenReturn(request);
109 var request2 = Mocks.mockRequest("{\"target_position\":10}", load("/awning_property.json"));
110 when(httpClient.newRequest(URI.create("http://example.org:8090/0/properties/target_position")))
111 .thenReturn(request2);
113 var thingUID = new ThingUID("webthing", "anwing");
114 var channelUID = Channels.createChannelUID(thingUID, "target_position");
116 var errorHandler = new WebthingTest.ErrorHandler();
117 var websocketConnectionFactory = new WebthingTest.TestWebsocketConnectionFactory();
118 var webthing = WebthingTest.createTestWebthing("http://example.org:8090/0", httpClient, errorHandler,
119 websocketConnectionFactory);
120 var channel = Channels.createChannel(thingUID, "target_position",
121 Objects.requireNonNull(webthing.getPropertyDescription("target_position")));
123 var testWebthingThingHandler = new TestWebthingThingHandler();
124 PropertyToChannelLink.establish(webthing, "target_position", testWebthingThingHandler, channel);
126 var message = new PropertyStatusMessage();
127 message.messageType = "propertyStatus";
128 message.data = Map.of("target_position", 77);
129 websocketConnectionFactory.webSocketRef.get().sendToClient(message);
131 assertEquals(new DecimalType(77), testWebthingThingHandler.itemState.get(channelUID));
135 public void testDataTypeMapping() throws Exception {
136 performDataTypeMappingTest("level_prop", 56.5, new DecimalType(56.5), 3.5, new DecimalType(3.5));
137 performDataTypeMappingTest("level_unit_prop", 10, new PercentType(10), 90, new PercentType(90));
138 performDataTypeMappingTest("thermo_prop", "off", new StringType("off"), "auto", new StringType("auto"));
139 performDataTypeMappingTest("temp_prop", 18.6, new DecimalType(18.6), 23.2, new DecimalType(23.2));
140 performDataTypeMappingTest("targettemp_prop", 18.6, new DecimalType(18.6), 23.2, new DecimalType(23.2));
141 performDataTypeMappingTest("open_prop", true, OpenClosedType.OPEN, false, OpenClosedType.CLOSED);
142 performDataTypeMappingTest("colortemp_prop", 10, new PercentType(10), 60, new PercentType(60));
143 performDataTypeMappingTest("color_prop", "#f2fe00", new HSBType("63,100,100"), "#ff0000",
144 new HSBType("0.0,100.0,100.0"));
145 performDataTypeMappingTest("colormode_prop", "color", new StringType("color"), "temperature",
146 new StringType("temperature"));
147 performDataTypeMappingTest("brightness_prop", 33, new PercentType(33), 65, new PercentType(65));
148 performDataTypeMappingTest("voltage_prop", 4.5, new DecimalType(4.5), 30.2, new DecimalType(30.2));
149 performDataTypeMappingTest("heating_prop", "off", new StringType("off"), "cooling", new StringType("cooling"));
150 performDataTypeMappingTest("onoff_prop", true, OnOffType.ON, false, OnOffType.OFF);
151 performDataTypeMappingTest("string_prop", "initial", new StringType("initial"), "updated",
152 new StringType("updated"));
153 performDataTypeMappingTest("number_prop", 80.5, new DecimalType(80.5), 60.9, new DecimalType(60.9));
154 performDataTypeMappingTest("integer_prop", 11, new DecimalType(11), 77, new DecimalType(77));
155 performDataTypeMappingTest("boolean_prop", true, OnOffType.ON, false, OnOffType.OFF);
158 private void performDataTypeMappingTest(String propertyName, Object initialValue, State initialState,
159 Object updatedValue, State updatedState) throws Exception {
160 var httpClient = mock(org.eclipse.jetty.client.HttpClient.class);
161 var request = Mocks.mockRequest(null, load("/datatypes_test_response.json"));
162 when(httpClient.newRequest(URI.create("http://example.org:8090/"))).thenReturn(request);
164 var request2 = Mocks.mockRequest(gson.toJson(Map.of(propertyName, updatedValue)),
165 gson.toJson(Map.of(propertyName, initialValue)));
166 when(httpClient.newRequest(URI.create("http://example.org:8090/properties/" + propertyName)))
167 .thenReturn(request2);
169 var thingUID = new ThingUID("webthing", "test");
170 var channelUID = Channels.createChannelUID(thingUID, propertyName);
172 var errorHandler = new WebthingTest.ErrorHandler();
173 var websocketConnectionFactory = new WebthingTest.TestWebsocketConnectionFactory();
174 var webthing = WebthingTest.createTestWebthing("http://example.org:8090/", httpClient, errorHandler,
175 websocketConnectionFactory);
176 var channel = Channels.createChannel(thingUID, propertyName,
177 Objects.requireNonNull(webthing.getPropertyDescription(propertyName)));
179 var testWebthingThingHandler = new TestWebthingThingHandler();
181 PropertyToChannelLink.establish(webthing, propertyName, testWebthingThingHandler, channel);
183 var message = new PropertyStatusMessage();
184 message.messageType = "propertyStatus";
185 message.data = Map.of(propertyName, initialValue);
186 websocketConnectionFactory.webSocketRef.get().sendToClient(message);
188 Command actualState = testWebthingThingHandler.itemState.get(channelUID);
189 if ((actualState instanceof HSBType actualHsb) && (initialState instanceof HSBType initialStateHsb)) {
190 assertTrue(actualHsb.closeTo(initialStateHsb, 0.01));
192 assertEquals(initialState, actualState);
195 ChannelToPropertyLink.establish(testWebthingThingHandler, channel, webthing, propertyName);
196 testWebthingThingHandler.listeners.get(channelUID).onItemStateChanged(channelUID, updatedState);
199 public static String load(String name) throws Exception {
200 return new String(Files.readAllBytes(Paths.get(WebthingTest.class.getResource(name).toURI())));
203 private static class TestWebthingThingHandler implements ChannelHandler {
204 public final Map<ChannelUID, ItemChangedListener> listeners = new ConcurrentHashMap<>();
205 public final Map<ChannelUID, Command> itemState = new ConcurrentHashMap<>();
208 public void observeChannel(ChannelUID channelUID, ItemChangedListener listener) {
209 listeners.put(channelUID, listener);
213 public void updateItemState(ChannelUID channelUID, Command command) {
214 itemState.put(channelUID, command);