]> git.basschouten.com Git - openhab-addons.git/blob
2b6d95e1453deaf8ab375bb574bc8c083fbaf2a6
[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.core.Is.is;
17 import static org.hamcrest.core.IsIterableContaining.hasItems;
18 import static org.mockito.Mockito.any;
19 import static org.mockito.Mockito.eq;
20 import static org.mockito.Mockito.mock;
21 import static org.mockito.Mockito.never;
22 import static org.mockito.Mockito.verify;
23 import static org.mockito.Mockito.when;
24 import static org.mockito.MockitoAnnotations.openMocks;
25 import static org.openhab.binding.wundergroundupdatereceiver.internal.WundergroundUpdateReceiverBindingConstants.DATEUTC_DATETIME_CHANNELTYPEUID;
26 import static org.openhab.binding.wundergroundupdatereceiver.internal.WundergroundUpdateReceiverBindingConstants.HUMIDITY_GROUP;
27 import static org.openhab.binding.wundergroundupdatereceiver.internal.WundergroundUpdateReceiverBindingConstants.LAST_QUERY_STATE_CHANNELTYPEUID;
28 import static org.openhab.binding.wundergroundupdatereceiver.internal.WundergroundUpdateReceiverBindingConstants.LAST_QUERY_TRIGGER_CHANNELTYPEUID;
29 import static org.openhab.binding.wundergroundupdatereceiver.internal.WundergroundUpdateReceiverBindingConstants.LAST_RECEIVED_DATETIME_CHANNELTYPEUID;
30 import static org.openhab.binding.wundergroundupdatereceiver.internal.WundergroundUpdateReceiverBindingConstants.METADATA_GROUP;
31 import static org.openhab.binding.wundergroundupdatereceiver.internal.WundergroundUpdateReceiverBindingConstants.POLLUTION_GROUP;
32 import static org.openhab.binding.wundergroundupdatereceiver.internal.WundergroundUpdateReceiverBindingConstants.PRESSURE_GROUP;
33 import static org.openhab.binding.wundergroundupdatereceiver.internal.WundergroundUpdateReceiverBindingConstants.RAIN_GROUP;
34 import static org.openhab.binding.wundergroundupdatereceiver.internal.WundergroundUpdateReceiverBindingConstants.SUNLIGHT_GROUP;
35 import static org.openhab.binding.wundergroundupdatereceiver.internal.WundergroundUpdateReceiverBindingConstants.TEMPERATURE_GROUP;
36 import static org.openhab.binding.wundergroundupdatereceiver.internal.WundergroundUpdateReceiverBindingConstants.WIND_GROUP;
37
38 import java.io.IOException;
39 import java.util.List;
40 import java.util.Map;
41
42 import javax.servlet.ServletException;
43 import javax.servlet.http.HttpServletResponse;
44
45 import org.eclipse.jdt.annotation.NonNullByDefault;
46 import org.eclipse.jetty.http.HttpFields;
47 import org.eclipse.jetty.http.HttpURI;
48 import org.eclipse.jetty.http.HttpVersion;
49 import org.eclipse.jetty.http.MetaData;
50 import org.eclipse.jetty.server.HttpChannel;
51 import org.eclipse.jetty.server.Request;
52 import org.junit.jupiter.api.BeforeEach;
53 import org.junit.jupiter.api.Test;
54 import org.mockito.Answers;
55 import org.mockito.ArgumentCaptor;
56 import org.mockito.Mock;
57 import org.openhab.core.config.core.Configuration;
58 import org.openhab.core.library.types.DateTimeType;
59 import org.openhab.core.library.types.DecimalType;
60 import org.openhab.core.library.types.OnOffType;
61 import org.openhab.core.library.types.QuantityType;
62 import org.openhab.core.library.types.StringType;
63 import org.openhab.core.library.unit.ImperialUnits;
64 import org.openhab.core.library.unit.Units;
65 import org.openhab.core.thing.Channel;
66 import org.openhab.core.thing.ChannelUID;
67 import org.openhab.core.thing.ManagedThingProvider;
68 import org.openhab.core.thing.Thing;
69 import org.openhab.core.thing.ThingStatus;
70 import org.openhab.core.thing.ThingUID;
71 import org.openhab.core.thing.binding.ThingHandlerCallback;
72 import org.openhab.core.thing.binding.builder.ChannelBuilder;
73 import org.openhab.core.thing.binding.builder.ThingBuilder;
74 import org.openhab.core.thing.type.ChannelKind;
75 import org.openhab.core.thing.type.ChannelTypeBuilder;
76 import org.openhab.core.thing.type.ChannelTypeRegistry;
77 import org.osgi.service.http.NamespaceException;
78
79 /**
80  * @author Daniel Demus - Initial contribution
81  */
82 @NonNullByDefault({})
83 class WundergroundUpdateReceiverServletTest {
84
85     private static final String STATION_ID_1 = "abcd1234";
86     private static final String STATION_ID_2 = "1234abcd";
87     private static final String REQ_STATION_ID = "dfggger";
88     private static final ThingUID TEST_THING_UID = new ThingUID(
89             WundergroundUpdateReceiverBindingConstants.THING_TYPE_UPDATE_RECEIVER, "test-receiver");
90
91     private @Mock ChannelTypeRegistry channelTypeRegistry;
92     private @Mock WundergroundUpdateReceiverDiscoveryService discoveryService;
93     private @Mock ManagedThingProvider managedThingProvider;
94
95     @BeforeEach
96     public void setUp() {
97         openMocks(this);
98     }
99
100     @Test
101     void theServletIsActiveAfterTheFirstHandlerIsAdded() throws ServletException, NamespaceException {
102         // Given
103         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(discoveryService);
104         WundergroundUpdateReceiverHandler handler = mock(WundergroundUpdateReceiverHandler.class);
105         when(handler.getStationId()).thenReturn(STATION_ID_1);
106
107         // When
108         sut.addHandler(handler);
109
110         // Then
111         assertThat(sut.isActive(), is(true));
112     }
113
114     @Test
115     void theServletIsInactiveAfterTheLastHandlerIsRemovedAndBackgroundDiscoveryIsDisabled()
116             throws ServletException, NamespaceException {
117         // Given
118         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(discoveryService);
119         WundergroundUpdateReceiverHandler handler = mock(WundergroundUpdateReceiverHandler.class);
120         when(handler.getStationId()).thenReturn(STATION_ID_1);
121         when(discoveryService.isBackgroundDiscoveryEnabled()).thenReturn(false);
122
123         // When
124         sut.addHandler(handler);
125
126         // Then
127         assertThat(sut.isActive(), is(true));
128
129         // When
130         sut.removeHandler(handler.getStationId());
131
132         // Then
133         assertThat(sut.isActive(), is(false));
134     }
135
136     @Test
137     void theServletIsActiveAfterTheLastHandlerIsRemovedButBackgroundDiscoveryIsEnabled()
138             throws ServletException, NamespaceException {
139         // Given
140         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(discoveryService);
141         WundergroundUpdateReceiverHandler handler = mock(WundergroundUpdateReceiverHandler.class);
142         when(handler.getStationId()).thenReturn(STATION_ID_1);
143         when(discoveryService.isBackgroundDiscoveryEnabled()).thenReturn(true);
144
145         // When
146         sut.addHandler(handler);
147
148         // Then
149         assertThat(sut.isActive(), is(true));
150
151         // When
152         sut.removeHandler(handler.getStationId());
153
154         // Then
155         assertThat(sut.isActive(), is(true));
156     }
157
158     @Test
159     void onDisposeAllHandlersAreRemovedAndServletIsInactive() throws ServletException, NamespaceException {
160         // Given
161         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(discoveryService);
162         WundergroundUpdateReceiverHandler handler1 = mock(WundergroundUpdateReceiverHandler.class);
163         when(handler1.getStationId()).thenReturn(STATION_ID_1);
164         WundergroundUpdateReceiverHandler handler2 = mock(WundergroundUpdateReceiverHandler.class);
165         when(handler2.getStationId()).thenReturn(STATION_ID_2);
166
167         // When
168         sut.addHandler(handler1);
169         sut.addHandler(handler2);
170
171         // Then
172         assertThat(sut.isActive(), is(true));
173
174         // When
175         sut.dispose();
176
177         // Then
178         assertThat(sut.isActive(), is(false));
179     }
180
181     @Test
182     void OnDisposeAllHandlersAreRemovedAndServletIsInactiveEvenThoughBackgroundDiscoveryIsEnabled()
183             throws ServletException, NamespaceException {
184         // Given
185         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(discoveryService);
186         WundergroundUpdateReceiverHandler handler1 = mock(WundergroundUpdateReceiverHandler.class);
187         when(handler1.getStationId()).thenReturn(STATION_ID_1);
188         WundergroundUpdateReceiverHandler handler2 = mock(WundergroundUpdateReceiverHandler.class);
189         when(handler2.getStationId()).thenReturn(STATION_ID_2);
190         when(discoveryService.isBackgroundDiscoveryEnabled()).thenReturn(true);
191
192         // When
193         sut.addHandler(handler1);
194         sut.addHandler(handler2);
195
196         // Then
197         assertThat(sut.isActive(), is(true));
198
199         // When
200         sut.dispose();
201
202         // Then
203         assertThat(sut.isActive(), is(false));
204     }
205
206     @Test
207     void changedStationIdPropagatesToHandlerKey() throws ServletException, NamespaceException {
208         // Given
209         Thing thing = mock(Thing.class);
210         when(thing.getUID()).thenReturn(TEST_THING_UID);
211         when(thing.getConfiguration()).thenReturn(new Configuration(
212                 Map.of(WundergroundUpdateReceiverBindingConstants.REPRESENTATION_PROPERTY, STATION_ID_1)));
213         when(thing.getStatus()).thenReturn(ThingStatus.ONLINE);
214         when(this.channelTypeRegistry.getChannelType(LAST_RECEIVED_DATETIME_CHANNELTYPEUID))
215                 .thenReturn(ChannelTypeBuilder.state(LAST_RECEIVED_DATETIME_CHANNELTYPEUID, "Label", "String").build());
216         when(this.channelTypeRegistry.getChannelType(DATEUTC_DATETIME_CHANNELTYPEUID))
217                 .thenReturn(ChannelTypeBuilder.state(DATEUTC_DATETIME_CHANNELTYPEUID, "Label", "DateTime").build());
218         when(this.channelTypeRegistry.getChannelType(LAST_QUERY_STATE_CHANNELTYPEUID))
219                 .thenReturn(ChannelTypeBuilder.state(LAST_QUERY_STATE_CHANNELTYPEUID, "Label", "String").build());
220         when(this.channelTypeRegistry.getChannelType(LAST_QUERY_TRIGGER_CHANNELTYPEUID))
221                 .thenReturn(ChannelTypeBuilder.trigger(LAST_QUERY_TRIGGER_CHANNELTYPEUID, "Label").build());
222         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(discoveryService);
223         WundergroundUpdateReceiverHandler handler = new WundergroundUpdateReceiverHandler(thing, sut, discoveryService,
224                 new WundergroundUpdateReceiverUnknownChannelTypeProvider(), channelTypeRegistry, managedThingProvider);
225         ThingHandlerCallback mockCallback = mock(ThingHandlerCallback.class);
226         handler.setCallback(mockCallback);
227
228         // When
229         handler.initialize();
230
231         // Then
232         assertThat(sut.isActive(), is(true));
233         assertThat(sut.getStationIds(), hasItems(STATION_ID_1));
234
235         // When
236         handler.handleConfigurationUpdate(
237                 Map.of(WundergroundUpdateReceiverBindingConstants.REPRESENTATION_PROPERTY, STATION_ID_2));
238
239         // Then
240         assertThat(sut.isActive(), is(true));
241         ArgumentCaptor<Thing> thingArg = ArgumentCaptor.forClass(Thing.class);
242         verify(mockCallback).configurationUpdated(thingArg.capture());
243         assertThat(thingArg.getValue().getConfiguration().getProperties()
244                 .get(WundergroundUpdateReceiverBindingConstants.REPRESENTATION_PROPERTY), is(STATION_ID_2));
245     }
246
247     @Test
248     void aGetRequestIsCorrectlyParsed() throws IOException {
249         // Given
250         ThingUID testThingUID = new ThingUID(WundergroundUpdateReceiverBindingConstants.THING_TYPE_UPDATE_RECEIVER,
251                 "test-receiver");
252         final String queryString = "ID=dfggger&PASSWORD=XXXXXX&tempf=26.1&humidity=74&dewptf=18.9&windchillf=26.1&winddir=14&windspeedmph=1.34&windgustmph=2.46&rainin=0.00&dailyrainin=0.00&weeklyrainin=0.00&monthlyrainin=0.08&yearlyrainin=3.06&solarradiation=42.24&UV=1&indoortempf=69.3&indoorhumidity=32&baromin=30.39&AqNOX=21&lowbatt=1&dateutc=2021-02-07%2014:04:03&softwaretype=WH2600%20V2.2.8&action=updateraw&realtime=1&rtfreq=5";
253         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(discoveryService);
254         List<Channel> channels = List.of(
255                 ChannelBuilder
256                         .create(new ChannelUID(testThingUID, METADATA_GROUP,
257                                 WundergroundUpdateReceiverBindingConstants.DATEUTC), "String")
258                         .withKind(ChannelKind.STATE).build(),
259                 ChannelBuilder
260                         .create(new ChannelUID(testThingUID, METADATA_GROUP,
261                                 WundergroundUpdateReceiverBindingConstants.REALTIME_FREQUENCY), "Number")
262                         .withKind(ChannelKind.STATE).build(),
263                 ChannelBuilder
264                         .create(new ChannelUID(testThingUID, METADATA_GROUP,
265                                 WundergroundUpdateReceiverBindingConstants.LOW_BATTERY), "Switch")
266                         .withKind(ChannelKind.STATE).build(),
267                 ChannelBuilder
268                         .create(new ChannelUID(testThingUID, WIND_GROUP,
269                                 WundergroundUpdateReceiverBindingConstants.WIND_DIRECTION), "Number:Angle")
270                         .withKind(ChannelKind.STATE).build(),
271                 ChannelBuilder
272                         .create(new ChannelUID(testThingUID, WIND_GROUP,
273                                 WundergroundUpdateReceiverBindingConstants.WIND_SPEED), "Number:Speed")
274                         .withKind(ChannelKind.STATE).build(),
275                 ChannelBuilder
276                         .create(new ChannelUID(testThingUID, WIND_GROUP,
277                                 WundergroundUpdateReceiverBindingConstants.GUST_SPEED), "Number:Speed")
278                         .withKind(ChannelKind.STATE).build(),
279                 ChannelBuilder
280                         .create(new ChannelUID(testThingUID, TEMPERATURE_GROUP,
281                                 WundergroundUpdateReceiverBindingConstants.TEMPERATURE), "Number:Temperature")
282                         .withKind(ChannelKind.STATE).build(),
283                 ChannelBuilder
284                         .create(new ChannelUID(testThingUID, RAIN_GROUP,
285                                 WundergroundUpdateReceiverBindingConstants.RAIN_IN), "Number:Length")
286                         .withKind(ChannelKind.STATE).build(),
287                 ChannelBuilder
288                         .create(new ChannelUID(testThingUID, SUNLIGHT_GROUP,
289                                 WundergroundUpdateReceiverBindingConstants.SOLAR_RADIATION), "Number:Intensity")
290                         .withKind(ChannelKind.STATE).build(),
291                 ChannelBuilder
292                         .create(new ChannelUID(testThingUID, SUNLIGHT_GROUP,
293                                 WundergroundUpdateReceiverBindingConstants.UV), "Number")
294                         .withKind(ChannelKind.STATE).build(),
295                 ChannelBuilder
296                         .create(new ChannelUID(testThingUID, PRESSURE_GROUP,
297                                 WundergroundUpdateReceiverBindingConstants.BAROM_IN), "Number:Pressure")
298                         .withKind(ChannelKind.STATE).build(),
299                 ChannelBuilder
300                         .create(new ChannelUID(testThingUID, HUMIDITY_GROUP,
301                                 WundergroundUpdateReceiverBindingConstants.DEWPOINT), "Number:Temperature")
302                         .withKind(ChannelKind.STATE).build(),
303                 ChannelBuilder
304                         .create(new ChannelUID(testThingUID, HUMIDITY_GROUP,
305                                 WundergroundUpdateReceiverBindingConstants.HUMIDITY), "Number:Dimensionless")
306                         .withKind(ChannelKind.STATE).build(),
307                 ChannelBuilder
308                         .create(new ChannelUID(testThingUID, POLLUTION_GROUP,
309                                 WundergroundUpdateReceiverBindingConstants.AQ_NOX), "Number:Dimensionless")
310                         .withKind(ChannelKind.STATE).build(),
311                 ChannelBuilder
312                         .create(new ChannelUID(testThingUID, METADATA_GROUP,
313                                 WundergroundUpdateReceiverBindingConstants.LAST_QUERY_TRIGGER), "StringType")
314                         .withKind(ChannelKind.TRIGGER).build());
315
316         Configuration config = new Configuration(Map.of("stationId", REQ_STATION_ID));
317         Thing testThing = ThingBuilder
318                 .create(WundergroundUpdateReceiverBindingConstants.THING_TYPE_UPDATE_RECEIVER, testThingUID)
319                 .withChannels(channels).withConfiguration(config).build();
320         ThingHandlerCallback callback = mock(ThingHandlerCallback.class);
321         WundergroundUpdateReceiverHandler handler = new WundergroundUpdateReceiverHandler(testThing, sut,
322                 discoveryService, new WundergroundUpdateReceiverUnknownChannelTypeProvider(), channelTypeRegistry,
323                 managedThingProvider);
324         handler.setCallback(callback);
325         handler.initialize();
326
327         HttpChannel httpChannel = mock(HttpChannel.class);
328         MetaData.Request request = new MetaData.Request("GET",
329                 new HttpURI("http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + queryString),
330                 HttpVersion.HTTP_1_1, new HttpFields());
331         Request req = new Request(httpChannel, null);
332         req.setMetaData(request);
333
334         // When
335         sut.doGet(req, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
336
337         // Then
338         verify(callback).stateUpdated(
339                 new ChannelUID(TEST_THING_UID, METADATA_GROUP, WundergroundUpdateReceiverBindingConstants.DATEUTC),
340                 StringType.valueOf("2021-02-07 14:04:03"));
341         verify(callback).stateUpdated(
342                 new ChannelUID(TEST_THING_UID, METADATA_GROUP, WundergroundUpdateReceiverBindingConstants.LOW_BATTERY),
343                 OnOffType.ON);
344         verify(callback).stateUpdated(new ChannelUID(TEST_THING_UID, METADATA_GROUP,
345                 WundergroundUpdateReceiverBindingConstants.REALTIME_FREQUENCY), new DecimalType(5));
346         verify(callback).stateUpdated(
347                 new ChannelUID(TEST_THING_UID, WIND_GROUP, WundergroundUpdateReceiverBindingConstants.WIND_DIRECTION),
348                 new QuantityType<>(14, Units.DEGREE_ANGLE));
349         verify(callback).stateUpdated(
350                 new ChannelUID(TEST_THING_UID, WIND_GROUP, WundergroundUpdateReceiverBindingConstants.WIND_SPEED),
351                 new QuantityType<>(1.34, ImperialUnits.MILES_PER_HOUR));
352         verify(callback).stateUpdated(
353                 new ChannelUID(TEST_THING_UID, WIND_GROUP, WundergroundUpdateReceiverBindingConstants.GUST_SPEED),
354                 new QuantityType<>(2.46, ImperialUnits.MILES_PER_HOUR));
355         verify(callback).stateUpdated(
356                 new ChannelUID(TEST_THING_UID, TEMPERATURE_GROUP,
357                         WundergroundUpdateReceiverBindingConstants.TEMPERATURE),
358                 new QuantityType<>(26.1, ImperialUnits.FAHRENHEIT));
359         verify(callback).stateUpdated(
360                 new ChannelUID(TEST_THING_UID, RAIN_GROUP, WundergroundUpdateReceiverBindingConstants.RAIN_IN),
361                 new QuantityType<>(0, ImperialUnits.INCH));
362         verify(callback).stateUpdated(
363                 new ChannelUID(TEST_THING_UID, SUNLIGHT_GROUP,
364                         WundergroundUpdateReceiverBindingConstants.SOLAR_RADIATION),
365                 new QuantityType<>(42.24, Units.IRRADIANCE));
366         verify(callback).stateUpdated(
367                 new ChannelUID(TEST_THING_UID, SUNLIGHT_GROUP, WundergroundUpdateReceiverBindingConstants.UV),
368                 new DecimalType(1));
369         verify(callback).stateUpdated(
370                 new ChannelUID(TEST_THING_UID, PRESSURE_GROUP, WundergroundUpdateReceiverBindingConstants.BAROM_IN),
371                 new QuantityType<>(30.39, ImperialUnits.INCH_OF_MERCURY));
372         verify(callback).stateUpdated(
373                 new ChannelUID(TEST_THING_UID, HUMIDITY_GROUP, WundergroundUpdateReceiverBindingConstants.DEWPOINT),
374                 new QuantityType<>(18.9, ImperialUnits.FAHRENHEIT));
375         verify(callback).stateUpdated(
376                 new ChannelUID(TEST_THING_UID, HUMIDITY_GROUP, WundergroundUpdateReceiverBindingConstants.HUMIDITY),
377                 new QuantityType<>(74, Units.PERCENT));
378         verify(callback).stateUpdated(
379                 new ChannelUID(TEST_THING_UID, POLLUTION_GROUP, WundergroundUpdateReceiverBindingConstants.AQ_NOX),
380                 new QuantityType<>(21, Units.PARTS_PER_BILLION));
381         verify(callback, never()).stateUpdated(new ChannelUID(TEST_THING_UID, METADATA_GROUP,
382                 WundergroundUpdateReceiverBindingConstants.SOFTWARE_TYPE), StringType.valueOf("WH2600 V2.2.8"));
383         verify(callback).stateUpdated(new ChannelUID(TEST_THING_UID, METADATA_GROUP,
384                 WundergroundUpdateReceiverBindingConstants.LAST_QUERY_STATE), StringType.valueOf(queryString));
385         verify(callback).stateUpdated(eq(new ChannelUID(TEST_THING_UID, METADATA_GROUP,
386                 WundergroundUpdateReceiverBindingConstants.LAST_RECEIVED)), any(DateTimeType.class));
387         verify(callback).channelTriggered(testThing, new ChannelUID(TEST_THING_UID, METADATA_GROUP,
388                 WundergroundUpdateReceiverBindingConstants.LAST_QUERY_TRIGGER), queryString);
389     }
390
391     @Test
392     void aGetRequestWithIndexedParametresAreCorrectlyParsed() throws IOException {
393         // Given
394         ThingUID testThingUID = new ThingUID(WundergroundUpdateReceiverBindingConstants.THING_TYPE_UPDATE_RECEIVER,
395                 "test-receiver");
396         final String queryString = "ID=dfggger&PASSWORD=XXXXXX&temp1f=26.1&humidity=74&temp2f=25.1&lowbatt=1&soilmoisture1=78&soilmoisture2=73&dateutc=2021-02-07%2014:04:03&softwaretype=WH2600%20V2.2.8&action=updateraw&realtime=1&rtfreq=5";
397         WundergroundUpdateReceiverServlet sut = new WundergroundUpdateReceiverServlet(discoveryService);
398         List<Channel> channels = List.of(
399                 ChannelBuilder
400                         .create(new ChannelUID(testThingUID, METADATA_GROUP,
401                                 WundergroundUpdateReceiverBindingConstants.DATEUTC), "String")
402                         .withKind(ChannelKind.STATE).build(),
403                 ChannelBuilder
404                         .create(new ChannelUID(testThingUID, METADATA_GROUP,
405                                 WundergroundUpdateReceiverBindingConstants.REALTIME_FREQUENCY), "Number")
406                         .withKind(ChannelKind.STATE).build(),
407                 ChannelBuilder
408                         .create(new ChannelUID(testThingUID, METADATA_GROUP,
409                                 WundergroundUpdateReceiverBindingConstants.LOW_BATTERY), "Switch")
410                         .withKind(ChannelKind.STATE).build(),
411                 ChannelBuilder.create(new ChannelUID(testThingUID, TEMPERATURE_GROUP, "temp1f"), "Number:Temperature")
412                         .withKind(ChannelKind.STATE).build(),
413                 ChannelBuilder.create(new ChannelUID(testThingUID, TEMPERATURE_GROUP, "temp2f"), "Number:Temperature")
414                         .withKind(ChannelKind.STATE).build(),
415                 ChannelBuilder
416                         .create(new ChannelUID(testThingUID, HUMIDITY_GROUP, "soilmoisture1"), "Number:Dimensionless")
417                         .withKind(ChannelKind.STATE).build(),
418                 ChannelBuilder
419                         .create(new ChannelUID(testThingUID, HUMIDITY_GROUP, "soilmoisture2"), "Number:Dimensionless")
420                         .withKind(ChannelKind.STATE).build(),
421                 ChannelBuilder
422                         .create(new ChannelUID(testThingUID, HUMIDITY_GROUP,
423                                 WundergroundUpdateReceiverBindingConstants.HUMIDITY), "Number:Dimensionless")
424                         .withKind(ChannelKind.STATE).build(),
425                 ChannelBuilder
426                         .create(new ChannelUID(testThingUID, METADATA_GROUP,
427                                 WundergroundUpdateReceiverBindingConstants.LAST_QUERY_TRIGGER), "StringType")
428                         .withKind(ChannelKind.TRIGGER).build());
429
430         Configuration config = new Configuration(Map.of("stationId", REQ_STATION_ID));
431         Thing testThing = ThingBuilder
432                 .create(WundergroundUpdateReceiverBindingConstants.THING_TYPE_UPDATE_RECEIVER, testThingUID)
433                 .withChannels(channels).withConfiguration(config).build();
434         ThingHandlerCallback callback = mock(ThingHandlerCallback.class);
435         WundergroundUpdateReceiverHandler handler = new WundergroundUpdateReceiverHandler(testThing, sut,
436                 discoveryService, new WundergroundUpdateReceiverUnknownChannelTypeProvider(), channelTypeRegistry,
437                 managedThingProvider);
438         handler.setCallback(callback);
439         handler.initialize();
440
441         HttpChannel httpChannel = mock(HttpChannel.class);
442         MetaData.Request request = new MetaData.Request("GET",
443                 new HttpURI("http://localhost" + WundergroundUpdateReceiverServlet.SERVLET_URL + "?" + queryString),
444                 HttpVersion.HTTP_1_1, new HttpFields());
445         Request req = new Request(httpChannel, null);
446         req.setMetaData(request);
447
448         // When
449         sut.doGet(req, mock(HttpServletResponse.class, Answers.RETURNS_MOCKS));
450
451         // Then
452         verify(callback).stateUpdated(
453                 new ChannelUID(TEST_THING_UID, METADATA_GROUP, WundergroundUpdateReceiverBindingConstants.DATEUTC),
454                 StringType.valueOf("2021-02-07 14:04:03"));
455         verify(callback).stateUpdated(
456                 new ChannelUID(TEST_THING_UID, METADATA_GROUP, WundergroundUpdateReceiverBindingConstants.LOW_BATTERY),
457                 OnOffType.ON);
458         verify(callback).stateUpdated(new ChannelUID(TEST_THING_UID, METADATA_GROUP,
459                 WundergroundUpdateReceiverBindingConstants.REALTIME_FREQUENCY), new DecimalType(5));
460         verify(callback).stateUpdated(new ChannelUID(TEST_THING_UID, TEMPERATURE_GROUP, "temp1f"),
461                 new QuantityType<>(26.1, ImperialUnits.FAHRENHEIT));
462         verify(callback).stateUpdated(new ChannelUID(TEST_THING_UID, TEMPERATURE_GROUP, "temp2f"),
463                 new QuantityType<>(25.1, ImperialUnits.FAHRENHEIT));
464         verify(callback).stateUpdated(
465                 new ChannelUID(TEST_THING_UID, HUMIDITY_GROUP, WundergroundUpdateReceiverBindingConstants.HUMIDITY),
466                 new QuantityType<>(74, Units.PERCENT));
467         verify(callback).stateUpdated(new ChannelUID(TEST_THING_UID, HUMIDITY_GROUP, "soilmoisture1"),
468                 new QuantityType<>(78, Units.PERCENT));
469         verify(callback).stateUpdated(new ChannelUID(TEST_THING_UID, HUMIDITY_GROUP, "soilmoisture2"),
470                 new QuantityType<>(73, Units.PERCENT));
471         verify(callback, never()).stateUpdated(new ChannelUID(TEST_THING_UID, METADATA_GROUP,
472                 WundergroundUpdateReceiverBindingConstants.SOFTWARE_TYPE), StringType.valueOf("WH2600 V2.2.8"));
473         verify(callback).stateUpdated(new ChannelUID(TEST_THING_UID, METADATA_GROUP,
474                 WundergroundUpdateReceiverBindingConstants.LAST_QUERY_STATE), StringType.valueOf(queryString));
475         verify(callback).stateUpdated(eq(new ChannelUID(TEST_THING_UID, METADATA_GROUP,
476                 WundergroundUpdateReceiverBindingConstants.LAST_RECEIVED)), any(DateTimeType.class));
477         verify(callback).channelTriggered(testThing, new ChannelUID(TEST_THING_UID, METADATA_GROUP,
478                 WundergroundUpdateReceiverBindingConstants.LAST_QUERY_TRIGGER), queryString);
479     }
480 }