]> git.basschouten.com Git - openhab-addons.git/blob
f71bc1bed7db17573f5ac841908020b31376df01
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.airgradient.internal.handler;
14
15 import static org.eclipse.jdt.annotation.Checks.requireNonNull;
16 import static org.hamcrest.MatcherAssert.assertThat;
17 import static org.hamcrest.Matchers.closeTo;
18 import static org.hamcrest.Matchers.empty;
19 import static org.hamcrest.Matchers.is;
20 import static org.hamcrest.Matchers.not;
21 import static org.mockito.ArgumentMatchers.anyString;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.eclipse.jetty.client.HttpClient;
26 import org.eclipse.jetty.client.api.ContentResponse;
27 import org.eclipse.jetty.client.api.Request;
28 import org.junit.jupiter.api.Assertions;
29 import org.junit.jupiter.api.BeforeEach;
30 import org.junit.jupiter.api.Test;
31 import org.mockito.Mockito;
32 import org.openhab.binding.airgradient.internal.communication.AirGradientCommunicationException;
33 import org.openhab.binding.airgradient.internal.communication.RemoteAPIController;
34 import org.openhab.binding.airgradient.internal.config.AirGradientAPIConfiguration;
35
36 import com.google.gson.Gson;
37
38 /**
39  * @author Jørgen Austvik - Initial contribution
40  */
41 @SuppressWarnings({ "null" })
42 @NonNullByDefault
43 public class RemoteApiControllerTest {
44
45     private static final AirGradientAPIConfiguration TEST_CONFIG = new AirGradientAPIConfiguration() {
46         {
47             hostname = "abc123";
48             token = "def456";
49         }
50     };
51
52     private static final String SINGLE_CONTENT = """
53              {"locationId":4321,"locationName":"Some other name","pm01":1,"pm02":2,"pm10":2,"pm003Count":536,"atmp":20.45,"rhum":16.61,"rco2":456,"tvoc":51.644928,"wifi":-54,"timestamp":"2024-01-07T13:00:20.000Z","ledMode":"co2","ledCo2Threshold1":1000,"ledCo2Threshold2":2000,"ledCo2ThresholdEnd":4000,"serialno":"serial","firmwareVersion":null,"tvocIndex":null,"noxIndex":null}
54             """;
55
56     private static final String MULTI_CONTENT = """
57             [
58              {"locationId":1234,"locationName":"Some Name","pm01":1,"pm02":2,"pm10":2,"pm003Count":536,"atmp":20.45,"rhum":16.61,"rco2":null,"tvoc":null,"wifi":-54,"timestamp":"2024-01-07T13:00:20.000Z","ledMode":"co2","ledCo2Threshold1":1000,"ledCo2Threshold2":2000,"ledCo2ThresholdEnd":4000,"serialno":"serial","firmwareVersion":null,"tvocIndex":null,"noxIndex":null},
59              {"locationId":4321,"locationName":"Some other name","pm01":1,"pm02":2,"pm10":2,"pm003Count":536,"atmp":20.45,"rhum":16.61,"rco2":null,"tvoc":null,"wifi":-54,"timestamp":"2024-01-07T13:00:20.000Z","ledMode":"co2","ledCo2Threshold1":1000,"ledCo2Threshold2":2000,"ledCo2ThresholdEnd":4000,"serialno":"serial","firmwareVersion":null,"tvocIndex":null,"noxIndex":null}
60             ]""";
61     private static final String MULTI_CONTENT2 = """
62             [{"locationId":654321,"locationName":"xxxx","pm01":0,"pm02":1,"pm10":1,"pm003Count":null,"atmp":24.2,"rhum":18,"rco2":519,"tvoc":50.793266,"wifi":-62,"timestamp":"2024-02-01T19:15:37.000Z","ledMode":"co2","ledCo2Threshold1":1000,"ledCo2Threshold2":2000,"ledCo2ThresholdEnd":4000,"serialno":"ecda3b1a2a50","firmwareVersion":null,"tvocIndex":52,"noxIndex":1},{"locationId":123456,"locationName":"yyyy","pm01":0,"pm02":0,"pm10":0,"pm003Count":105,"atmp":22.33,"rhum":24,"rco2":468,"tvoc":130.95694,"wifi":-50,"timestamp":"2024-02-01T19:15:34.000Z","ledMode":"co2","ledCo2Threshold1":1000,"ledCo2Threshold2":2000,"ledCo2ThresholdEnd":4000,"serialno":"84fce612e644","firmwareVersion":null,"tvocIndex":137,"noxIndex":1}]
63             """;
64
65     private static final String PROMETHEUS_CONTENT = """
66             # HELP pm02 Particulate Matter PM2.5 value
67             # TYPE pm02 gauge
68             pm02{id="Airgradient"}6
69             # HELP rco2 CO2 value, in ppm
70             # TYPE rco2 gauge
71             rco2{id="Airgradient"}862
72             # HELP atmp Temperature, in degrees Celsius
73             # TYPE atmp gauge
74             atmp{id="Airgradient"}31.6
75             # HELP rhum Relative humidity, in percent
76             # TYPE rhum gauge
77             rhum{id="Airgradient"}38
78             # HELP tvoc Total volatile organic components, in μg/m³
79             # TYPE tvoc gauge
80             tvoc{id="Airgradient"}51.644928
81             # HELP nox, in μg/m³
82             # TYPE nox gauge
83             nox{id="Airgradient"}1
84             """;
85
86     private static final String OPEN_METRICS_CONTENT = """
87             # HELP airgradient_info AirGradient device information
88             # TYPE airgradient_info info
89             airgradient_info{airgradient_serial_number="4XXXXXXXXXXc",airgradient_device_type="ONE_I-9PSL",airgradient_library_version="3.0.4"} 1
90             # HELP airgradient_config_ok 1 if the AirGradient device was able to successfully fetch its configuration from the server
91             # TYPE airgradient_config_ok gauge
92             airgradient_config_ok{} 1
93             # HELP airgradient_post_ok 1 if the AirGradient device was able to successfully send to the server
94             # TYPE airgradient_post_ok gauge
95             airgradient_post_ok{} 1
96             # HELP airgradient_wifi_rssi_dbm WiFi signal strength from the AirGradient device perspective, in dBm
97             # TYPE airgradient_wifi_rssi_dbm gauge
98             # UNIT airgradient_wifi_rssi_dbm dbm
99             airgradient_wifi_rssi_dbm{} -51
100             # HELP airgradient_co2_ppm Carbon dioxide concentration as measured by the AirGradient S8 sensor, in parts per million
101             # TYPE airgradient_co2_ppm gauge
102             # UNIT airgradient_co2_ppm ppm
103             airgradient_co2_ppm{} 589
104             # HELP airgradient_pm1_ugm3 PM1.0 concentration as measured by the AirGradient PMS sensor, in micrograms per cubic meter
105             # TYPE airgradient_pm1_ugm3 gauge
106             # UNIT airgradient_pm1_ugm3 ugm3
107             airgradient_pm1_ugm3{} 3
108             # HELP airgradient_pm2d5_ugm3 PM2.5 concentration as measured by the AirGradient PMS sensor, in micrograms per cubic meter
109             # TYPE airgradient_pm2d5_ugm3 gauge
110             # UNIT airgradient_pm2d5_ugm3 ugm3
111             airgradient_pm2d5_ugm3{} 3
112             # HELP airgradient_pm10_ugm3 PM10 concentration as measured by the AirGradient PMS sensor, in micrograms per cubic meter
113             # TYPE airgradient_pm10_ugm3 gauge
114             # UNIT airgradient_pm10_ugm3 ugm3
115             airgradient_pm10_ugm3{} 3
116             # HELP airgradient_pm0d3_p100ml PM0.3 concentration as measured by the AirGradient PMS sensor, in number of particules per 100 milliliters
117             # TYPE airgradient_pm0d3_p100ml gauge
118             # UNIT airgradient_pm0d3_p100ml p100ml
119             airgradient_pm0d3_p100ml{} 594
120             # HELP airgradient_tvoc_index The processed Total Volatile Organic Compounds (TVOC) index as measured by the AirGradient SGP sensor
121             # TYPE airgradient_tvoc_index gauge
122             airgradient_tvoc_index{} 220
123             # HELP airgradient_tvoc_raw_index The raw input value to the Total Volatile Organic Compounds (TVOC) index as measured by the AirGradient SGP sensor
124             # TYPE airgradient_tvoc_raw_index gauge
125             airgradient_tvoc_raw_index{} 30801
126             # HELP airgradient_nox_index The processed Nitrous Oxide (NOx) index as measured by the AirGradient SGP sensor
127             # TYPE airgradient_nox_index gauge
128             airgradient_nox_index{} 1
129             # HELP airgradient_temperature_degc The ambient temperature as measured by the AirGradient SHT sensor, in degrees Celsius
130             # TYPE airgradient_temperature_degc gauge
131             # UNIT airgradient_temperature_degc degc
132             airgradient_temperature_degc{} 23.69
133             # HELP airgradient_humidity_percent The relative humidity as measured by the AirGradient SHT sensor
134             # TYPE airgradient_humidity_percent gauge
135             # UNIT airgradient_humidity_percent percent
136             airgradient_humidity_percent{} 39
137             # EOF
138             """;
139
140     @Nullable
141     private RemoteAPIController sut;
142
143     @Nullable
144     HttpClient httpClientMock;
145
146     @Nullable
147     Request requestMock;
148
149     @BeforeEach
150     public void setUp() {
151         httpClientMock = Mockito.mock(HttpClient.class);
152         requestMock = Mockito.mock(Request.class);
153
154         sut = new RemoteAPIController(requireNonNull(httpClientMock), new Gson(), TEST_CONFIG);
155     }
156
157     @Test
158     public void testGetMeasuresNone() throws Exception {
159         ContentResponse response = Mockito.mock(ContentResponse.class);
160         Mockito.when(httpClientMock.newRequest(anyString())).thenReturn(requestMock);
161         Mockito.when(requestMock.send()).thenReturn(response);
162         Mockito.when(response.getMediaType()).thenReturn("application/json");
163         Mockito.when(response.getStatus()).thenReturn(500);
164
165         AirGradientCommunicationException agce = Assertions.assertThrows(AirGradientCommunicationException.class,
166                 () -> sut.getMeasures());
167         assertThat(agce.getMessage(), is("Returned status code: 500"));
168     }
169
170     @Test
171     public void testGetMeasuresSingle() throws Exception {
172         ContentResponse response = Mockito.mock(ContentResponse.class);
173         Mockito.when(httpClientMock.newRequest(anyString())).thenReturn(requestMock);
174         Mockito.when(requestMock.send()).thenReturn(response);
175         Mockito.when(response.getStatus()).thenReturn(200);
176         Mockito.when(response.getMediaType()).thenReturn("application/json");
177         Mockito.when(response.getContentAsString()).thenReturn(SINGLE_CONTENT);
178
179         var res = sut.getMeasures();
180         assertThat(res, is(not(empty())));
181         assertThat(res.size(), is(1));
182         assertThat(res.get(0).locationName, is("Some other name"));
183     }
184
185     @Test
186     public void testGetMeasuresMulti() throws Exception {
187         ContentResponse response = Mockito.mock(ContentResponse.class);
188         Mockito.when(httpClientMock.newRequest(anyString())).thenReturn(requestMock);
189         Mockito.when(requestMock.send()).thenReturn(response);
190         Mockito.when(response.getStatus()).thenReturn(200);
191         Mockito.when(response.getMediaType()).thenReturn("application/json");
192         Mockito.when(response.getContentAsString()).thenReturn(MULTI_CONTENT);
193
194         var res = sut.getMeasures();
195         assertThat(res, is(not(empty())));
196         assertThat(res.size(), is(2));
197         assertThat(res.get(0).locationName, is("Some Name"));
198         assertThat(res.get(1).locationName, is("Some other name"));
199     }
200
201     @Test
202     public void testGetMeasuresMulti2() throws Exception {
203         ContentResponse response = Mockito.mock(ContentResponse.class);
204         Mockito.when(httpClientMock.newRequest(anyString())).thenReturn(requestMock);
205         Mockito.when(requestMock.send()).thenReturn(response);
206         Mockito.when(response.getStatus()).thenReturn(200);
207         Mockito.when(response.getMediaType()).thenReturn("application/json");
208         Mockito.when(response.getContentAsString()).thenReturn(MULTI_CONTENT2);
209
210         var res = sut.getMeasures();
211         assertThat(res, is(not(empty())));
212         assertThat(res.size(), is(2));
213         assertThat(res.get(0).locationName, is("xxxx"));
214         assertThat(res.get(1).locationName, is("yyyy"));
215     }
216
217     @Test
218     public void testGetMeasuresPrometheus() throws Exception {
219         ContentResponse response = Mockito.mock(ContentResponse.class);
220         Mockito.when(httpClientMock.newRequest(anyString())).thenReturn(requestMock);
221         Mockito.when(requestMock.send()).thenReturn(response);
222         Mockito.when(response.getStatus()).thenReturn(200);
223         Mockito.when(response.getMediaType()).thenReturn("text/plain");
224         Mockito.when(response.getContentAsString()).thenReturn(PROMETHEUS_CONTENT);
225
226         var res = sut.getMeasures();
227         assertThat(res, is(not(empty())));
228         assertThat(res.size(), is(1));
229         assertThat(res.get(0).pm02, closeTo(6, 0.1));
230         assertThat(res.get(0).rco2, closeTo(862, 0.1));
231         assertThat(res.get(0).atmp, closeTo(31.6, 0.1));
232         assertThat(res.get(0).rhum, closeTo(38, 0.1));
233         assertThat(res.get(0).tvoc, closeTo(51.644928, 0.1));
234         assertThat(res.get(0).noxIndex, closeTo(1, 0.1));
235         assertThat(res.get(0).locationId, is("Airgradient"));
236         assertThat(res.get(0).locationName, is("Airgradient"));
237         assertThat(res.get(0).serialno, is("Airgradient"));
238     }
239
240     @Test
241     public void testGetMeasuresOpenMetrics() throws Exception {
242         ContentResponse response = Mockito.mock(ContentResponse.class);
243         Mockito.when(httpClientMock.newRequest(anyString())).thenReturn(requestMock);
244         Mockito.when(requestMock.send()).thenReturn(response);
245         Mockito.when(response.getStatus()).thenReturn(200);
246         Mockito.when(response.getMediaType()).thenReturn("application/openmetrics-text");
247         Mockito.when(response.getContentAsString()).thenReturn(OPEN_METRICS_CONTENT);
248
249         var res = sut.getMeasures();
250         assertThat(res, is(not(empty())));
251         assertThat(res.size(), is(1));
252         assertThat(res.get(0).pm01, closeTo(3, 0.1));
253         assertThat(res.get(0).pm02, closeTo(3, 0.1));
254         assertThat(res.get(0).pm10, closeTo(3, 0.1));
255         assertThat(res.get(0).rco2, closeTo(589, 0.1));
256         assertThat(res.get(0).atmp, closeTo(23.69, 0.1));
257         assertThat(res.get(0).rhum, closeTo(39, 0.1));
258         assertThat(res.get(0).tvoc, closeTo(220, 0.1));
259         assertThat(res.get(0).noxIndex, closeTo(1, 0.1));
260         assertThat(res.get(0).serialno, is("4XXXXXXXXXXc"));
261     }
262 }