]> git.basschouten.com Git - openhab-addons.git/blob
e504fe5df52254cfbddd4a0387e6eff93207dd30
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.wundergroundupdatereceiver.internal;
14
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.*;
21
22 import java.io.IOException;
23 import java.util.Arrays;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.stream.Collectors;
27
28 import javax.servlet.ServletException;
29 import javax.servlet.http.HttpServletResponse;
30
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.ManagedThingProvider;
45 import org.openhab.core.thing.Thing;
46 import org.openhab.core.thing.ThingUID;
47 import org.openhab.core.thing.binding.ThingHandlerCallback;
48 import org.openhab.core.thing.binding.builder.ThingBuilder;
49 import org.openhab.core.thing.internal.type.StateChannelTypeBuilderImpl;
50 import org.openhab.core.thing.internal.type.TriggerChannelTypeBuilderImpl;
51 import org.openhab.core.thing.type.ChannelTypeProvider;
52 import org.openhab.core.thing.type.ChannelTypeRegistry;
53 import org.openhab.core.thing.type.ChannelTypeUID;
54 import org.osgi.service.http.HttpService;
55 import org.osgi.service.http.NamespaceException;
56
57 /**
58  * @author Daniel Demus - Initial contribution
59  */
60 @NonNullByDefault({})
61 class WundergroundUpdateReceiverDiscoveryServiceTest {
62
63     private static final String STATION_ID_1 = "abcd1234";
64     private static final String REQ_STATION_ID = "dfggger";
65     private static final ThingUID TEST_THING_UID = new ThingUID(THING_TYPE_UPDATE_RECEIVER, "test-receiver");
66
67     @BeforeEach
68     public void setUp() {
69         openMocks(this);
70     }
71
72     @Test
73     void aRequestWithAnUnregisteredStationidIsAddedToTheQueueOnce()
74             throws ServletException, NamespaceException, IOException {
75         // Given
76         final String queryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "tempf=26.1&" + "humidity=74&" + "dewptf=18.9&"
77                 + "windchillf=26.1&" + "winddir=14&" + "windspeedmph=1.34&" + "windgustmph=2.46&" + "rainin=0.00&"
78                 + "dailyrainin=0.00&" + "weeklyrainin=0.00&" + "monthlyrainin=0.08&" + "yearlyrainin=3.06&"
79                 + "solarradiation=42.24&" + "UV=1&indoortempf=69.3&" + "indoorhumidity=32&" + "baromin=30.39&"
80                 + "AqNOX=21&" + "lowbatt=1&" + "dateutc=2021-02-07%2014:04:03&" + "softwaretype=WH2600%20V2.2.8&"
81                 + "action=updateraw&" + "realtime=1&" + "rtfreq=5";
82         WundergroundUpdateReceiverDiscoveryService discoveryService = mock(
83                 WundergroundUpdateReceiverDiscoveryService.class);
84         HttpService httpService = mock(HttpService.class);
85         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(httpService, discoveryService);
86         WundergroundUpdateReceiverHandler handler = mock(WundergroundUpdateReceiverHandler.class);
87         when(handler.getStationId()).thenReturn(STATION_ID_1);
88         sut.addHandler(handler);
89         when(discoveryService.isBackgroundDiscoveryEnabled()).thenReturn(false);
90
91         // Then
92         verify(httpService).registerServlet(eq(WundergroundUpdateReceiverServlet.SERVLET_URL), eq(sut), any(), any());
93         assertThat(sut.isActive(), is(true));
94
95         HttpChannel httpChannel = mock(HttpChannel.class);
96         MetaData.Request request = new MetaData.Request("GET",
97                 new HttpURI("http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + queryString),
98                 HttpVersion.HTTP_1_1, new HttpFields());
99         Request req = new Request(httpChannel, null);
100         req.setMetaData(request);
101
102         // When
103         sut.doGet(req, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
104
105         // Then
106         verify(handler, never()).updateChannelStates(any());
107         verify(discoveryService).addUnhandledStationId(eq("dfggger"), any());
108         assertThat(sut.isActive(), is(true));
109     }
110
111     @Test
112     void multipleIndexedParametersOfTheSameChanneltypeAreCorrectlyDiscovered() throws IOException {
113         // Given
114         final String queryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "temp1f=26.1&" + "humidity=74&" + "temp2f=25.1&"
115                 + "lowbatt=1&" + "soilmoisture1=78&" + "soilmoisture2=73&" + "dateutc=2021-02-07%2014:04:03&"
116                 + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&" + "realtime=1&" + "rtfreq=5";
117         MetaData.Request request = new MetaData.Request("GET",
118                 new HttpURI("http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + queryString),
119                 HttpVersion.HTTP_1_1, new HttpFields());
120         HttpChannel httpChannel = mock(HttpChannel.class);
121         Request req = new Request(httpChannel, null);
122         req.setMetaData(request);
123
124         TestChannelTypeRegistry channelTypeRegistry = new TestChannelTypeRegistry();
125         WundergroundUpdateReceiverDiscoveryService discoveryService = new WundergroundUpdateReceiverDiscoveryService(
126                 false);
127         discoveryService.addUnhandledStationId(REQ_STATION_ID, req.getParameterMap());
128         HttpService httpService = mock(HttpService.class);
129         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(httpService, discoveryService);
130         Thing thing = ThingBuilder.create(SUPPORTED_THING_TYPES_UIDS.stream().findFirst().get(), TEST_THING_UID)
131                 .withConfiguration(new Configuration(Map.of(REPRESENTATION_PROPERTY, REQ_STATION_ID)))
132                 .withLabel("test thing").withLocation("location").build();
133         ManagedThingProvider managedThingProvider = mock(ManagedThingProvider.class);
134         when(managedThingProvider.get(TEST_THING_UID)).thenReturn(thing);
135         WundergroundUpdateReceiverHandler handler = new WundergroundUpdateReceiverHandler(thing, sut, discoveryService,
136                 new WundergroundUpdateReceiverUnknownChannelTypeProvider(), channelTypeRegistry, managedThingProvider);
137         handler.setCallback(mock(ThingHandlerCallback.class));
138         handler.initialize();
139         sut.addHandler(handler);
140
141         // When
142         sut.activate();
143
144         // Then
145         assertThat(sut.isActive(), is(true));
146
147         // When
148         sut.doGet(req, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
149
150         // Then
151         assertThat(sut.getHandlers().size(), is(1));
152         assertThat(sut.getHandlers().containsKey(REQ_STATION_ID), is(true));
153         assertThat(handler.getThing().getChannels().stream()
154                 .filter(channel -> channel.getChannelTypeUID() == TEMPERATURE_CHANNELTYPEUID).count(), is(2L));
155     }
156
157     @Test
158     void unregisteredChannelsAreAddedOnTheFlyWhenDiscovered() throws IOException {
159         // Given
160         final String firstDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "tempf=26.1&" + "humidity=74&"
161                 + "dateutc=2021-02-07%2014:04:03&" + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&"
162                 + "realtime=1&" + "rtfreq=5";
163         MetaData.Request request1 = new MetaData.Request("GET", new HttpURI(
164                 "http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + firstDeviceQueryString),
165                 HttpVersion.HTTP_1_1, new HttpFields());
166         HttpChannel httpChannel = mock(HttpChannel.class);
167         Request req1 = new Request(httpChannel, null);
168         req1.setMetaData(request1);
169
170         TestChannelTypeRegistry channelTypeRegistry = new TestChannelTypeRegistry();
171         WundergroundUpdateReceiverDiscoveryService discoveryService = new WundergroundUpdateReceiverDiscoveryService(
172                 true);
173         discoveryService.addUnhandledStationId(REQ_STATION_ID, req1.getParameterMap());
174         HttpService httpService = mock(HttpService.class);
175         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(httpService, discoveryService);
176         Thing thing = ThingBuilder.create(SUPPORTED_THING_TYPES_UIDS.stream().findFirst().get(), TEST_THING_UID)
177                 .withConfiguration(new Configuration(Map.of(REPRESENTATION_PROPERTY, REQ_STATION_ID)))
178                 .withLabel("test thing").withLocation("location").build();
179         ManagedThingProvider managedThingProvider = mock(ManagedThingProvider.class);
180         when(managedThingProvider.get(any())).thenReturn(thing);
181         WundergroundUpdateReceiverHandler handler = new WundergroundUpdateReceiverHandler(thing, sut, discoveryService,
182                 new WundergroundUpdateReceiverUnknownChannelTypeProvider(), channelTypeRegistry, managedThingProvider);
183         handler.setCallback(mock(ThingHandlerCallback.class));
184
185         // When
186         handler.initialize();
187         sut.addHandler(handler);
188
189         // Then
190         ChannelTypeUID[] expectedBefore = new ChannelTypeUID[] { TEMPERATURE_CHANNELTYPEUID, HUMIDITY_CHANNELTYPEUID,
191                 DATEUTC_CHANNELTYPEUID, SOFTWARETYPE_CHANNELTYPEUID, REALTIME_FREQUENCY_CHANNELTYPEUID,
192                 LAST_QUERY_STATE_CHANNELTYPEUID, LAST_RECEIVED_DATETIME_CHANNELTYPEUID,
193                 LAST_QUERY_TRIGGER_CHANNELTYPEUID };
194         List<ChannelTypeUID> before = handler.getThing().getChannels().stream().map(Channel::getChannelTypeUID)
195                 .collect(Collectors.toList());
196         assertThat(before, hasItems(expectedBefore));
197
198         // When
199         final String secondDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "lowbatt=1&" + "soilmoisture1=78&"
200                 + "soilmoisture2=73&" + "solarradiation=42.24&" + "dateutc=2021-02-07%2014:04:03&"
201                 + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&" + "realtime=1&" + "rtfreq=5";
202         MetaData.Request request = new MetaData.Request("GET", new HttpURI(
203                 "http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + secondDeviceQueryString),
204                 HttpVersion.HTTP_1_1, new HttpFields());
205         Request req2 = new Request(httpChannel, null);
206         req2.setMetaData(request);
207         sut.activate();
208
209         // Then
210         assertThat(sut.isActive(), is(true));
211
212         // When
213         sut.doGet(req2, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
214
215         // Then
216         ChannelTypeUID[] expectedActual = Arrays.copyOf(expectedBefore, expectedBefore.length + 3);
217         System.arraycopy(new ChannelTypeUID[] { LOW_BATTERY_CHANNELTYPEUID, SOIL_MOISTURE_CHANNELTYPEUID,
218                 SOLARRADIATION_CHANNELTYPEUID }, 0, expectedActual, expectedBefore.length, 3);
219         List<ChannelTypeUID> actual = handler.getThing().getChannels().stream().map(Channel::getChannelTypeUID)
220                 .collect(Collectors.toList());
221         assertThat(actual, hasItems(expectedActual));
222     }
223
224     @Test
225     void unregisteredChannelsAreNotAddedOnUnmanagedThings() throws IOException {
226         // Given
227         final String firstDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "tempf=26.1&" + "humidity=74&"
228                 + "dateutc=2021-02-07%2014:04:03&" + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&"
229                 + "realtime=1&" + "rtfreq=5";
230         MetaData.Request request1 = new MetaData.Request("GET", new HttpURI(
231                 "http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + firstDeviceQueryString),
232                 HttpVersion.HTTP_1_1, new HttpFields());
233         HttpChannel httpChannel = mock(HttpChannel.class);
234         Request req1 = new Request(httpChannel, null);
235         req1.setMetaData(request1);
236
237         TestChannelTypeRegistry channelTypeRegistry = new TestChannelTypeRegistry();
238         WundergroundUpdateReceiverDiscoveryService discoveryService = new WundergroundUpdateReceiverDiscoveryService(
239                 true);
240         discoveryService.addUnhandledStationId(REQ_STATION_ID, req1.getParameterMap());
241         HttpService httpService = mock(HttpService.class);
242         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(httpService, discoveryService);
243         Thing thing = ThingBuilder.create(SUPPORTED_THING_TYPES_UIDS.stream().findFirst().get(), TEST_THING_UID)
244                 .withConfiguration(new Configuration(Map.of(REPRESENTATION_PROPERTY, REQ_STATION_ID)))
245                 .withLabel("test thing").withLocation("location").build();
246         ManagedThingProvider managedThingProvider = mock(ManagedThingProvider.class);
247         when(managedThingProvider.get(any())).thenReturn(null);
248         WundergroundUpdateReceiverHandler handler = new WundergroundUpdateReceiverHandler(thing, sut, discoveryService,
249                 new WundergroundUpdateReceiverUnknownChannelTypeProvider(), channelTypeRegistry, managedThingProvider);
250         handler.setCallback(mock(ThingHandlerCallback.class));
251
252         // When
253         handler.initialize();
254         sut.addHandler(handler);
255
256         // Then
257         ChannelTypeUID[] expectedBefore = new ChannelTypeUID[] { TEMPERATURE_CHANNELTYPEUID, HUMIDITY_CHANNELTYPEUID,
258                 DATEUTC_CHANNELTYPEUID, SOFTWARETYPE_CHANNELTYPEUID, REALTIME_FREQUENCY_CHANNELTYPEUID,
259                 LAST_QUERY_STATE_CHANNELTYPEUID, LAST_RECEIVED_DATETIME_CHANNELTYPEUID,
260                 LAST_QUERY_TRIGGER_CHANNELTYPEUID };
261         List<ChannelTypeUID> before = handler.getThing().getChannels().stream().map(Channel::getChannelTypeUID)
262                 .collect(Collectors.toList());
263         assertThat(before, hasItems(expectedBefore));
264
265         // When
266         final String secondDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "lowbatt=1&" + "soilmoisture1=78&"
267                 + "soilmoisture2=73&" + "solarradiation=42.24&" + "dateutc=2021-02-07%2014:04:03&"
268                 + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&" + "realtime=1&" + "rtfreq=5";
269         MetaData.Request request = new MetaData.Request("GET", new HttpURI(
270                 "http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + secondDeviceQueryString),
271                 HttpVersion.HTTP_1_1, new HttpFields());
272         Request req2 = new Request(httpChannel, null);
273         req2.setMetaData(request);
274         sut.activate();
275
276         // Then
277         assertThat(sut.isActive(), is(true));
278
279         // When
280         sut.doGet(req2, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
281
282         // Then
283         List<ChannelTypeUID> actual = handler.getThing().getChannels().stream().map(Channel::getChannelTypeUID)
284                 .collect(Collectors.toList());
285         assertThat(actual, equalTo(before));
286     }
287
288     class TestChannelTypeRegistry extends ChannelTypeRegistry {
289
290         TestChannelTypeRegistry() {
291             super();
292             ChannelTypeProvider provider = mock(ChannelTypeProvider.class);
293             when(provider.getChannelType(eq(SOFTWARETYPE_CHANNELTYPEUID), any())).thenReturn(
294                     new StateChannelTypeBuilderImpl(SOFTWARETYPE_CHANNELTYPEUID, "Software type", "String").build());
295             when(provider.getChannelType(eq(TEMPERATURE_CHANNELTYPEUID), any()))
296                     .thenReturn(DefaultSystemChannelTypeProvider.SYSTEM_OUTDOOR_TEMPERATURE);
297             when(provider.getChannelType(eq(SOIL_MOISTURE_CHANNELTYPEUID), any()))
298                     .thenReturn(new StateChannelTypeBuilderImpl(SOIL_MOISTURE_CHANNELTYPEUID, "Soilmoisture",
299                             "Number:Dimensionless").build());
300             when(provider.getChannelType(eq(SOLARRADIATION_CHANNELTYPEUID), any()))
301                     .thenReturn(new StateChannelTypeBuilderImpl(SOLARRADIATION_CHANNELTYPEUID, "Solar Radiation",
302                             "Number:Intensity").build());
303             when(provider.getChannelType(eq(HUMIDITY_CHANNELTYPEUID), any())).thenReturn(
304                     new StateChannelTypeBuilderImpl(HUMIDITY_CHANNELTYPEUID, "Humidity", "Number:Dimensionless")
305                             .build());
306             when(provider.getChannelType(eq(DATEUTC_CHANNELTYPEUID), any())).thenReturn(
307                     new StateChannelTypeBuilderImpl(DATEUTC_CHANNELTYPEUID, "Last Updated", "String").build());
308             when(provider.getChannelType(eq(LOW_BATTERY_CHANNELTYPEUID), any()))
309                     .thenReturn(DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_LOW_BATTERY);
310             when(provider.getChannelType(eq(REALTIME_FREQUENCY_CHANNELTYPEUID), any())).thenReturn(
311                     new StateChannelTypeBuilderImpl(REALTIME_FREQUENCY_CHANNELTYPEUID, "Realtime frequency", "Number")
312                             .build());
313             when(provider.getChannelType(eq(LAST_QUERY_STATE_CHANNELTYPEUID), any())).thenReturn(
314                     new StateChannelTypeBuilderImpl(LAST_QUERY_STATE_CHANNELTYPEUID, "The last query", "String")
315                             .build());
316             when(provider.getChannelType(eq(LAST_RECEIVED_DATETIME_CHANNELTYPEUID), any())).thenReturn(
317                     new StateChannelTypeBuilderImpl(LAST_RECEIVED_DATETIME_CHANNELTYPEUID, "Last Received", "DateTime")
318                             .build());
319             when(provider.getChannelType(eq(LAST_QUERY_TRIGGER_CHANNELTYPEUID), any())).thenReturn(
320                     new TriggerChannelTypeBuilderImpl(LAST_QUERY_TRIGGER_CHANNELTYPEUID, "The last query").build());
321             this.addChannelTypeProvider(provider);
322             this.addChannelTypeProvider(new WundergroundUpdateReceiverUnknownChannelTypeProvider());
323         }
324     }
325 }