2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.nest.handler;
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;
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;
29 import java.util.TimeZone;
30 import java.util.function.Function;
32 import javax.ws.rs.client.ClientBuilder;
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;
87 * {@link NestThingHandlerOSGiTest} is an abstract base class for Nest OSGi based tests.
89 * @author Wouter Born - Increase test coverage
91 public abstract class NestThingHandlerOSGiTest extends JavaOSGiTest {
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;
98 private final Logger logger = LoggerFactory.getLogger(NestThingHandlerOSGiTest.class);
100 private static NestTestServer server;
101 private static NestTestApiServlet servlet = new NestTestApiServlet();
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();
113 protected Bridge bridge;
114 protected NestTestBridgeHandler bridgeHandler;
115 protected Thing thing;
116 protected NestBaseHandler<?> thingHandler;
117 private Class<? extends NestBaseHandler<?>> thingClass;
119 private NestTestHandlerFactory nestTestHandlerFactory;
120 private @NonNullByDefault({}) ClientBuilder clientBuilder;
121 private @NonNullByDefault({}) SseEventSourceFactory eventSourceFactory;
123 public NestThingHandlerOSGiTest(Class<? extends NestBaseHandler<?>> thingClass) {
124 this.thingClass = thingClass;
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();
135 public void setUp() throws ItemNotFoundException {
136 registerService(volatileStorageService);
138 managedThingProvider = getService(ThingProvider.class, ManagedThingProvider.class);
139 assertThat("Could not get ManagedThingProvider", managedThingProvider, is(notNullValue()));
141 thingTypeRegistry = getService(ThingTypeRegistry.class);
142 assertThat("Could not get ThingTypeRegistry", thingTypeRegistry, is(notNullValue()));
144 channelTypeRegistry = getService(ChannelTypeRegistry.class);
145 assertThat("Could not get ChannelTypeRegistry", channelTypeRegistry, is(notNullValue()));
147 channelGroupTypeRegistry = getService(ChannelGroupTypeRegistry.class);
148 assertThat("Could not get ChannelGroupTypeRegistry", channelGroupTypeRegistry, is(notNullValue()));
150 eventPublisher = getService(EventPublisher.class);
151 assertThat("Could not get EventPublisher", eventPublisher, is(notNullValue()));
153 itemFactory = getService(ItemFactory.class);
154 assertThat("Could not get ItemFactory", itemFactory, is(notNullValue()));
156 itemRegistry = getService(ItemRegistry.class);
157 assertThat("Could not get ItemRegistry", itemRegistry, is(notNullValue()));
159 managedItemChannelLinkProvider = getService(ManagedItemChannelLinkProvider.class);
160 assertThat("Could not get ManagedItemChannelLinkProvider", managedItemChannelLinkProvider, is(notNullValue()));
162 clientBuilder = getService(ClientBuilder.class);
163 assertThat("Could not get ClientBuilder", clientBuilder, is(notNullValue()));
165 eventSourceFactory = getService(SseEventSourceFactory.class);
166 assertThat("Could not get SseEventSourceFactory", eventSourceFactory, is(notNullValue()));
168 ComponentContext componentContext = mock(ComponentContext.class);
169 when(componentContext.getBundleContext()).thenReturn(bundleContext);
171 nestTestHandlerFactory = new NestTestHandlerFactory(clientBuilder, eventSourceFactory);
172 nestTestHandlerFactory.activate(componentContext,
173 Map.of(NestTestHandlerFactory.REDIRECT_URL_CONFIG_PROPERTY, REDIRECT_URL));
174 registerService(nestTestHandlerFactory);
176 nestTestHandlerFactory = getService(ThingHandlerFactory.class, NestTestHandlerFactory.class);
177 assertThat("Could not get NestTestHandlerFactory", nestTestHandlerFactory, is(notNullValue()));
179 bridge = buildBridge();
180 thing = buildThing(bridge);
182 bridgeHandler = addThing(bridge, NestTestBridgeHandler.class);
183 thingHandler = addThing(thing, thingClass);
185 createAndLinkItems();
186 assertThatAllItemStatesAreNull();
190 public void tearDown() {
192 servlet.closeConnections();
195 managedThingProvider.remove(thing.getUID());
197 if (bridge != null) {
198 managedThingProvider.remove(bridge.getUID());
201 unregisterService(volatileStorageService);
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");
212 return BridgeBuilder.create(NestTestBridgeHandler.THING_TYPE_TEST_BRIDGE, "test_account")
213 .withLabel("Test Account").withConfiguration(new Configuration(properties)).build();
216 protected abstract Thing buildThing(Bridge bridge);
218 protected List<Channel> buildChannels(ThingTypeUID thingTypeUID, ThingUID thingUID) {
219 waitForAssert(() -> assertThat(thingTypeRegistry.getThingType(thingTypeUID), notNullValue()));
221 ThingType thingType = thingTypeRegistry.getThingType(thingTypeUID);
223 List<Channel> channels = new ArrayList<>();
224 channels.addAll(buildChannels(thingUID, thingType.getChannelDefinitions(), (id) -> id));
226 for (ChannelGroupDefinition channelGroupDefinition : thingType.getChannelGroupDefinitions()) {
227 ChannelGroupType channelGroupType = channelGroupTypeRegistry
228 .getChannelGroupType(channelGroupDefinition.getTypeUID());
229 String groupId = channelGroupDefinition.getId();
230 if (channelGroupType != null) {
232 buildChannels(thingUID, channelGroupType.getChannelDefinitions(), (id) -> groupId + "#" + id));
236 channels.sort((Channel c1, Channel c2) -> c1.getUID().getId().compareTo(c2.getUID().getId()));
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())
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();
265 protected String getThingId() {
266 return thing.getUID().getId();
269 protected ThingUID getThingUID() {
270 return thing.getUID();
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);
279 protected void createAndLinkItems() {
280 thing.getChannels().forEach(c -> {
281 String itemName = getItemName(c.getUID().getId());
282 Item item = itemFactory.createItem(c.getAcceptedItemType(), itemName);
284 itemRegistry.add(item);
286 managedItemChannelLinkProvider.add(new ItemChannelLink(itemName, c.getUID()));
290 protected void assertThatItemHasState(String channelId, State state) {
291 waitForAssert(() -> assertThat("Wrong state for item of channel '" + channelId + "' ", getItemState(channelId),
295 protected void assertThatItemHasNotState(String channelId, State state) {
296 waitForAssert(() -> assertThat("Wrong state for item of channel '" + channelId + "' ", getItemState(channelId),
300 protected void assertThatAllItemStatesAreNull() {
301 thing.getChannels().forEach(c -> assertThatItemHasState(c.getUID().getId(), UnDefType.NULL));
304 protected void assertThatAllItemStatesAreNotNull() {
305 thing.getChannels().forEach(c -> assertThatItemHasNotState(c.getUID().getId(), UnDefType.NULL));
308 protected ChannelUID getChannelUID(String channelId) {
309 return new ChannelUID(getThingUID(), channelId);
312 protected String getItemName(String channelId) {
313 return getThingId() + "_" + channelId.replaceAll("#", "_");
316 private State getItemState(String channelId) {
317 String itemName = getItemName(channelId);
319 return itemRegistry.getItem(itemName).getState();
320 } catch (ItemNotFoundException e) {
321 throw new AssertionError("Item with name '" + itemName + "' not found");
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));
333 protected void updateAllItemStatesToNull() {
334 thing.getChannels().forEach(c -> updateItemState(c.getUID().getId(), UnDefType.NULL));
337 protected void refreshAllChannels() {
338 thing.getChannels().forEach(c -> thingHandler.handleCommand(c.getUID(), RefreshType.REFRESH));
341 protected void handleCommand(String channelId, Command command) {
342 thingHandler.handleCommand(getChannelUID(channelId), command);
345 protected void updateItemState(String channelId, State state) {
346 String itemName = getItemName(channelId);
347 eventPublisher.post(ItemEventFactory.createStateEvent(itemName, state));
350 protected void assertNestApiPropertyState(String nestId, String propertyName, String state) {
351 waitForAssert(() -> assertThat(servlet.getNestIdPropertyState(nestId, propertyName), is(state)));
354 public static DateTimeType parseDateTimeType(String text) {
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);