]> git.basschouten.com Git - openhab-addons.git/blob
55e220e50063ed9a68c439812f82070a7c98085e
[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(httpService, 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.get(0), METADATA_GROUP, LAST_RECEIVED, LAST_RECEIVED_DATETIME_CHANNELTYPEUID,
111                 ChannelKind.STATE, is("DateTime"));
112         assertChannel(actual.get(1), METADATA_GROUP, LAST_QUERY_TRIGGER, LAST_QUERY_TRIGGER_CHANNELTYPEUID,
113                 ChannelKind.TRIGGER, nullValue());
114         assertChannel(actual.get(2), METADATA_GROUP, LAST_QUERY_STATE, LAST_QUERY_STATE_CHANNELTYPEUID,
115                 ChannelKind.STATE, is("String"));
116         assertChannel(actual.get(3), METADATA_GROUP, DATEUTC, DATEUTC_CHANNELTYPEUID, ChannelKind.STATE, is("String"));
117         assertChannel(actual.get(4), METADATA_GROUP, REALTIME_FREQUENCY, REALTIME_FREQUENCY_CHANNELTYPEUID,
118                 ChannelKind.STATE, is("Number"));
119         assertChannel(actual.get(5), METADATA_GROUP, SOFTWARE_TYPE, SOFTWARETYPE_CHANNELTYPEUID, ChannelKind.STATE,
120                 is("String"));
121         assertChannel(actual.get(6), HUMIDITY_GROUP, HUMIDITY, HUMIDITY_CHANNELTYPEUID, ChannelKind.STATE,
122                 is("Number:Dimensionless"));
123         assertChannel(actual.get(7), WIND_GROUP, WIND_SPEED_AVG_2MIN, WIND_SPEED_AVG_2MIN_CHANNELTYPEUID,
124                 ChannelKind.STATE, is("Number:Speed"));
125         assertChannel(actual.get(8), 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         HttpService httpService = mock(HttpService.class);
142         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(httpService, discoveryService);
143         WundergroundUpdateReceiverHandler handler = mock(WundergroundUpdateReceiverHandler.class);
144         when(handler.getStationId()).thenReturn(STATION_ID_1);
145         sut.addHandler(handler);
146         when(discoveryService.isBackgroundDiscoveryEnabled()).thenReturn(false);
147
148         // Then
149         verify(httpService).registerServlet(eq(WundergroundUpdateReceiverServlet.SERVLET_URL), eq(sut), any(), any());
150         assertThat(sut.isActive(), is(true));
151
152         HttpChannel httpChannel = mock(HttpChannel.class);
153         MetaData.Request request = new MetaData.Request("GET",
154                 new HttpURI("http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + queryString),
155                 HttpVersion.HTTP_1_1, new HttpFields());
156         Request req = new Request(httpChannel, null);
157         req.setMetaData(request);
158
159         // When
160         sut.doGet(req, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
161
162         // Then
163         verify(handler, never()).updateChannelStates(any());
164         verify(discoveryService).addUnhandledStationId(eq("dfggger"), any());
165         assertThat(sut.isActive(), is(true));
166     }
167
168     @Test
169     void multipleIndexedParametersOfTheSameChanneltypeAreCorrectlyDiscovered() throws IOException {
170         // Given
171         final String queryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "temp1f=26.1&" + "humidity=74&" + "temp2f=25.1&"
172                 + "lowbatt=1&" + "soilmoisture1=78&" + "soilmoisture2=73&" + "dateutc=2021-02-07%2014:04:03&"
173                 + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&" + "realtime=1&" + "rtfreq=5";
174         MetaData.Request request = new MetaData.Request("GET",
175                 new HttpURI("http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + queryString),
176                 HttpVersion.HTTP_1_1, new HttpFields());
177         HttpChannel httpChannel = mock(HttpChannel.class);
178         Request req = new Request(httpChannel, null);
179         req.setMetaData(request);
180
181         TestChannelTypeRegistry channelTypeRegistry = new TestChannelTypeRegistry();
182         WundergroundUpdateReceiverDiscoveryService discoveryService = new WundergroundUpdateReceiverDiscoveryService(
183                 false);
184         HttpService httpService = mock(HttpService.class);
185         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(httpService, discoveryService);
186         discoveryService.addUnhandledStationId(REQ_STATION_ID, sut.normalizeParameterMap(req.getParameterMap()));
187         Thing thing = ThingBuilder.create(SUPPORTED_THING_TYPES_UIDS.stream().findFirst().get(), TEST_THING_UID)
188                 .withConfiguration(new Configuration(Map.of(REPRESENTATION_PROPERTY, REQ_STATION_ID)))
189                 .withLabel("test thing").withLocation("location").build();
190         ManagedThingProvider managedThingProvider = mock(ManagedThingProvider.class);
191         when(managedThingProvider.get(TEST_THING_UID)).thenReturn(thing);
192         WundergroundUpdateReceiverHandler handler = new WundergroundUpdateReceiverHandler(thing, sut, discoveryService,
193                 new WundergroundUpdateReceiverUnknownChannelTypeProvider(), channelTypeRegistry, managedThingProvider);
194         handler.setCallback(mock(ThingHandlerCallback.class));
195         handler.initialize();
196         sut.addHandler(handler);
197
198         // When
199         sut.activate();
200
201         // Then
202         assertThat(sut.isActive(), is(true));
203
204         // When
205         sut.doGet(req, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
206
207         // Then
208         assertThat(sut.getHandlers().size(), is(1));
209         assertThat(sut.getHandlers().containsKey(REQ_STATION_ID), is(true));
210         assertThat(handler.getThing().getChannels().stream()
211                 .filter(channel -> channel.getChannelTypeUID() == TEMPERATURE_CHANNELTYPEUID).count(), is(2L));
212     }
213
214     @Test
215     void unregisteredChannelsAreAddedOnTheFlyWhenDiscovered() throws IOException {
216         // Given
217         final String firstDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "tempf=26.1&" + "humidity=74&"
218                 + "dateutc=2021-02-07%2014:04:03&" + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&"
219                 + "realtime=1&" + "rtfreq=5";
220         MetaData.Request request1 = new MetaData.Request("GET", new HttpURI(
221                 "http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + firstDeviceQueryString),
222                 HttpVersion.HTTP_1_1, new HttpFields());
223         HttpChannel httpChannel = mock(HttpChannel.class);
224         Request req1 = new Request(httpChannel, null);
225         req1.setMetaData(request1);
226
227         TestChannelTypeRegistry channelTypeRegistry = new TestChannelTypeRegistry();
228         WundergroundUpdateReceiverDiscoveryService discoveryService = new WundergroundUpdateReceiverDiscoveryService(
229                 true);
230         HttpService httpService = mock(HttpService.class);
231         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(httpService, discoveryService);
232         discoveryService.addUnhandledStationId(REQ_STATION_ID, sut.normalizeParameterMap(req1.getParameterMap()));
233         Thing thing = ThingBuilder.create(SUPPORTED_THING_TYPES_UIDS.stream().findFirst().get(), TEST_THING_UID)
234                 .withConfiguration(new Configuration(Map.of(REPRESENTATION_PROPERTY, REQ_STATION_ID)))
235                 .withLabel("test thing").withLocation("location").build();
236         ManagedThingProvider managedThingProvider = mock(ManagedThingProvider.class);
237         when(managedThingProvider.get(any())).thenReturn(thing);
238         WundergroundUpdateReceiverHandler handler = new WundergroundUpdateReceiverHandler(thing, sut, discoveryService,
239                 new WundergroundUpdateReceiverUnknownChannelTypeProvider(), channelTypeRegistry, managedThingProvider);
240         handler.setCallback(mock(ThingHandlerCallback.class));
241
242         // When
243         handler.initialize();
244         sut.addHandler(handler);
245
246         // Then
247         ChannelTypeUID[] expectedBefore = new ChannelTypeUID[] { TEMPERATURE_CHANNELTYPEUID, HUMIDITY_CHANNELTYPEUID,
248                 DATEUTC_CHANNELTYPEUID, SOFTWARETYPE_CHANNELTYPEUID, REALTIME_FREQUENCY_CHANNELTYPEUID,
249                 LAST_QUERY_STATE_CHANNELTYPEUID, LAST_RECEIVED_DATETIME_CHANNELTYPEUID,
250                 LAST_QUERY_TRIGGER_CHANNELTYPEUID };
251         List<ChannelTypeUID> before = handler.getThing().getChannels().stream().map(Channel::getChannelTypeUID)
252                 .collect(Collectors.toList());
253         assertThat(before, hasItems(expectedBefore));
254
255         // When
256         final String secondDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "lowbatt=1&" + "soilmoisture1=78&"
257                 + "soilmoisture2=73&" + "solarradiation=42.24&" + "dateutc=2021-02-07%2014:04:03&"
258                 + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&" + "realtime=1&" + "rtfreq=5";
259         MetaData.Request request = new MetaData.Request("GET", new HttpURI(
260                 "http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + secondDeviceQueryString),
261                 HttpVersion.HTTP_1_1, new HttpFields());
262         Request req2 = new Request(httpChannel, null);
263         req2.setMetaData(request);
264         sut.activate();
265
266         // Then
267         assertThat(sut.isActive(), is(true));
268
269         // When
270         sut.doGet(req2, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
271
272         // Then
273         ChannelTypeUID[] expectedActual = Arrays.copyOf(expectedBefore, expectedBefore.length + 3);
274         System.arraycopy(new ChannelTypeUID[] { LOW_BATTERY_CHANNELTYPEUID, SOIL_MOISTURE_CHANNELTYPEUID,
275                 SOLARRADIATION_CHANNELTYPEUID }, 0, expectedActual, expectedBefore.length, 3);
276         List<ChannelTypeUID> actual = handler.getThing().getChannels().stream().map(Channel::getChannelTypeUID)
277                 .collect(Collectors.toList());
278         assertThat(actual, hasItems(expectedActual));
279     }
280
281     @Test
282     void unregisteredChannelsAreNotAddedOnUnmanagedThings() throws IOException {
283         // Given
284         final String firstDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "tempf=26.1&" + "humidity=74&"
285                 + "dateutc=2021-02-07%2014:04:03&" + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&"
286                 + "realtime=1&" + "rtfreq=5";
287         MetaData.Request request1 = new MetaData.Request("GET", new HttpURI(
288                 "http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + firstDeviceQueryString),
289                 HttpVersion.HTTP_1_1, new HttpFields());
290         HttpChannel httpChannel = mock(HttpChannel.class);
291         Request req1 = new Request(httpChannel, null);
292         req1.setMetaData(request1);
293
294         TestChannelTypeRegistry channelTypeRegistry = new TestChannelTypeRegistry();
295         WundergroundUpdateReceiverDiscoveryService discoveryService = new WundergroundUpdateReceiverDiscoveryService(
296                 true);
297         HttpService httpService = mock(HttpService.class);
298         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(httpService, discoveryService);
299         discoveryService.addUnhandledStationId(REQ_STATION_ID, sut.normalizeParameterMap(req1.getParameterMap()));
300         Thing thing = ThingBuilder.create(SUPPORTED_THING_TYPES_UIDS.stream().findFirst().get(), TEST_THING_UID)
301                 .withConfiguration(new Configuration(Map.of(REPRESENTATION_PROPERTY, REQ_STATION_ID)))
302                 .withLabel("test thing").withLocation("location").build();
303         ManagedThingProvider managedThingProvider = mock(ManagedThingProvider.class);
304         when(managedThingProvider.get(any())).thenReturn(null);
305         WundergroundUpdateReceiverHandler handler = new WundergroundUpdateReceiverHandler(thing, sut, discoveryService,
306                 new WundergroundUpdateReceiverUnknownChannelTypeProvider(), channelTypeRegistry, managedThingProvider);
307         handler.setCallback(mock(ThingHandlerCallback.class));
308
309         // When
310         handler.initialize();
311         sut.addHandler(handler);
312
313         // Then
314         ChannelTypeUID[] expectedBefore = new ChannelTypeUID[] { TEMPERATURE_CHANNELTYPEUID, HUMIDITY_CHANNELTYPEUID,
315                 DATEUTC_CHANNELTYPEUID, SOFTWARETYPE_CHANNELTYPEUID, REALTIME_FREQUENCY_CHANNELTYPEUID,
316                 LAST_QUERY_STATE_CHANNELTYPEUID, LAST_RECEIVED_DATETIME_CHANNELTYPEUID,
317                 LAST_QUERY_TRIGGER_CHANNELTYPEUID };
318         List<ChannelTypeUID> before = handler.getThing().getChannels().stream().map(Channel::getChannelTypeUID)
319                 .collect(Collectors.toList());
320         assertThat(before, hasItems(expectedBefore));
321
322         // When
323         final String secondDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "lowbatt=1&" + "soilmoisture1=78&"
324                 + "soilmoisture2=73&" + "solarradiation=42.24&" + "dateutc=2021-02-07%2014:04:03&"
325                 + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&" + "realtime=1&" + "rtfreq=5";
326         MetaData.Request request = new MetaData.Request("GET", new HttpURI(
327                 "http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + secondDeviceQueryString),
328                 HttpVersion.HTTP_1_1, new HttpFields());
329         Request req2 = new Request(httpChannel, null);
330         req2.setMetaData(request);
331         sut.activate();
332
333         // Then
334         assertThat(sut.isActive(), is(true));
335
336         // When
337         sut.doGet(req2, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
338
339         // Then
340         List<ChannelTypeUID> actual = handler.getThing().getChannels().stream().map(Channel::getChannelTypeUID)
341                 .collect(Collectors.toList());
342         assertThat(actual, equalTo(before));
343     }
344
345     @Test
346     void lastQueryTriggerIsMigratedSuccessfully() throws IOException {
347         // Given
348         final String firstDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "tempf=26.1&" + "humidity=74&"
349                 + "dateutc=2021-02-07%2014:04:03&" + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&"
350                 + "realtime=1&" + "rtfreq=5";
351         MetaData.Request request1 = new MetaData.Request("GET", new HttpURI(
352                 "http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + firstDeviceQueryString),
353                 HttpVersion.HTTP_1_1, new HttpFields());
354         HttpChannel httpChannel = mock(HttpChannel.class);
355         Request req1 = new Request(httpChannel, null);
356         req1.setMetaData(request1);
357
358         UpdatingChannelTypeRegistry channelTypeRegistry = new UpdatingChannelTypeRegistry();
359         WundergroundUpdateReceiverDiscoveryService discoveryService = new WundergroundUpdateReceiverDiscoveryService(
360                 true);
361         HttpService httpService = mock(HttpService.class);
362         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(httpService, discoveryService);
363         discoveryService.addUnhandledStationId(REQ_STATION_ID, sut.normalizeParameterMap(req1.getParameterMap()));
364         Thing thing = ThingBuilder.create(SUPPORTED_THING_TYPES_UIDS.stream().findFirst().get(), TEST_THING_UID)
365                 .withConfiguration(new Configuration(Map.of(REPRESENTATION_PROPERTY, REQ_STATION_ID)))
366                 .withLabel("test thing").withLocation("location").build();
367         ManagedThingProvider managedThingProvider = mock(ManagedThingProvider.class);
368         when(managedThingProvider.get(any())).thenReturn(null);
369         WundergroundUpdateReceiverHandler handler = new WundergroundUpdateReceiverHandler(thing, sut, discoveryService,
370                 new WundergroundUpdateReceiverUnknownChannelTypeProvider(), channelTypeRegistry, managedThingProvider);
371         handler.setCallback(mock(ThingHandlerCallback.class));
372
373         // When
374         handler.initialize();
375         sut.addHandler(handler);
376
377         // Then
378         ChannelTypeUID[] expectedBefore = new ChannelTypeUID[] { TEMPERATURE_CHANNELTYPEUID, HUMIDITY_CHANNELTYPEUID,
379                 DATEUTC_CHANNELTYPEUID, SOFTWARETYPE_CHANNELTYPEUID, REALTIME_FREQUENCY_CHANNELTYPEUID,
380                 LAST_QUERY_STATE_CHANNELTYPEUID, LAST_RECEIVED_DATETIME_CHANNELTYPEUID,
381                 LAST_QUERY_TRIGGER_CHANNELTYPEUID };
382         List<ChannelTypeUID> before = handler.getThing().getChannels().stream().map(Channel::getChannelTypeUID)
383                 .collect(Collectors.toList());
384         assertThat(before, hasItems(expectedBefore));
385
386         // When
387         var actual = handler.getThing().getChannels();
388
389         // Then
390         assertThat(actual.size(), is(8));
391         assertChannel(actual.get(7), METADATA_GROUP, LAST_QUERY_TRIGGER, LAST_QUERY_TRIGGER_CHANNELTYPEUID,
392                 ChannelKind.STATE, is("DateTime"));
393
394         // When
395         handler.dispose();
396         handler.initialize();
397
398         final String secondDeviceQueryString = "ID=dfggger&" + "PASSWORD=XXXXXX&" + "lowbatt=1&" + "soilmoisture1=78&"
399                 + "soilmoisture2=73&" + "solarradiation=42.24&" + "dateutc=2021-02-07%2014:04:03&"
400                 + "softwaretype=WH2600%20V2.2.8&" + "action=updateraw&" + "realtime=1&" + "rtfreq=5";
401         MetaData.Request request = new MetaData.Request("GET", new HttpURI(
402                 "http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + secondDeviceQueryString),
403                 HttpVersion.HTTP_1_1, new HttpFields());
404         Request req2 = new Request(httpChannel, null);
405         req2.setMetaData(request);
406         sut.activate();
407
408         // Then
409         assertThat(sut.isActive(), is(true));
410
411         // When
412         sut.doGet(req2, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
413         actual = handler.getThing().getChannels();
414
415         // Then
416         assertThat(actual.size(), is(8));
417         assertChannel(actual.get(7), METADATA_GROUP, LAST_QUERY_TRIGGER, LAST_QUERY_TRIGGER_CHANNELTYPEUID,
418                 ChannelKind.TRIGGER, nullValue());
419     }
420
421     private void assertChannel(Channel actual, String expectedGroup, String expectedName, ChannelTypeUID expectedUid,
422             ChannelKind expectedKind, Matcher<Object> expectedItemType) {
423         assertThat(actual, is(notNullValue()));
424         assertThat(actual.getLabel() + " UID", actual.getUID(),
425                 is(new ChannelUID(TEST_THING_UID, expectedGroup, expectedName)));
426         assertThat(actual.getLabel() + " ChannelTypeUID", actual.getChannelTypeUID(), is(expectedUid));
427         assertThat(actual.getLabel() + " Kind", actual.getKind(), is(expectedKind));
428         assertThat(actual.getLabel() + " AcceptedItemType", actual.getAcceptedItemType(), expectedItemType);
429     }
430
431     abstract class AbstractTestChannelTypeRegistry extends ChannelTypeRegistry {
432
433         protected final ChannelTypeProvider provider;
434
435         AbstractTestChannelTypeRegistry(ChannelTypeProvider mock) {
436             super();
437             this.provider = mock;
438             when(provider.getChannelType(eq(SOFTWARETYPE_CHANNELTYPEUID), any())).thenReturn(
439                     new StateChannelTypeBuilderImpl(SOFTWARETYPE_CHANNELTYPEUID, "Software type", "String").build());
440             when(provider.getChannelType(eq(TEMPERATURE_CHANNELTYPEUID), any()))
441                     .thenReturn(DefaultSystemChannelTypeProvider.SYSTEM_OUTDOOR_TEMPERATURE);
442             when(provider.getChannelType(eq(SOIL_MOISTURE_CHANNELTYPEUID), any()))
443                     .thenReturn(new StateChannelTypeBuilderImpl(SOIL_MOISTURE_CHANNELTYPEUID, "Soilmoisture",
444                             "Number:Dimensionless").build());
445             when(provider.getChannelType(eq(SOLARRADIATION_CHANNELTYPEUID), any()))
446                     .thenReturn(new StateChannelTypeBuilderImpl(SOLARRADIATION_CHANNELTYPEUID, "Solar Radiation",
447                             "Number:Intensity").build());
448             when(provider.getChannelType(eq(HUMIDITY_CHANNELTYPEUID), any())).thenReturn(
449                     new StateChannelTypeBuilderImpl(HUMIDITY_CHANNELTYPEUID, "Humidity", "Number:Dimensionless")
450                             .build());
451             when(provider.getChannelType(eq(WIND_SPEED_AVG_2MIN_CHANNELTYPEUID), any()))
452                     .thenReturn(new StateChannelTypeBuilderImpl(WIND_SPEED_AVG_2MIN_CHANNELTYPEUID,
453                             "Wind Speed 2min Average", "Number:Speed").build());
454             when(provider.getChannelType(eq(PM2_5_MASS_CHANNELTYPEUID), any())).thenReturn(
455                     new StateChannelTypeBuilderImpl(PM2_5_MASS_CHANNELTYPEUID, "PM2.5 Mass", "Number:Density").build());
456             when(provider.getChannelType(eq(DATEUTC_CHANNELTYPEUID), any())).thenReturn(
457                     new StateChannelTypeBuilderImpl(DATEUTC_CHANNELTYPEUID, "Last Updated", "String").build());
458             when(provider.getChannelType(eq(LOW_BATTERY_CHANNELTYPEUID), any()))
459                     .thenReturn(DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_LOW_BATTERY);
460             when(provider.getChannelType(eq(REALTIME_FREQUENCY_CHANNELTYPEUID), any())).thenReturn(
461                     new StateChannelTypeBuilderImpl(REALTIME_FREQUENCY_CHANNELTYPEUID, "Realtime frequency", "Number")
462                             .build());
463             when(provider.getChannelType(eq(LAST_QUERY_STATE_CHANNELTYPEUID), any())).thenReturn(
464                     new StateChannelTypeBuilderImpl(LAST_QUERY_STATE_CHANNELTYPEUID, "The last query", "String")
465                             .build());
466             when(provider.getChannelType(eq(LAST_RECEIVED_DATETIME_CHANNELTYPEUID), any())).thenReturn(
467                     new StateChannelTypeBuilderImpl(LAST_RECEIVED_DATETIME_CHANNELTYPEUID, "Last Received", "DateTime")
468                             .build());
469             this.addChannelTypeProvider(provider);
470             this.addChannelTypeProvider(new WundergroundUpdateReceiverUnknownChannelTypeProvider());
471         }
472     }
473
474     class TestChannelTypeRegistry extends AbstractTestChannelTypeRegistry {
475
476         TestChannelTypeRegistry() {
477             super(mock(ChannelTypeProvider.class));
478             when(provider.getChannelType(eq(LAST_QUERY_TRIGGER_CHANNELTYPEUID), any())).thenReturn(
479                     new TriggerChannelTypeBuilderImpl(LAST_QUERY_TRIGGER_CHANNELTYPEUID, "The last query").build());
480         }
481     }
482
483     class UpdatingChannelTypeRegistry extends AbstractTestChannelTypeRegistry {
484
485         UpdatingChannelTypeRegistry() {
486             super(mock(ChannelTypeProvider.class));
487             when(provider.getChannelType(eq(LAST_QUERY_TRIGGER_CHANNELTYPEUID), any()))
488                     .thenReturn(new StateChannelTypeBuilderImpl(LAST_QUERY_TRIGGER_CHANNELTYPEUID, "The last query",
489                             "DateTime").build())
490                     .thenReturn(new StateChannelTypeBuilderImpl(LAST_QUERY_TRIGGER_CHANNELTYPEUID, "The last query",
491                             "DateTime").build())
492                     .thenReturn(new TriggerChannelTypeBuilderImpl(LAST_QUERY_TRIGGER_CHANNELTYPEUID, "The last query")
493                             .build());
494         }
495     }
496 }