]> git.basschouten.com Git - openhab-addons.git/blob
50dec648cf4c1ad712af7ab032e0c31aa6a95998
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.hamcrest.Matcher;
39 import org.junit.jupiter.api.BeforeEach;
40 import org.junit.jupiter.api.Test;
41 import org.mockito.Answers;
42 import org.openhab.core.config.core.Configuration;
43 import org.openhab.core.thing.Channel;
44 import org.openhab.core.thing.ChannelUID;
45 import org.openhab.core.thing.DefaultSystemChannelTypeProvider;
46 import org.openhab.core.thing.ManagedThingProvider;
47 import org.openhab.core.thing.Thing;
48 import org.openhab.core.thing.ThingUID;
49 import org.openhab.core.thing.binding.ThingHandlerCallback;
50 import org.openhab.core.thing.binding.builder.ThingBuilder;
51 import org.openhab.core.thing.internal.type.StateChannelTypeBuilderImpl;
52 import org.openhab.core.thing.internal.type.TriggerChannelTypeBuilderImpl;
53 import org.openhab.core.thing.type.ChannelKind;
54 import org.openhab.core.thing.type.ChannelTypeProvider;
55 import org.openhab.core.thing.type.ChannelTypeRegistry;
56 import org.openhab.core.thing.type.ChannelTypeUID;
57 import org.osgi.service.http.HttpService;
58 import org.osgi.service.http.NamespaceException;
59
60 /**
61  * @author Daniel Demus - Initial contribution
62  */
63 @NonNullByDefault({})
64 class WundergroundUpdateReceiverDiscoveryServiceTest {
65
66     private static final String STATION_ID_1 = "abcd1234";
67     private static final String REQ_STATION_ID = "dfggger";
68     private static final ThingUID TEST_THING_UID = new ThingUID(THING_TYPE_UPDATE_RECEIVER, "test-receiver");
69
70     @BeforeEach
71     public void setUp() {
72         openMocks(this);
73     }
74
75     @Test
76     void programmaticChannelsAreAddedCorrectlyOnce() {
77         // Given
78         final String queryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "humidity=74&" + "AqPM2.5=30&"
79                 + "windspdmph_avg2m=10&" + "dateutc=2021-02-07%2014:04:03&" + "softwaretype=WH2600%20V2.2.8&"
80                 + "action=updateraw&" + "realtime=1&" + "rtfreq=5";
81         MetaData.Request request = new MetaData.Request("GET",
82                 new HttpURI("http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + queryString),
83                 HttpVersion.HTTP_1_1, new HttpFields());
84         HttpChannel httpChannel = mock(HttpChannel.class);
85         Request req = new Request(httpChannel, null);
86         req.setMetaData(request);
87
88         TestChannelTypeRegistry channelTypeRegistry = new TestChannelTypeRegistry();
89         WundergroundUpdateReceiverDiscoveryService discoveryService = new WundergroundUpdateReceiverDiscoveryService(
90                 true);
91         HttpService httpService = mock(HttpService.class);
92         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(discoveryService);
93         discoveryService.addUnhandledStationId(REQ_STATION_ID, sut.normalizeParameterMap(req.getParameterMap()));
94         Thing thing = ThingBuilder.create(SUPPORTED_THING_TYPES_UIDS.stream().findFirst().get(), TEST_THING_UID)
95                 .withConfiguration(new Configuration(Map.of(REPRESENTATION_PROPERTY, REQ_STATION_ID)))
96                 .withLabel("test thing").withLocation("location").build();
97         ManagedThingProvider managedThingProvider = mock(ManagedThingProvider.class);
98         when(managedThingProvider.get(any())).thenReturn(thing);
99         WundergroundUpdateReceiverHandler handler = new WundergroundUpdateReceiverHandler(thing, sut, discoveryService,
100                 new WundergroundUpdateReceiverUnknownChannelTypeProvider(), channelTypeRegistry, managedThingProvider);
101         handler.setCallback(mock(ThingHandlerCallback.class));
102
103         // When
104         handler.initialize();
105         var actual = handler.getThing().getChannels();
106
107         // Then
108         assertThat(actual.size(), is(9));
109
110         assertChannel(actual, METADATA_GROUP, LAST_RECEIVED, LAST_RECEIVED_DATETIME_CHANNELTYPEUID, ChannelKind.STATE,
111                 is("DateTime"));
112         assertChannel(actual, METADATA_GROUP, LAST_QUERY_TRIGGER, LAST_QUERY_TRIGGER_CHANNELTYPEUID,
113                 ChannelKind.TRIGGER, nullValue());
114         assertChannel(actual, METADATA_GROUP, LAST_QUERY_STATE, LAST_QUERY_STATE_CHANNELTYPEUID, ChannelKind.STATE,
115                 is("String"));
116         assertChannel(actual, METADATA_GROUP, DATEUTC, DATEUTC_CHANNELTYPEUID, ChannelKind.STATE, is("String"));
117         assertChannel(actual, METADATA_GROUP, REALTIME_FREQUENCY, REALTIME_FREQUENCY_CHANNELTYPEUID, ChannelKind.STATE,
118                 is("Number"));
119         assertChannel(actual, METADATA_GROUP, SOFTWARE_TYPE, SOFTWARETYPE_CHANNELTYPEUID, ChannelKind.STATE,
120                 is("String"));
121         assertChannel(actual, HUMIDITY_GROUP, HUMIDITY, HUMIDITY_CHANNELTYPEUID, ChannelKind.STATE,
122                 is("Number:Dimensionless"));
123         assertChannel(actual, WIND_GROUP, WIND_SPEED_AVG_2MIN, WIND_SPEED_AVG_2MIN_CHANNELTYPEUID, ChannelKind.STATE,
124                 is("Number:Speed"));
125         assertChannel(actual, POLLUTION_GROUP, AQ_PM2_5, PM2_5_MASS_CHANNELTYPEUID, ChannelKind.STATE,
126                 is("Number:Density"));
127     }
128
129     @Test
130     void aRequestWithAnUnregisteredStationidIsAddedToTheQueueOnce()
131             throws ServletException, NamespaceException, IOException {
132         // Given
133         final String queryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "tempf=26.1&" + "humidity=74&" + "dewptf=18.9&"
134                 + "windchillf=26.1&" + "winddir=14&" + "windspeedmph=1.34&" + "windgustmph=2.46&" + "rainin=0.00&"
135                 + "dailyrainin=0.00&" + "weeklyrainin=0.00&" + "monthlyrainin=0.08&" + "yearlyrainin=3.06&"
136                 + "solarradiation=42.24&" + "UV=1&indoortempf=69.3&" + "indoorhumidity=32&" + "baromin=30.39&"
137                 + "AqNOX=21&" + "lowbatt=1&" + "dateutc=2021-02-07%2014:04:03&" + "softwaretype=WH2600%20V2.2.8&"
138                 + "action=updateraw&" + "realtime=1&" + "rtfreq=5";
139         WundergroundUpdateReceiverDiscoveryService discoveryService = mock(
140                 WundergroundUpdateReceiverDiscoveryService.class);
141         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(discoveryService);
142         WundergroundUpdateReceiverHandler handler = mock(WundergroundUpdateReceiverHandler.class);
143         when(handler.getStationId()).thenReturn(STATION_ID_1);
144         sut.addHandler(handler);
145         when(discoveryService.isBackgroundDiscoveryEnabled()).thenReturn(false);
146
147         // Then
148         assertThat(sut.isActive(), is(true));
149
150         HttpChannel httpChannel = mock(HttpChannel.class);
151         MetaData.Request request = new MetaData.Request("GET",
152                 new HttpURI("http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + queryString),
153                 HttpVersion.HTTP_1_1, new HttpFields());
154         Request req = new Request(httpChannel, null);
155         req.setMetaData(request);
156
157         // When
158         sut.doGet(req, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
159
160         // Then
161         verify(handler, never()).updateChannelStates(any());
162         verify(discoveryService).addUnhandledStationId(eq("dfggger"), any());
163         assertThat(sut.isActive(), is(true));
164     }
165
166     @Test
167     void multipleIndexedParametersOfTheSameChanneltypeAreCorrectlyDiscovered() throws IOException {
168         // Given
169         final String queryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "temp1f=26.1&" + "humidity=74&" + "temp2f=25.1&"
170                 + "lowbatt=1&" + "soilmoisture1=78&" + "soilmoisture2=73&" + "dateutc=2021-02-07%2014:04:03&"
171                 + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&" + "realtime=1&" + "rtfreq=5";
172         MetaData.Request request = new MetaData.Request("GET",
173                 new HttpURI("http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + queryString),
174                 HttpVersion.HTTP_1_1, new HttpFields());
175         HttpChannel httpChannel = mock(HttpChannel.class);
176         Request req = new Request(httpChannel, null);
177         req.setMetaData(request);
178
179         TestChannelTypeRegistry channelTypeRegistry = new TestChannelTypeRegistry();
180         WundergroundUpdateReceiverDiscoveryService discoveryService = new WundergroundUpdateReceiverDiscoveryService(
181                 false);
182         HttpService httpService = mock(HttpService.class);
183         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(discoveryService);
184         discoveryService.addUnhandledStationId(REQ_STATION_ID, sut.normalizeParameterMap(req.getParameterMap()));
185         Thing thing = ThingBuilder.create(SUPPORTED_THING_TYPES_UIDS.stream().findFirst().get(), TEST_THING_UID)
186                 .withConfiguration(new Configuration(Map.of(REPRESENTATION_PROPERTY, REQ_STATION_ID)))
187                 .withLabel("test thing").withLocation("location").build();
188         ManagedThingProvider managedThingProvider = mock(ManagedThingProvider.class);
189         when(managedThingProvider.get(TEST_THING_UID)).thenReturn(thing);
190         WundergroundUpdateReceiverHandler handler = new WundergroundUpdateReceiverHandler(thing, sut, discoveryService,
191                 new WundergroundUpdateReceiverUnknownChannelTypeProvider(), channelTypeRegistry, managedThingProvider);
192         handler.setCallback(mock(ThingHandlerCallback.class));
193         handler.initialize();
194         sut.addHandler(handler);
195
196         // When
197         sut.enable();
198
199         // Then
200         assertThat(sut.isActive(), is(true));
201
202         // When
203         sut.doGet(req, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
204
205         // Then
206         assertThat(sut.getHandlers().size(), is(1));
207         assertThat(sut.getHandlers().containsKey(REQ_STATION_ID), is(true));
208         assertThat(handler.getThing().getChannels().stream()
209                 .filter(channel -> channel.getChannelTypeUID() == TEMPERATURE_CHANNELTYPEUID).count(), is(2L));
210     }
211
212     @Test
213     void unregisteredChannelsAreAddedOnTheFlyWhenDiscovered() throws IOException {
214         // Given
215         final String firstDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "tempf=26.1&" + "humidity=74&"
216                 + "dateutc=2021-02-07%2014:04:03&" + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&"
217                 + "realtime=1&" + "rtfreq=5";
218         MetaData.Request request1 = new MetaData.Request("GET", new HttpURI(
219                 "http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + firstDeviceQueryString),
220                 HttpVersion.HTTP_1_1, new HttpFields());
221         HttpChannel httpChannel = mock(HttpChannel.class);
222         Request req1 = new Request(httpChannel, null);
223         req1.setMetaData(request1);
224
225         TestChannelTypeRegistry channelTypeRegistry = new TestChannelTypeRegistry();
226         WundergroundUpdateReceiverDiscoveryService discoveryService = new WundergroundUpdateReceiverDiscoveryService(
227                 true);
228         HttpService httpService = mock(HttpService.class);
229         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(discoveryService);
230         discoveryService.addUnhandledStationId(REQ_STATION_ID, sut.normalizeParameterMap(req1.getParameterMap()));
231         Thing thing = ThingBuilder.create(SUPPORTED_THING_TYPES_UIDS.stream().findFirst().get(), TEST_THING_UID)
232                 .withConfiguration(new Configuration(Map.of(REPRESENTATION_PROPERTY, REQ_STATION_ID)))
233                 .withLabel("test thing").withLocation("location").build();
234         ManagedThingProvider managedThingProvider = mock(ManagedThingProvider.class);
235         when(managedThingProvider.get(any())).thenReturn(thing);
236         WundergroundUpdateReceiverHandler handler = new WundergroundUpdateReceiverHandler(thing, sut, discoveryService,
237                 new WundergroundUpdateReceiverUnknownChannelTypeProvider(), channelTypeRegistry, managedThingProvider);
238         handler.setCallback(mock(ThingHandlerCallback.class));
239
240         // When
241         handler.initialize();
242         sut.addHandler(handler);
243
244         // Then
245         ChannelTypeUID[] expectedBefore = new ChannelTypeUID[] { TEMPERATURE_CHANNELTYPEUID, HUMIDITY_CHANNELTYPEUID,
246                 DATEUTC_CHANNELTYPEUID, SOFTWARETYPE_CHANNELTYPEUID, REALTIME_FREQUENCY_CHANNELTYPEUID,
247                 LAST_QUERY_STATE_CHANNELTYPEUID, LAST_RECEIVED_DATETIME_CHANNELTYPEUID,
248                 LAST_QUERY_TRIGGER_CHANNELTYPEUID };
249         List<ChannelTypeUID> before = handler.getThing().getChannels().stream().map(Channel::getChannelTypeUID)
250                 .collect(Collectors.toList());
251         assertThat(before, hasItems(expectedBefore));
252
253         // When
254         final String secondDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "lowbatt=1&" + "soilmoisture1=78&"
255                 + "soilmoisture2=73&" + "solarradiation=42.24&" + "dateutc=2021-02-07%2014:04:03&"
256                 + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&" + "realtime=1&" + "rtfreq=5";
257         MetaData.Request request = new MetaData.Request("GET", new HttpURI(
258                 "http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + secondDeviceQueryString),
259                 HttpVersion.HTTP_1_1, new HttpFields());
260         Request req2 = new Request(httpChannel, null);
261         req2.setMetaData(request);
262         sut.enable();
263
264         // Then
265         assertThat(sut.isActive(), is(true));
266
267         // When
268         sut.doGet(req2, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
269
270         // Then
271         ChannelTypeUID[] expectedActual = Arrays.copyOf(expectedBefore, expectedBefore.length + 3);
272         System.arraycopy(new ChannelTypeUID[] { LOW_BATTERY_CHANNELTYPEUID, SOIL_MOISTURE_CHANNELTYPEUID,
273                 SOLARRADIATION_CHANNELTYPEUID }, 0, expectedActual, expectedBefore.length, 3);
274         List<ChannelTypeUID> actual = handler.getThing().getChannels().stream().map(Channel::getChannelTypeUID)
275                 .collect(Collectors.toList());
276         assertThat(actual, hasItems(expectedActual));
277     }
278
279     @Test
280     void unregisteredChannelsAreNotAddedOnUnmanagedThings() throws IOException {
281         // Given
282         final String firstDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "tempf=26.1&" + "humidity=74&"
283                 + "dateutc=2021-02-07%2014:04:03&" + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&"
284                 + "realtime=1&" + "rtfreq=5";
285         MetaData.Request request1 = new MetaData.Request("GET", new HttpURI(
286                 "http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + firstDeviceQueryString),
287                 HttpVersion.HTTP_1_1, new HttpFields());
288         HttpChannel httpChannel = mock(HttpChannel.class);
289         Request req1 = new Request(httpChannel, null);
290         req1.setMetaData(request1);
291
292         TestChannelTypeRegistry channelTypeRegistry = new TestChannelTypeRegistry();
293         WundergroundUpdateReceiverDiscoveryService discoveryService = new WundergroundUpdateReceiverDiscoveryService(
294                 true);
295         HttpService httpService = mock(HttpService.class);
296         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(discoveryService);
297         discoveryService.addUnhandledStationId(REQ_STATION_ID, sut.normalizeParameterMap(req1.getParameterMap()));
298         Thing thing = ThingBuilder.create(SUPPORTED_THING_TYPES_UIDS.stream().findFirst().get(), TEST_THING_UID)
299                 .withConfiguration(new Configuration(Map.of(REPRESENTATION_PROPERTY, REQ_STATION_ID)))
300                 .withLabel("test thing").withLocation("location").build();
301         ManagedThingProvider managedThingProvider = mock(ManagedThingProvider.class);
302         when(managedThingProvider.get(any())).thenReturn(null);
303         WundergroundUpdateReceiverHandler handler = new WundergroundUpdateReceiverHandler(thing, sut, discoveryService,
304                 new WundergroundUpdateReceiverUnknownChannelTypeProvider(), channelTypeRegistry, managedThingProvider);
305         handler.setCallback(mock(ThingHandlerCallback.class));
306
307         // When
308         handler.initialize();
309         sut.addHandler(handler);
310
311         // Then
312         ChannelTypeUID[] expectedBefore = new ChannelTypeUID[] { TEMPERATURE_CHANNELTYPEUID, HUMIDITY_CHANNELTYPEUID,
313                 DATEUTC_CHANNELTYPEUID, SOFTWARETYPE_CHANNELTYPEUID, REALTIME_FREQUENCY_CHANNELTYPEUID,
314                 LAST_QUERY_STATE_CHANNELTYPEUID, LAST_RECEIVED_DATETIME_CHANNELTYPEUID,
315                 LAST_QUERY_TRIGGER_CHANNELTYPEUID };
316         List<ChannelTypeUID> before = handler.getThing().getChannels().stream().map(Channel::getChannelTypeUID)
317                 .collect(Collectors.toList());
318         assertThat(before, hasItems(expectedBefore));
319
320         // When
321         final String secondDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "lowbatt=1&" + "soilmoisture1=78&"
322                 + "soilmoisture2=73&" + "solarradiation=42.24&" + "dateutc=2021-02-07%2014:04:03&"
323                 + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&" + "realtime=1&" + "rtfreq=5";
324         MetaData.Request request = new MetaData.Request("GET", new HttpURI(
325                 "http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + secondDeviceQueryString),
326                 HttpVersion.HTTP_1_1, new HttpFields());
327         Request req2 = new Request(httpChannel, null);
328         req2.setMetaData(request);
329         sut.enable();
330
331         // Then
332         assertThat(sut.isActive(), is(true));
333
334         // When
335         sut.doGet(req2, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
336
337         // Then
338         List<ChannelTypeUID> actual = handler.getThing().getChannels().stream().map(Channel::getChannelTypeUID)
339                 .collect(Collectors.toList());
340         assertThat(actual, equalTo(before));
341     }
342
343     @Test
344     void lastQueryTriggerIsMigratedSuccessfully() throws IOException {
345         // Given
346         final String firstDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "tempf=26.1&" + "humidity=74&"
347                 + "dateutc=2021-02-07%2014:04:03&" + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&"
348                 + "realtime=1&" + "rtfreq=5";
349         MetaData.Request request1 = new MetaData.Request("GET", new HttpURI(
350                 "http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + firstDeviceQueryString),
351                 HttpVersion.HTTP_1_1, new HttpFields());
352         HttpChannel httpChannel = mock(HttpChannel.class);
353         Request req1 = new Request(httpChannel, null);
354         req1.setMetaData(request1);
355
356         UpdatingChannelTypeRegistry channelTypeRegistry = new UpdatingChannelTypeRegistry();
357         WundergroundUpdateReceiverDiscoveryService discoveryService = new WundergroundUpdateReceiverDiscoveryService(
358                 true);
359         HttpService httpService = mock(HttpService.class);
360         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(discoveryService);
361         discoveryService.addUnhandledStationId(REQ_STATION_ID, sut.normalizeParameterMap(req1.getParameterMap()));
362         Thing thing = ThingBuilder.create(SUPPORTED_THING_TYPES_UIDS.stream().findFirst().get(), TEST_THING_UID)
363                 .withConfiguration(new Configuration(Map.of(REPRESENTATION_PROPERTY, REQ_STATION_ID)))
364                 .withLabel("test thing").withLocation("location").build();
365         ManagedThingProvider managedThingProvider = mock(ManagedThingProvider.class);
366         when(managedThingProvider.get(any())).thenReturn(null);
367         WundergroundUpdateReceiverHandler handler = new WundergroundUpdateReceiverHandler(thing, sut, discoveryService,
368                 new WundergroundUpdateReceiverUnknownChannelTypeProvider(), channelTypeRegistry, managedThingProvider);
369         handler.setCallback(mock(ThingHandlerCallback.class));
370
371         // When
372         handler.initialize();
373         sut.addHandler(handler);
374
375         // Then
376         ChannelTypeUID[] expectedBefore = new ChannelTypeUID[] { TEMPERATURE_CHANNELTYPEUID, HUMIDITY_CHANNELTYPEUID,
377                 DATEUTC_CHANNELTYPEUID, SOFTWARETYPE_CHANNELTYPEUID, REALTIME_FREQUENCY_CHANNELTYPEUID,
378                 LAST_QUERY_STATE_CHANNELTYPEUID, LAST_RECEIVED_DATETIME_CHANNELTYPEUID,
379                 LAST_QUERY_TRIGGER_CHANNELTYPEUID };
380         List<ChannelTypeUID> before = handler.getThing().getChannels().stream().map(Channel::getChannelTypeUID)
381                 .collect(Collectors.toList());
382         assertThat(before, hasItems(expectedBefore));
383
384         // When
385         var actual = handler.getThing().getChannels();
386
387         // Then
388         assertThat(actual.size(), is(8));
389         assertChannel(actual, METADATA_GROUP, LAST_QUERY_TRIGGER, LAST_QUERY_TRIGGER_CHANNELTYPEUID, ChannelKind.STATE,
390                 is("DateTime"));
391
392         // When
393         handler.dispose();
394         handler.initialize();
395
396         final String secondDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "lowbatt=1&" + "soilmoisture1=78&"
397                 + "soilmoisture2=73&" + "solarradiation=42.24&" + "dateutc=2021-02-07%2014:04:03&"
398                 + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&" + "realtime=1&" + "rtfreq=5";
399         MetaData.Request request = new MetaData.Request("GET", new HttpURI(
400                 "http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + secondDeviceQueryString),
401                 HttpVersion.HTTP_1_1, new HttpFields());
402         Request req2 = new Request(httpChannel, null);
403         req2.setMetaData(request);
404         sut.enable();
405
406         // Then
407         assertThat(sut.isActive(), is(true));
408
409         // When
410         sut.doGet(req2, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
411         actual = handler.getThing().getChannels();
412
413         // Then
414         assertThat(actual.size(), is(8));
415         assertChannel(actual, METADATA_GROUP, LAST_QUERY_TRIGGER, LAST_QUERY_TRIGGER_CHANNELTYPEUID,
416                 ChannelKind.TRIGGER, nullValue());
417     }
418
419     private void assertChannel(List<Channel> channels, String expectedGroup, String expectedName,
420             ChannelTypeUID expectedUid, ChannelKind expectedKind, Matcher<Object> expectedItemType) {
421         ChannelUID channelUID = new ChannelUID(TEST_THING_UID, expectedGroup, expectedName);
422         Channel actual = channels.stream().filter(c -> channelUID.equals(c.getUID())).findFirst().orElse(null);
423         assertThat(actual, is(notNullValue()));
424         assertThat(actual.getLabel() + " UID", actual.getUID(), is(channelUID));
425         assertThat(actual.getLabel() + " ChannelTypeUID", actual.getChannelTypeUID(), is(expectedUid));
426         assertThat(actual.getLabel() + " Kind", actual.getKind(), is(expectedKind));
427         assertThat(actual.getLabel() + " AcceptedItemType", actual.getAcceptedItemType(), expectedItemType);
428     }
429
430     abstract class AbstractTestChannelTypeRegistry extends ChannelTypeRegistry {
431
432         protected final ChannelTypeProvider provider;
433
434         AbstractTestChannelTypeRegistry(ChannelTypeProvider mock) {
435             super();
436             this.provider = mock;
437             when(provider.getChannelType(eq(SOFTWARETYPE_CHANNELTYPEUID), any())).thenReturn(
438                     new StateChannelTypeBuilderImpl(SOFTWARETYPE_CHANNELTYPEUID, "Software type", "String").build());
439             when(provider.getChannelType(eq(TEMPERATURE_CHANNELTYPEUID), any()))
440                     .thenReturn(DefaultSystemChannelTypeProvider.SYSTEM_OUTDOOR_TEMPERATURE);
441             when(provider.getChannelType(eq(SOIL_MOISTURE_CHANNELTYPEUID), any()))
442                     .thenReturn(new StateChannelTypeBuilderImpl(SOIL_MOISTURE_CHANNELTYPEUID, "Soilmoisture",
443                             "Number:Dimensionless").build());
444             when(provider.getChannelType(eq(SOLARRADIATION_CHANNELTYPEUID), any()))
445                     .thenReturn(new StateChannelTypeBuilderImpl(SOLARRADIATION_CHANNELTYPEUID, "Solar Radiation",
446                             "Number:Intensity").build());
447             when(provider.getChannelType(eq(HUMIDITY_CHANNELTYPEUID), any())).thenReturn(
448                     new StateChannelTypeBuilderImpl(HUMIDITY_CHANNELTYPEUID, "Humidity", "Number:Dimensionless")
449                             .build());
450             when(provider.getChannelType(eq(WIND_SPEED_AVG_2MIN_CHANNELTYPEUID), any()))
451                     .thenReturn(new StateChannelTypeBuilderImpl(WIND_SPEED_AVG_2MIN_CHANNELTYPEUID,
452                             "Wind Speed 2min Average", "Number:Speed").build());
453             when(provider.getChannelType(eq(PM2_5_MASS_CHANNELTYPEUID), any())).thenReturn(
454                     new StateChannelTypeBuilderImpl(PM2_5_MASS_CHANNELTYPEUID, "PM2.5 Mass", "Number:Density").build());
455             when(provider.getChannelType(eq(DATEUTC_CHANNELTYPEUID), any())).thenReturn(
456                     new StateChannelTypeBuilderImpl(DATEUTC_CHANNELTYPEUID, "Last Updated", "String").build());
457             when(provider.getChannelType(eq(LOW_BATTERY_CHANNELTYPEUID), any()))
458                     .thenReturn(DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_LOW_BATTERY);
459             when(provider.getChannelType(eq(REALTIME_FREQUENCY_CHANNELTYPEUID), any())).thenReturn(
460                     new StateChannelTypeBuilderImpl(REALTIME_FREQUENCY_CHANNELTYPEUID, "Realtime frequency", "Number")
461                             .build());
462             when(provider.getChannelType(eq(LAST_QUERY_STATE_CHANNELTYPEUID), any())).thenReturn(
463                     new StateChannelTypeBuilderImpl(LAST_QUERY_STATE_CHANNELTYPEUID, "The last query", "String")
464                             .build());
465             when(provider.getChannelType(eq(LAST_RECEIVED_DATETIME_CHANNELTYPEUID), any())).thenReturn(
466                     new StateChannelTypeBuilderImpl(LAST_RECEIVED_DATETIME_CHANNELTYPEUID, "Last Received", "DateTime")
467                             .build());
468             this.addChannelTypeProvider(provider);
469             this.addChannelTypeProvider(new WundergroundUpdateReceiverUnknownChannelTypeProvider());
470         }
471     }
472
473     class TestChannelTypeRegistry extends AbstractTestChannelTypeRegistry {
474
475         TestChannelTypeRegistry() {
476             super(mock(ChannelTypeProvider.class));
477             when(provider.getChannelType(eq(LAST_QUERY_TRIGGER_CHANNELTYPEUID), any())).thenReturn(
478                     new TriggerChannelTypeBuilderImpl(LAST_QUERY_TRIGGER_CHANNELTYPEUID, "The last query").build());
479         }
480     }
481
482     class UpdatingChannelTypeRegistry extends AbstractTestChannelTypeRegistry {
483
484         UpdatingChannelTypeRegistry() {
485             super(mock(ChannelTypeProvider.class));
486             when(provider.getChannelType(eq(LAST_QUERY_TRIGGER_CHANNELTYPEUID), any()))
487                     .thenReturn(new StateChannelTypeBuilderImpl(LAST_QUERY_TRIGGER_CHANNELTYPEUID, "The last query",
488                             "DateTime").build())
489                     .thenReturn(new StateChannelTypeBuilderImpl(LAST_QUERY_TRIGGER_CHANNELTYPEUID, "The last query",
490                             "DateTime").build())
491                     .thenReturn(new TriggerChannelTypeBuilderImpl(LAST_QUERY_TRIGGER_CHANNELTYPEUID, "The last query")
492                             .build());
493         }
494     }
495 }