2 * Copyright (c) 2010-2022 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.wundergroundupdatereceiver.internal;
15 import static org.hamcrest.MatcherAssert.assertThat;
16 import static org.hamcrest.Matchers.*;
17 import static org.hamcrest.core.Is.is;
18 import static org.mockito.Mockito.*;
19 import static org.mockito.MockitoAnnotations.openMocks;
20 import static org.openhab.binding.wundergroundupdatereceiver.internal.WundergroundUpdateReceiverBindingConstants.*;
22 import java.io.IOException;
23 import java.util.Arrays;
24 import java.util.List;
26 import java.util.stream.Collectors;
28 import javax.servlet.ServletException;
29 import javax.servlet.http.HttpServletResponse;
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.eclipse.jetty.http.HttpFields;
33 import org.eclipse.jetty.http.HttpURI;
34 import org.eclipse.jetty.http.HttpVersion;
35 import org.eclipse.jetty.http.MetaData;
36 import org.eclipse.jetty.server.HttpChannel;
37 import org.eclipse.jetty.server.Request;
38 import org.junit.jupiter.api.BeforeEach;
39 import org.junit.jupiter.api.Test;
40 import org.mockito.Answers;
41 import org.openhab.core.config.core.Configuration;
42 import org.openhab.core.thing.Channel;
43 import org.openhab.core.thing.DefaultSystemChannelTypeProvider;
44 import org.openhab.core.thing.Thing;
45 import org.openhab.core.thing.ThingUID;
46 import org.openhab.core.thing.binding.ThingHandlerCallback;
47 import org.openhab.core.thing.binding.builder.ThingBuilder;
48 import org.openhab.core.thing.internal.type.StateChannelTypeBuilderImpl;
49 import org.openhab.core.thing.internal.type.TriggerChannelTypeBuilderImpl;
50 import org.openhab.core.thing.type.ChannelTypeProvider;
51 import org.openhab.core.thing.type.ChannelTypeRegistry;
52 import org.openhab.core.thing.type.ChannelTypeUID;
53 import org.osgi.service.http.HttpService;
54 import org.osgi.service.http.NamespaceException;
57 * @author Daniel Demus - Initial contribution
60 class WundergroundUpdateReceiverDiscoveryServiceTest {
62 private static final String STATION_ID_1 = "abcd1234";
63 private static final String REQ_STATION_ID = "dfggger";
64 private static final ThingUID TEST_THING_UID = new ThingUID(THING_TYPE_UPDATE_RECEIVER, "test-receiver");
72 void aRequestWithAnUnregisteredStationidIsAddedToTheQueueOnce()
73 throws ServletException, NamespaceException, IOException {
75 final String queryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "tempf=26.1&" + "humidity=74&" + "dewptf=18.9&"
76 + "windchillf=26.1&" + "winddir=14&" + "windspeedmph=1.34&" + "windgustmph=2.46&" + "rainin=0.00&"
77 + "dailyrainin=0.00&" + "weeklyrainin=0.00&" + "monthlyrainin=0.08&" + "yearlyrainin=3.06&"
78 + "solarradiation=42.24&" + "UV=1&indoortempf=69.3&" + "indoorhumidity=32&" + "baromin=30.39&"
79 + "AqNOX=21&" + "lowbatt=1&" + "dateutc=2021-02-07%2014:04:03&" + "softwaretype=WH2600%20V2.2.8&"
80 + "action=updateraw&" + "realtime=1&" + "rtfreq=5";
81 WundergroundUpdateReceiverDiscoveryService discoveryService = mock(
82 WundergroundUpdateReceiverDiscoveryService.class);
83 HttpService httpService = mock(HttpService.class);
84 WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(httpService, discoveryService);
85 WundergroundUpdateReceiverHandler handler = mock(WundergroundUpdateReceiverHandler.class);
86 when(handler.getStationId()).thenReturn(STATION_ID_1);
87 sut.addHandler(handler);
88 when(discoveryService.isBackgroundDiscoveryEnabled()).thenReturn(false);
91 verify(httpService).registerServlet(eq(WundergroundUpdateReceiverServlet.SERVLET_URL), eq(sut), any(), any());
92 assertThat(sut.isActive(), is(true));
94 HttpChannel httpChannel = mock(HttpChannel.class);
95 MetaData.Request request = new MetaData.Request("GET",
96 new HttpURI("http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + queryString),
97 HttpVersion.HTTP_1_1, new HttpFields());
98 Request req = new Request(httpChannel, null);
99 req.setMetaData(request);
102 sut.doGet(req, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
105 verify(handler, never()).updateChannelStates(any());
106 verify(discoveryService).addUnhandledStationId(eq("dfggger"), any());
107 assertThat(sut.isActive(), is(true));
111 void multipleIndexedParametersOfTheSameChanneltypeAreCorrectlyDiscovered() throws IOException {
113 final String queryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "temp1f=26.1&" + "humidity=74&" + "temp2f=25.1&"
114 + "lowbatt=1&" + "soilmoisture1=78&" + "soilmoisture2=73&" + "dateutc=2021-02-07%2014:04:03&"
115 + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&" + "realtime=1&" + "rtfreq=5";
116 MetaData.Request request = new MetaData.Request("GET",
117 new HttpURI("http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + queryString),
118 HttpVersion.HTTP_1_1, new HttpFields());
119 HttpChannel httpChannel = mock(HttpChannel.class);
120 Request req = new Request(httpChannel, null);
121 req.setMetaData(request);
123 TestChannelTypeRegistry channelTypeRegistry = new TestChannelTypeRegistry();
124 WundergroundUpdateReceiverDiscoveryService discoveryService = new WundergroundUpdateReceiverDiscoveryService(
126 discoveryService.addUnhandledStationId(REQ_STATION_ID, req.getParameterMap());
127 HttpService httpService = mock(HttpService.class);
128 WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(httpService, discoveryService);
129 Thing thing = ThingBuilder.create(SUPPORTED_THING_TYPES_UIDS.stream().findFirst().get(), TEST_THING_UID)
130 .withConfiguration(new Configuration(Map.of(REPRESENTATION_PROPERTY, REQ_STATION_ID)))
131 .withLabel("test thing").withLocation("location").build();
132 WundergroundUpdateReceiverHandler handler = new WundergroundUpdateReceiverHandler(thing, sut, discoveryService,
133 new WundergroundUpdateReceiverUnknownChannelTypeProvider(), channelTypeRegistry);
134 handler.setCallback(mock(ThingHandlerCallback.class));
135 handler.initialize();
136 sut.addHandler(handler);
142 assertThat(sut.isActive(), is(true));
145 sut.doGet(req, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
148 assertThat(sut.getHandlers().size(), is(1));
149 assertThat(sut.getHandlers().containsKey(REQ_STATION_ID), is(true));
150 assertThat(handler.getThing().getChannels().stream()
151 .filter(channel -> channel.getChannelTypeUID() == TEMPERATURE_CHANNELTYPEUID).count(), is(2L));
155 void unregisteredChannelsAreAddedOnTheFlyWhenDiscovered() throws IOException {
157 final String firstDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "tempf=26.1&" + "humidity=74&"
158 + "dateutc=2021-02-07%2014:04:03&" + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&"
159 + "realtime=1&" + "rtfreq=5";
160 MetaData.Request request1 = new MetaData.Request("GET", new HttpURI(
161 "http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + firstDeviceQueryString),
162 HttpVersion.HTTP_1_1, new HttpFields());
163 HttpChannel httpChannel = mock(HttpChannel.class);
164 Request req1 = new Request(httpChannel, null);
165 req1.setMetaData(request1);
167 TestChannelTypeRegistry channelTypeRegistry = new TestChannelTypeRegistry();
168 WundergroundUpdateReceiverDiscoveryService discoveryService = new WundergroundUpdateReceiverDiscoveryService(
170 discoveryService.addUnhandledStationId(REQ_STATION_ID, req1.getParameterMap());
171 HttpService httpService = mock(HttpService.class);
172 WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(httpService, discoveryService);
173 Thing thing = ThingBuilder.create(SUPPORTED_THING_TYPES_UIDS.stream().findFirst().get(), TEST_THING_UID)
174 .withConfiguration(new Configuration(Map.of(REPRESENTATION_PROPERTY, REQ_STATION_ID)))
175 .withLabel("test thing").withLocation("location").build();
176 WundergroundUpdateReceiverHandler handler = new WundergroundUpdateReceiverHandler(thing, sut, discoveryService,
177 new WundergroundUpdateReceiverUnknownChannelTypeProvider(), channelTypeRegistry);
178 handler.setCallback(mock(ThingHandlerCallback.class));
181 handler.initialize();
182 sut.addHandler(handler);
185 ChannelTypeUID[] expectedBefore = new ChannelTypeUID[] { TEMPERATURE_CHANNELTYPEUID, HUMIDITY_CHANNELTYPEUID,
186 DATEUTC_CHANNELTYPEUID, SOFTWARETYPE_CHANNELTYPEUID, REALTIME_FREQUENCY_CHANNELTYPEUID,
187 LAST_QUERY_STATE_CHANNELTYPEUID, LAST_RECEIVED_DATETIME_CHANNELTYPEUID,
188 LAST_QUERY_TRIGGER_CHANNELTYPEUID };
189 List<ChannelTypeUID> before = handler.getThing().getChannels().stream().map(Channel::getChannelTypeUID)
190 .collect(Collectors.toList());
191 assertThat(before, hasItems(expectedBefore));
194 final String secondDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "lowbatt=1&" + "soilmoisture1=78&"
195 + "soilmoisture2=73&" + "solarradiation=42.24&" + "dateutc=2021-02-07%2014:04:03&"
196 + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&" + "realtime=1&" + "rtfreq=5";
197 MetaData.Request request = new MetaData.Request("GET", new HttpURI(
198 "http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + secondDeviceQueryString),
199 HttpVersion.HTTP_1_1, new HttpFields());
200 Request req2 = new Request(httpChannel, null);
201 req2.setMetaData(request);
205 assertThat(sut.isActive(), is(true));
208 sut.doGet(req2, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
211 ChannelTypeUID[] expectedActual = Arrays.copyOf(expectedBefore, expectedBefore.length + 3);
212 System.arraycopy(new ChannelTypeUID[] { LOW_BATTERY_CHANNELTYPEUID, SOIL_MOISTURE_CHANNELTYPEUID,
213 SOLARRADIATION_CHANNELTYPEUID }, 0, expectedActual, expectedBefore.length, 3);
214 List<ChannelTypeUID> actual = handler.getThing().getChannels().stream().map(Channel::getChannelTypeUID)
215 .collect(Collectors.toList());
216 assertThat(actual, hasItems(expectedActual));
219 class TestChannelTypeRegistry extends ChannelTypeRegistry {
221 TestChannelTypeRegistry() {
223 ChannelTypeProvider provider = mock(ChannelTypeProvider.class);
224 when(provider.getChannelType(eq(SOFTWARETYPE_CHANNELTYPEUID), any())).thenReturn(
225 new StateChannelTypeBuilderImpl(SOFTWARETYPE_CHANNELTYPEUID, "Software type", "String").build());
226 when(provider.getChannelType(eq(TEMPERATURE_CHANNELTYPEUID), any()))
227 .thenReturn(DefaultSystemChannelTypeProvider.SYSTEM_OUTDOOR_TEMPERATURE);
228 when(provider.getChannelType(eq(SOIL_MOISTURE_CHANNELTYPEUID), any()))
229 .thenReturn(new StateChannelTypeBuilderImpl(SOIL_MOISTURE_CHANNELTYPEUID, "Soilmoisture",
230 "Number:Dimensionless").build());
231 when(provider.getChannelType(eq(SOLARRADIATION_CHANNELTYPEUID), any()))
232 .thenReturn(new StateChannelTypeBuilderImpl(SOLARRADIATION_CHANNELTYPEUID, "Solar Radiation",
233 "Number:Intensity").build());
234 when(provider.getChannelType(eq(HUMIDITY_CHANNELTYPEUID), any())).thenReturn(
235 new StateChannelTypeBuilderImpl(HUMIDITY_CHANNELTYPEUID, "Humidity", "Number:Dimensionless")
237 when(provider.getChannelType(eq(DATEUTC_CHANNELTYPEUID), any())).thenReturn(
238 new StateChannelTypeBuilderImpl(DATEUTC_CHANNELTYPEUID, "Last Updated", "String").build());
239 when(provider.getChannelType(eq(LOW_BATTERY_CHANNELTYPEUID), any()))
240 .thenReturn(DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_LOW_BATTERY);
241 when(provider.getChannelType(eq(REALTIME_FREQUENCY_CHANNELTYPEUID), any())).thenReturn(
242 new StateChannelTypeBuilderImpl(REALTIME_FREQUENCY_CHANNELTYPEUID, "Realtime frequency", "Number")
244 when(provider.getChannelType(eq(LAST_QUERY_STATE_CHANNELTYPEUID), any())).thenReturn(
245 new StateChannelTypeBuilderImpl(LAST_QUERY_STATE_CHANNELTYPEUID, "The last query", "String")
247 when(provider.getChannelType(eq(LAST_RECEIVED_DATETIME_CHANNELTYPEUID), any())).thenReturn(
248 new StateChannelTypeBuilderImpl(LAST_RECEIVED_DATETIME_CHANNELTYPEUID, "Last Received", "DateTime")
250 when(provider.getChannelType(eq(LAST_QUERY_TRIGGER_CHANNELTYPEUID), any())).thenReturn(
251 new TriggerChannelTypeBuilderImpl(LAST_QUERY_TRIGGER_CHANNELTYPEUID, "The last query").build());
252 this.addChannelTypeProvider(provider);
253 this.addChannelTypeProvider(new WundergroundUpdateReceiverUnknownChannelTypeProvider());