]> git.basschouten.com Git - openhab-addons.git/blob
5ac87e1bbfa24f73d80195d99ae2f9477a5cc157
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.nest.handler;
14
15 import static org.hamcrest.CoreMatchers.*;
16 import static org.hamcrest.MatcherAssert.assertThat;
17 import static org.hamcrest.core.Is.is;
18 import static org.hamcrest.core.IsNot.not;
19 import static org.mockito.Mockito.*;
20 import static org.openhab.binding.nest.internal.rest.NestStreamingRestClient.PUT;
21
22 import java.io.IOException;
23 import java.time.Instant;
24 import java.time.format.DateTimeParseException;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.TimeZone;
30 import java.util.function.Function;
31
32 import javax.ws.rs.client.ClientBuilder;
33
34 import org.eclipse.jdt.annotation.NonNullByDefault;
35 import org.eclipse.jetty.servlet.ServletHolder;
36 import org.junit.jupiter.api.AfterEach;
37 import org.junit.jupiter.api.BeforeAll;
38 import org.junit.jupiter.api.BeforeEach;
39 import org.openhab.binding.nest.internal.config.NestBridgeConfiguration;
40 import org.openhab.binding.nest.internal.handler.NestBaseHandler;
41 import org.openhab.binding.nest.test.NestTestApiServlet;
42 import org.openhab.binding.nest.test.NestTestBridgeHandler;
43 import org.openhab.binding.nest.test.NestTestHandlerFactory;
44 import org.openhab.binding.nest.test.NestTestServer;
45 import org.openhab.core.config.core.Configuration;
46 import org.openhab.core.events.EventPublisher;
47 import org.openhab.core.items.Item;
48 import org.openhab.core.items.ItemFactory;
49 import org.openhab.core.items.ItemNotFoundException;
50 import org.openhab.core.items.ItemRegistry;
51 import org.openhab.core.items.events.ItemEventFactory;
52 import org.openhab.core.library.types.DateTimeType;
53 import org.openhab.core.test.TestPortUtil;
54 import org.openhab.core.test.java.JavaOSGiTest;
55 import org.openhab.core.test.storage.VolatileStorageService;
56 import org.openhab.core.thing.Bridge;
57 import org.openhab.core.thing.Channel;
58 import org.openhab.core.thing.ChannelUID;
59 import org.openhab.core.thing.ManagedThingProvider;
60 import org.openhab.core.thing.Thing;
61 import org.openhab.core.thing.ThingProvider;
62 import org.openhab.core.thing.ThingTypeUID;
63 import org.openhab.core.thing.ThingUID;
64 import org.openhab.core.thing.binding.ThingHandlerFactory;
65 import org.openhab.core.thing.binding.builder.BridgeBuilder;
66 import org.openhab.core.thing.binding.builder.ChannelBuilder;
67 import org.openhab.core.thing.link.ItemChannelLink;
68 import org.openhab.core.thing.link.ManagedItemChannelLinkProvider;
69 import org.openhab.core.thing.type.ChannelDefinition;
70 import org.openhab.core.thing.type.ChannelGroupDefinition;
71 import org.openhab.core.thing.type.ChannelGroupType;
72 import org.openhab.core.thing.type.ChannelGroupTypeRegistry;
73 import org.openhab.core.thing.type.ChannelType;
74 import org.openhab.core.thing.type.ChannelTypeRegistry;
75 import org.openhab.core.thing.type.ThingType;
76 import org.openhab.core.thing.type.ThingTypeRegistry;
77 import org.openhab.core.types.Command;
78 import org.openhab.core.types.RefreshType;
79 import org.openhab.core.types.State;
80 import org.openhab.core.types.UnDefType;
81 import org.osgi.service.component.ComponentContext;
82 import org.osgi.service.jaxrs.client.SseEventSourceFactory;
83 import org.slf4j.Logger;
84 import org.slf4j.LoggerFactory;
85
86 /**
87  * {@link NestThingHandlerOSGiTest} is an abstract base class for Nest OSGi based tests.
88  *
89  * @author Wouter Born - Increase test coverage
90  */
91 public abstract class NestThingHandlerOSGiTest extends JavaOSGiTest {
92
93     private static final String SERVER_HOST = "127.0.0.1";
94     private static final int SERVER_PORT = TestPortUtil.findFreePort();
95     private static final int SERVER_TIMEOUT = -1;
96     private static final String REDIRECT_URL = "http://" + SERVER_HOST + ":" + SERVER_PORT;
97
98     private final Logger logger = LoggerFactory.getLogger(NestThingHandlerOSGiTest.class);
99
100     private static NestTestServer server;
101     private static NestTestApiServlet servlet = new NestTestApiServlet();
102
103     private ChannelTypeRegistry channelTypeRegistry;
104     private ChannelGroupTypeRegistry channelGroupTypeRegistry;
105     private ItemFactory itemFactory;
106     private ItemRegistry itemRegistry;
107     private EventPublisher eventPublisher;
108     private ManagedThingProvider managedThingProvider;
109     private ThingTypeRegistry thingTypeRegistry;
110     private ManagedItemChannelLinkProvider managedItemChannelLinkProvider;
111     private VolatileStorageService volatileStorageService = new VolatileStorageService();
112
113     protected Bridge bridge;
114     protected NestTestBridgeHandler bridgeHandler;
115     protected Thing thing;
116     protected NestBaseHandler<?> thingHandler;
117     private Class<? extends NestBaseHandler<?>> thingClass;
118
119     private NestTestHandlerFactory nestTestHandlerFactory;
120     private @NonNullByDefault({}) ClientBuilder clientBuilder;
121     private @NonNullByDefault({}) SseEventSourceFactory eventSourceFactory;
122
123     public NestThingHandlerOSGiTest(Class<? extends NestBaseHandler<?>> thingClass) {
124         this.thingClass = thingClass;
125     }
126
127     @BeforeAll
128     public static void setUpClass() throws Exception {
129         ServletHolder holder = new ServletHolder(servlet);
130         server = new NestTestServer(SERVER_HOST, SERVER_PORT, SERVER_TIMEOUT, holder);
131         server.startServer();
132     }
133
134     @BeforeEach
135     public void setUp() throws ItemNotFoundException {
136         registerService(volatileStorageService);
137
138         managedThingProvider = getService(ThingProvider.class, ManagedThingProvider.class);
139         assertThat("Could not get ManagedThingProvider", managedThingProvider, is(notNullValue()));
140
141         thingTypeRegistry = getService(ThingTypeRegistry.class);
142         assertThat("Could not get ThingTypeRegistry", thingTypeRegistry, is(notNullValue()));
143
144         channelTypeRegistry = getService(ChannelTypeRegistry.class);
145         assertThat("Could not get ChannelTypeRegistry", channelTypeRegistry, is(notNullValue()));
146
147         channelGroupTypeRegistry = getService(ChannelGroupTypeRegistry.class);
148         assertThat("Could not get ChannelGroupTypeRegistry", channelGroupTypeRegistry, is(notNullValue()));
149
150         eventPublisher = getService(EventPublisher.class);
151         assertThat("Could not get EventPublisher", eventPublisher, is(notNullValue()));
152
153         itemFactory = getService(ItemFactory.class);
154         assertThat("Could not get ItemFactory", itemFactory, is(notNullValue()));
155
156         itemRegistry = getService(ItemRegistry.class);
157         assertThat("Could not get ItemRegistry", itemRegistry, is(notNullValue()));
158
159         managedItemChannelLinkProvider = getService(ManagedItemChannelLinkProvider.class);
160         assertThat("Could not get ManagedItemChannelLinkProvider", managedItemChannelLinkProvider, is(notNullValue()));
161
162         clientBuilder = getService(ClientBuilder.class);
163         assertThat("Could not get ClientBuilder", clientBuilder, is(notNullValue()));
164
165         eventSourceFactory = getService(SseEventSourceFactory.class);
166         assertThat("Could not get SseEventSourceFactory", eventSourceFactory, is(notNullValue()));
167
168         ComponentContext componentContext = mock(ComponentContext.class);
169         when(componentContext.getBundleContext()).thenReturn(bundleContext);
170
171         nestTestHandlerFactory = new NestTestHandlerFactory(clientBuilder, eventSourceFactory);
172         nestTestHandlerFactory.activate(componentContext,
173                 Map.of(NestTestHandlerFactory.REDIRECT_URL_CONFIG_PROPERTY, REDIRECT_URL));
174         registerService(nestTestHandlerFactory);
175
176         nestTestHandlerFactory = getService(ThingHandlerFactory.class, NestTestHandlerFactory.class);
177         assertThat("Could not get NestTestHandlerFactory", nestTestHandlerFactory, is(notNullValue()));
178
179         bridge = buildBridge();
180         thing = buildThing(bridge);
181
182         bridgeHandler = addThing(bridge, NestTestBridgeHandler.class);
183         thingHandler = addThing(thing, thingClass);
184
185         createAndLinkItems();
186         assertThatAllItemStatesAreNull();
187     }
188
189     @AfterEach
190     public void tearDown() {
191         servlet.reset();
192         servlet.closeConnections();
193
194         if (thing != null) {
195             managedThingProvider.remove(thing.getUID());
196         }
197         if (bridge != null) {
198             managedThingProvider.remove(bridge.getUID());
199         }
200
201         unregisterService(volatileStorageService);
202     }
203
204     protected Bridge buildBridge() {
205         Map<String, Object> properties = new HashMap<>();
206         properties.put(NestBridgeConfiguration.ACCESS_TOKEN,
207                 "c.eQ5QBBPiFOTNzPHbmZPcE9yPZ7GayzLusifgQR2DQRFNyUS9ESvlhJF0D7vG8Y0TFV39zX1vIOsWrv8RKCMrFepNUb9FqHEboa4MtWLUsGb4tD9oBh0jrV4HooJUmz5sVA5KZR0dkxyLYyPc");
208         properties.put(NestBridgeConfiguration.PINCODE, "64P2XRYT");
209         properties.put(NestBridgeConfiguration.PRODUCT_ID, "8fdf9885-ca07-4252-1aa3-f3d5ca9589e0");
210         properties.put(NestBridgeConfiguration.PRODUCT_SECRET, "QITLR3iyUlWaj9dbvCxsCKp4f");
211
212         return BridgeBuilder.create(NestTestBridgeHandler.THING_TYPE_TEST_BRIDGE, "test_account")
213                 .withLabel("Test Account").withConfiguration(new Configuration(properties)).build();
214     }
215
216     protected abstract Thing buildThing(Bridge bridge);
217
218     protected List<Channel> buildChannels(ThingTypeUID thingTypeUID, ThingUID thingUID) {
219         waitForAssert(() -> assertThat(thingTypeRegistry.getThingType(thingTypeUID), notNullValue()));
220
221         ThingType thingType = thingTypeRegistry.getThingType(thingTypeUID);
222
223         List<Channel> channels = new ArrayList<>();
224         channels.addAll(buildChannels(thingUID, thingType.getChannelDefinitions(), (id) -> id));
225
226         for (ChannelGroupDefinition channelGroupDefinition : thingType.getChannelGroupDefinitions()) {
227             ChannelGroupType channelGroupType = channelGroupTypeRegistry
228                     .getChannelGroupType(channelGroupDefinition.getTypeUID());
229             String groupId = channelGroupDefinition.getId();
230             if (channelGroupType != null) {
231                 channels.addAll(
232                         buildChannels(thingUID, channelGroupType.getChannelDefinitions(), (id) -> groupId + "#" + id));
233             }
234         }
235
236         channels.sort((Channel c1, Channel c2) -> c1.getUID().getId().compareTo(c2.getUID().getId()));
237         return channels;
238     }
239
240     protected List<Channel> buildChannels(ThingUID thingUID, List<ChannelDefinition> channelDefinitions,
241             Function<String, String> channelIdFunction) {
242         List<Channel> result = new ArrayList<>();
243         for (ChannelDefinition channelDefinition : channelDefinitions) {
244             ChannelType channelType = channelTypeRegistry.getChannelType(channelDefinition.getChannelTypeUID());
245             if (channelType != null) {
246                 result.add(ChannelBuilder
247                         .create(new ChannelUID(thingUID, channelIdFunction.apply(channelDefinition.getId())),
248                                 channelType.getItemType())
249                         .build());
250             }
251         }
252         return result;
253     }
254
255     @SuppressWarnings("unchecked")
256     protected <T> T addThing(Thing thing, Class<T> thingHandlerClass) {
257         assertThat(thing.getHandler(), is(nullValue()));
258         managedThingProvider.add(thing);
259         waitForAssert(() -> assertThat(thing.getHandler(), notNullValue()));
260         assertThat(thing.getConfiguration(), is(notNullValue()));
261         assertThat(thing.getHandler(), is(instanceOf(thingHandlerClass)));
262         return (T) thing.getHandler();
263     }
264
265     protected String getThingId() {
266         return thing.getUID().getId();
267     }
268
269     protected ThingUID getThingUID() {
270         return thing.getUID();
271     }
272
273     protected void putStreamingEventData(String json) throws IOException {
274         String singleLineJson = json.replaceAll("\n\r\\s+", "").replaceAll("\n\\s+", "").replaceAll("\n\r", "")
275                 .replaceAll("\n", "");
276         servlet.queueEvent(PUT, singleLineJson);
277     }
278
279     protected void createAndLinkItems() {
280         thing.getChannels().forEach(c -> {
281             String itemName = getItemName(c.getUID().getId());
282             Item item = itemFactory.createItem(c.getAcceptedItemType(), itemName);
283             if (item != null) {
284                 itemRegistry.add(item);
285             }
286             managedItemChannelLinkProvider.add(new ItemChannelLink(itemName, c.getUID()));
287         });
288     }
289
290     protected void assertThatItemHasState(String channelId, State state) {
291         waitForAssert(() -> assertThat("Wrong state for item of channel '" + channelId + "' ", getItemState(channelId),
292                 is(state)));
293     }
294
295     protected void assertThatItemHasNotState(String channelId, State state) {
296         waitForAssert(() -> assertThat("Wrong state for item of channel '" + channelId + "' ", getItemState(channelId),
297                 is(not(state))));
298     }
299
300     protected void assertThatAllItemStatesAreNull() {
301         thing.getChannels().forEach(c -> assertThatItemHasState(c.getUID().getId(), UnDefType.NULL));
302     }
303
304     protected void assertThatAllItemStatesAreNotNull() {
305         thing.getChannels().forEach(c -> assertThatItemHasNotState(c.getUID().getId(), UnDefType.NULL));
306     }
307
308     protected ChannelUID getChannelUID(String channelId) {
309         return new ChannelUID(getThingUID(), channelId);
310     }
311
312     protected String getItemName(String channelId) {
313         return getThingId() + "_" + channelId.replaceAll("#", "_");
314     }
315
316     private State getItemState(String channelId) {
317         String itemName = getItemName(channelId);
318         try {
319             return itemRegistry.getItem(itemName).getState();
320         } catch (ItemNotFoundException e) {
321             throw new AssertionError("Item with name '" + itemName + "' not found");
322         }
323     }
324
325     protected void logItemStates() {
326         thing.getChannels().forEach(c -> {
327             String channelId = c.getUID().getId();
328             String itemName = getItemName(channelId);
329             logger.debug("{} = {}", itemName, getItemState(channelId));
330         });
331     }
332
333     protected void updateAllItemStatesToNull() {
334         thing.getChannels().forEach(c -> updateItemState(c.getUID().getId(), UnDefType.NULL));
335     }
336
337     protected void refreshAllChannels() {
338         thing.getChannels().forEach(c -> thingHandler.handleCommand(c.getUID(), RefreshType.REFRESH));
339     }
340
341     protected void handleCommand(String channelId, Command command) {
342         thingHandler.handleCommand(getChannelUID(channelId), command);
343     }
344
345     protected void updateItemState(String channelId, State state) {
346         String itemName = getItemName(channelId);
347         eventPublisher.post(ItemEventFactory.createStateEvent(itemName, state));
348     }
349
350     protected void assertNestApiPropertyState(String nestId, String propertyName, String state) {
351         waitForAssert(() -> assertThat(servlet.getNestIdPropertyState(nestId, propertyName), is(state)));
352     }
353
354     public static DateTimeType parseDateTimeType(String text) {
355         try {
356             return new DateTimeType(Instant.parse(text).atZone(TimeZone.getDefault().toZoneId()));
357         } catch (DateTimeParseException e) {
358             throw new IllegalArgumentException("Invalid date time argument: " + text, e);
359         }
360     }
361 }