2 * Copyright (c) 2010-2023 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.deconz.internal.handler;
15 import static org.hamcrest.MatcherAssert.assertThat;
16 import static org.hamcrest.Matchers.is;
17 import static org.hamcrest.Matchers.notNullValue;
18 import static org.mockito.ArgumentMatchers.any;
19 import static org.mockito.ArgumentMatchers.eq;
20 import static org.mockito.Mockito.atLeast;
21 import static org.mockito.Mockito.doAnswer;
22 import static org.mockito.Mockito.times;
23 import static org.mockito.Mockito.verify;
24 import static org.mockito.Mockito.when;
25 import static org.openhab.binding.deconz.internal.BindingConstants.BINDING_ID;
26 import static org.openhab.binding.deconz.internal.BindingConstants.BRIDGE_TYPE;
27 import static org.openhab.binding.deconz.internal.BindingConstants.CONFIG_ID;
28 import static org.openhab.binding.deconz.internal.BindingConstants.THING_TYPE_THERMOSTAT;
30 import java.io.IOException;
31 import java.util.List;
33 import java.util.Optional;
35 import java.util.concurrent.CompletableFuture;
36 import java.util.function.BiFunction;
37 import java.util.stream.Collectors;
39 import org.eclipse.jdt.annotation.NonNullByDefault;
40 import org.eclipse.jdt.annotation.Nullable;
41 import org.junit.jupiter.api.BeforeEach;
42 import org.mockito.ArgumentCaptor;
43 import org.mockito.Mock;
44 import org.openhab.binding.deconz.DeconzTest;
45 import org.openhab.binding.deconz.internal.dto.BridgeFullState;
46 import org.openhab.binding.deconz.internal.dto.DeconzBaseMessage;
47 import org.openhab.binding.deconz.internal.dto.SensorMessage;
48 import org.openhab.binding.deconz.internal.netutils.WebSocketConnection;
49 import org.openhab.binding.deconz.internal.types.LightType;
50 import org.openhab.binding.deconz.internal.types.LightTypeDeserializer;
51 import org.openhab.binding.deconz.internal.types.ResourceType;
52 import org.openhab.binding.deconz.internal.types.ThermostatMode;
53 import org.openhab.binding.deconz.internal.types.ThermostatModeGsonTypeAdapter;
54 import org.openhab.core.config.core.Configuration;
55 import org.openhab.core.test.java.JavaTest;
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.Thing;
60 import org.openhab.core.thing.ThingStatus;
61 import org.openhab.core.thing.ThingStatusDetail;
62 import org.openhab.core.thing.ThingStatusInfo;
63 import org.openhab.core.thing.ThingTypeUID;
64 import org.openhab.core.thing.ThingUID;
65 import org.openhab.core.thing.binding.ThingHandlerCallback;
66 import org.openhab.core.thing.binding.builder.ChannelBuilder;
67 import org.openhab.core.thing.binding.builder.ThingBuilder;
68 import org.openhab.core.thing.type.ChannelTypeUID;
69 import org.openhab.core.types.State;
71 import com.google.gson.Gson;
72 import com.google.gson.GsonBuilder;
75 * The {@link BaseDeconzThingHandlerTest} is the base class for test classes that are used to test subclasses of
76 * {@link DeconzBaseThingHandler}
78 * @author Jan N. Klug - Initial contribution
81 public class BaseDeconzThingHandlerTest extends JavaTest {
82 private static final ThingUID BRIDGE_UID = new ThingUID(BRIDGE_TYPE, "bridge");
83 private static final ThingUID THING_UID = new ThingUID(THING_TYPE_THERMOSTAT, "thing");
84 protected @NonNullByDefault({}) DeconzBaseMessage deconzMessage;
85 private @Mock @NonNullByDefault({}) Bridge bridge;
86 private @Mock @NonNullByDefault({}) ThingHandlerCallback callback;
87 private @Mock @NonNullByDefault({}) DeconzBridgeHandler bridgeHandler;
88 private @Mock @NonNullByDefault({}) WebSocketConnection webSocketConnection;
89 private @Mock @NonNullByDefault({}) BridgeFullState bridgeFullState;
90 private @NonNullByDefault({}) Gson gson;
91 private @NonNullByDefault({}) Thing thing;
92 private @NonNullByDefault({}) DeconzBaseThingHandler thingHandler;
95 public void setupMocks() {
96 GsonBuilder gsonBuilder = new GsonBuilder();
97 gsonBuilder.registerTypeAdapter(LightType.class, new LightTypeDeserializer());
98 gsonBuilder.registerTypeAdapter(ThermostatMode.class, new ThermostatModeGsonTypeAdapter());
99 gson = gsonBuilder.create();
101 when(callback.getBridge(BRIDGE_UID)).thenReturn(bridge);
102 when(callback.createChannelBuilder(any(ChannelUID.class), any(ChannelTypeUID.class)))
103 .thenAnswer(i -> ChannelBuilder.create((ChannelUID) i.getArgument(0)).withType(i.getArgument(1)));
105 thing = i.getArgument(0);
106 thingHandler.thingUpdated(thing);
108 }).when(callback).thingUpdated(any(Thing.class));
110 when(bridge.getStatusInfo()).thenReturn(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""));
111 when(bridge.getHandler()).thenReturn(bridgeHandler);
113 when(bridgeHandler.getWebSocketConnection()).thenReturn(webSocketConnection);
114 when(bridgeHandler.getBridgeFullState())
115 .thenReturn(CompletableFuture.completedFuture(Optional.of(bridgeFullState)));
117 when(bridgeFullState.getMessage(ResourceType.SENSORS, "1")).thenAnswer(i -> deconzMessage);
120 protected void createThing(ThingTypeUID thingTypeUID, List<String> channels,
121 BiFunction<Thing, Gson, DeconzBaseThingHandler> handlerSupplier) {
122 ThingBuilder thingBuilder = ThingBuilder.create(thingTypeUID, THING_UID);
123 thingBuilder.withBridge(BRIDGE_UID);
124 for (String channelId : channels) {
125 Channel channel = ChannelBuilder.create(new ChannelUID(THING_UID, channelId))
126 .withType(new ChannelTypeUID(BINDING_ID, channelId)).build();
127 thingBuilder.withChannel(channel);
129 thingBuilder.withConfiguration(new Configuration(Map.of(CONFIG_ID, "1")));
130 thing = thingBuilder.build();
132 thingHandler = handlerSupplier.apply(thing, gson);
133 thingHandler.setCallback(callback);
136 protected void assertThing(String fileName, Set<TestParam> expected) throws IOException {
137 deconzMessage = DeconzTest.getObjectFromJson(fileName, SensorMessage.class, gson);
139 thingHandler.initialize();
141 ArgumentCaptor<ThingStatusInfo> captor = ArgumentCaptor.forClass(ThingStatusInfo.class);
142 verify(callback, atLeast(2).description("assertQuantityOfStatusUpdates")).statusUpdated(eq(thing),
145 List<ThingStatusInfo> statusInfoList = captor.getAllValues();
146 assertThat("assertFirstThingStatus", statusInfoList.get(0).getStatus(), is(ThingStatus.UNKNOWN));
147 assertThat("assertLastThingStatus", statusInfoList.get(statusInfoList.size() - 1).getStatus(),
148 is(ThingStatus.ONLINE));
150 assertThat("assertChannelCount:" + getAllChannels(thing), thing.getChannels().size(), is(expected.size()));
151 for (TestParam testParam : expected) {
152 Channel channel = thing.getChannel(testParam.channelId());
153 assertThat("assertNonNullChannel:" + testParam.channelId, channel, is(notNullValue()));
155 State state = testParam.state;
156 if (channel != null && state != null) {
157 verify(callback, times(3).description(channel + " did not receive an update"))
158 .stateUpdated(eq(channel.getUID()), eq(state));
163 private String getAllChannels(Thing thing) {
164 return thing.getChannels().stream().map(Channel::getUID).map(ChannelUID::getId)
165 .collect(Collectors.joining(","));
168 protected record TestParam(String channelId, @Nullable State state) {