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