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