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.io.hueemulation.internal.rest;
15 import static org.hamcrest.CoreMatchers.*;
16 import static org.hamcrest.MatcherAssert.assertThat;
17 import static org.junit.jupiter.api.Assertions.assertEquals;
18 import static org.mockito.ArgumentMatchers.argThat;
19 import static org.mockito.Mockito.verify;
21 import java.io.IOException;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jetty.client.api.ContentResponse;
25 import org.glassfish.jersey.server.ResourceConfig;
26 import org.junit.jupiter.api.AfterEach;
27 import org.junit.jupiter.api.BeforeEach;
28 import org.junit.jupiter.api.Test;
29 import org.openhab.core.events.Event;
30 import org.openhab.core.items.GroupItem;
31 import org.openhab.core.items.ItemRegistry;
32 import org.openhab.core.items.events.ItemCommandEvent;
33 import org.openhab.core.library.items.ColorItem;
34 import org.openhab.core.library.items.SwitchItem;
35 import org.openhab.core.library.types.HSBType;
36 import org.openhab.core.library.types.OnOffType;
37 import org.openhab.io.hueemulation.internal.ConfigStore;
38 import org.openhab.io.hueemulation.internal.DeviceType;
39 import org.openhab.io.hueemulation.internal.dto.HueGroupEntry;
40 import org.openhab.io.hueemulation.internal.dto.HueLightEntry;
41 import org.openhab.io.hueemulation.internal.dto.HueStateColorBulb;
42 import org.openhab.io.hueemulation.internal.dto.HueStatePlug;
43 import org.openhab.io.hueemulation.internal.rest.mocks.DummyItemRegistry;
46 * Tests for {@link LightsAndGroups}.
48 * @author David Graeff - Initial contribution
51 public class LightsAndGroupsTests {
52 protected @NonNullByDefault({}) CommonSetup commonSetup;
53 protected @NonNullByDefault({}) ItemRegistry itemRegistry;
54 protected @NonNullByDefault({}) ConfigStore cs;
56 LightsAndGroups subject = new LightsAndGroups();
59 public void setUp() throws IOException {
60 commonSetup = new CommonSetup(false);
61 itemRegistry = new DummyItemRegistry();
63 this.cs = commonSetup.cs;
66 subject.eventPublisher = commonSetup.eventPublisher;
67 subject.userManagement = commonSetup.userManagement;
68 subject.itemRegistry = itemRegistry;
71 // Add simulated lights
72 cs.ds.lights.put("1", new HueLightEntry(new SwitchItem("switch"), "switch", DeviceType.SwitchType));
73 cs.ds.lights.put("2", new HueLightEntry(new ColorItem("color"), "color", DeviceType.ColorType));
74 cs.ds.lights.put("3", new HueLightEntry(new ColorItem("white"), "white", DeviceType.WhiteTemperatureType));
77 cs.ds.groups.put("10",
78 new HueGroupEntry("name", new GroupItem("white", new SwitchItem("switch")), DeviceType.SwitchType));
80 commonSetup.start(new ResourceConfig().registerInstances(subject));
84 public void tearDown() throws Exception {
85 commonSetup.dispose();
89 public void addSwitchableByCategory() {
90 SwitchItem item = new SwitchItem("switch1");
91 item.setCategory("Light");
92 itemRegistry.add(item);
93 HueLightEntry device = cs.ds.lights.get(cs.mapItemUIDtoHueID(item));
94 assertThat(device.item, is(item));
95 assertThat(device.state, is(instanceOf(HueStatePlug.class)));
99 public void addSwitchableByTag() {
100 SwitchItem item = new SwitchItem("switch1");
101 item.addTag("Switchable");
102 itemRegistry.add(item);
103 HueLightEntry device = cs.ds.lights.get(cs.mapItemUIDtoHueID(item));
104 assertThat(device.item, is(item));
105 assertThat(device.state, is(instanceOf(HueStatePlug.class)));
109 public void ignoreByTag() {
110 SwitchItem item = new SwitchItem("switch1");
111 item.addTags("Switchable", "internal"); // The ignore tag will win
112 itemRegistry.add(item);
113 HueLightEntry device = cs.ds.lights.get(cs.mapItemUIDtoHueID(item));
114 assertThat(device, is(nullValue()));
118 public void addGroupSwitchableByTag() {
119 GroupItem item = new GroupItem("group1", new SwitchItem("switch1"));
120 item.addTag("Switchable");
121 itemRegistry.add(item);
122 HueGroupEntry device = cs.ds.groups.get(cs.mapItemUIDtoHueID(item));
123 assertThat(device.groupItem, is(item));
124 assertThat(device.action, is(instanceOf(HueStatePlug.class)));
128 public void addDeviceAsGroupSwitchableByTag() {
129 GroupItem item = new GroupItem("group1", new SwitchItem("switch1"));
130 item.addTag("Switchable");
131 item.addTag("Huelight");
132 itemRegistry.add(item);
133 HueLightEntry device = cs.ds.lights.get(cs.mapItemUIDtoHueID(item));
134 assertThat(device.item, is(item));
135 assertThat(device.state, is(instanceOf(HueStatePlug.class)));
139 public void addGroupWithoutTypeByTag() {
140 GroupItem item = new GroupItem("group1", null);
141 item.addTag("Switchable");
143 itemRegistry.add(item);
145 HueGroupEntry device = cs.ds.groups.get(cs.mapItemUIDtoHueID(item));
146 assertThat(device.groupItem, is(item));
147 assertThat(device.action, is(instanceOf(HueStatePlug.class)));
148 assertThat(cs.ds.groups.get(cs.mapItemUIDtoHueID(item)).groupItem, is(item));
152 public void removeGroupWithoutTypeAndTag() {
153 String groupName = "group1";
154 GroupItem item = new GroupItem(groupName, null);
155 item.addTag("Switchable");
156 itemRegistry.add(item);
158 String hueID = cs.mapItemUIDtoHueID(item);
159 assertThat(cs.ds.groups.get(hueID), notNullValue());
161 subject.updated(item, new GroupItem(groupName, null));
163 assertThat(cs.ds.groups.get(hueID), nullValue());
167 public void updateSwitchable() {
168 SwitchItem item = new SwitchItem("switch1");
169 item.setLabel("labelOld");
170 item.addTag("Switchable");
171 itemRegistry.add(item);
172 String hueID = cs.mapItemUIDtoHueID(item);
173 HueLightEntry device = cs.ds.lights.get(hueID);
174 assertThat(device.item, is(item));
175 assertThat(device.state, is(instanceOf(HueStatePlug.class)));
176 assertThat(device.name, is("labelOld"));
178 SwitchItem newitem = new SwitchItem("switch1");
179 newitem.setLabel("labelNew");
180 newitem.addTag("Switchable");
181 subject.updated(item, newitem);
182 device = cs.ds.lights.get(hueID);
183 assertThat(device.item, is(newitem));
184 assertThat(device.state, is(instanceOf(HueStatePlug.class)));
185 assertThat(device.name, is("labelNew"));
187 // Update with an item that has no tags anymore -> should be removed
188 SwitchItem newitemWithoutTag = new SwitchItem("switch1");
189 newitemWithoutTag.setLabel("labelNew2");
190 subject.updated(newitem, newitemWithoutTag);
192 device = cs.ds.lights.get(hueID);
193 assertThat(device, nullValue());
197 public void changeSwitchState() throws Exception {
198 assertThat(((HueStatePlug) cs.ds.lights.get("1").state).on, is(false));
200 String body = "{'on':true}";
201 ContentResponse response = commonSetup.sendPut("/testuser/lights/1/state", body);
202 assertEquals(200, response.getStatus());
203 assertThat(response.getContentAsString(), containsString("success"));
204 assertThat(((HueStatePlug) cs.ds.lights.get("1").state).on, is(true));
205 verify(commonSetup.eventPublisher).post(argThat((Event t) -> {
206 assertThat(t.getPayload(), is("{\"type\":\"OnOff\",\"value\":\"ON\"}"));
212 public void changeGroupItemSwitchState() throws Exception {
213 assertThat(((HueStatePlug) cs.ds.groups.get("10").action).on, is(false));
215 String body = "{'on':true}";
216 ContentResponse response = commonSetup.sendPut("/testuser/groups/10/action", body);
217 assertEquals(200, response.getStatus());
218 assertThat(response.getContentAsString(), containsString("success"));
219 assertThat(((HueStatePlug) cs.ds.groups.get("10").action).on, is(true));
220 verify(commonSetup.eventPublisher).post(argThat((Event t) -> {
221 assertThat(t.getPayload(), is("{\"type\":\"OnOff\",\"value\":\"ON\"}"));
227 public void changeOnValue() throws Exception {
228 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(false));
230 String body = "{'on':true}";
231 ContentResponse response = commonSetup.sendPut("/testuser/lights/2/state", body);
232 assertEquals(200, response.getStatus());
233 String entity = response.getContentAsString();
234 assertThat(entity, is("[{\"success\":{\"/lights/2/state/on\":true}}]"));
235 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(true));
239 public void changeOnAndBriValues() throws Exception {
240 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(false));
241 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).bri, is(1));
243 String body = "{'on':true,'bri':200}";
244 ContentResponse response = commonSetup.sendPut("/testuser/lights/2/state", body);
245 assertEquals(200, response.getStatus());
246 assertThat(response.getContentAsString(), containsString("success"));
247 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(true));
248 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).bri, is(200));
252 public void changeHueSatValues() throws Exception {
253 HueLightEntry hueDevice = cs.ds.lights.get("2");
254 hueDevice.item.setState(OnOffType.ON);
255 hueDevice.state.as(HueStateColorBulb.class).on = true;
257 String body = "{'hue':1000,'sat':50}";
258 ContentResponse response = commonSetup.sendPut("/testuser/lights/2/state", body);
259 assertEquals(200, response.getStatus());
260 assertThat(response.getContentAsString(), containsString("success"));
261 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(true));
262 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).hue, is(1000));
263 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).sat, is(50));
265 verify(commonSetup.eventPublisher).post(argThat(ce -> assertHueValue((ItemCommandEvent) ce, 1000)));
269 * Amazon echos are setting ct only, if commanded to turn a light white.
272 public void changeCtValue() throws Exception {
273 HueLightEntry hueDevice = cs.ds.lights.get("2");
274 hueDevice.item.setState(OnOffType.ON);
275 hueDevice.state.as(HueStateColorBulb.class).on = true;
277 String body = "{'ct':500}";
278 ContentResponse response = commonSetup.sendPut("/testuser/lights/2/state", body);
279 assertEquals(200, response.getStatus());
280 body = response.getContentAsString();
281 assertThat(body, containsString("success"));
282 assertThat(body, containsString("ct"));
283 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(true));
284 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).ct, is(500));
285 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).sat, is(0));
287 // Saturation is expected to be 0 -> white light
288 verify(commonSetup.eventPublisher).post(argThat(ce -> assertSatValue((ItemCommandEvent) ce, 0)));
292 public void switchOnWithXY() throws Exception {
293 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(false));
294 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).bri, is(1));
296 String body = "{'on':true,'bri':200,'xy':[0.5119,0.4147]}";
297 ContentResponse response = commonSetup.sendPut("/testuser/lights/2/state", body);
298 assertEquals(200, response.getStatus());
299 assertThat(response.getContentAsString(), containsString("success"));
300 assertThat(response.getContentAsString(), containsString("xy"));
301 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(true));
302 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).bri, is(200));
303 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).xy[0], is(0.5119));
304 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).xy[1], is(0.4147));
305 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).colormode, is(HueStateColorBulb.ColorMode.xy));
306 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).toHSBType().getHue().intValue(),
307 is((int) 27.47722590981918));
308 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).toHSBType().getSaturation().intValue(), is(88));
309 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).toHSBType().getBrightness().intValue(), is(78));
313 public void allLightsAndSingleLight() throws Exception {
314 ContentResponse response = commonSetup.sendGet("/testuser/lights");
315 assertEquals(200, response.getStatus());
317 String body = response.getContentAsString();
319 assertThat(body, containsString("switch"));
320 assertThat(body, containsString("color"));
321 assertThat(body, containsString("white"));
323 // Single light access test
324 response = commonSetup.sendGet("/testuser/lights/2");
325 assertEquals(200, response.getStatus());
326 body = response.getContentAsString();
327 assertThat(body, containsString("color"));
330 private boolean assertHueValue(ItemCommandEvent ce, int hueValue) {
331 assertThat(((HSBType) ce.getItemCommand()).getHue().intValue(), is(hueValue * 360 / HueStateColorBulb.MAX_HUE));
335 private boolean assertSatValue(ItemCommandEvent ce, int satValue) {
336 assertThat(((HSBType) ce.getItemCommand()).getSaturation().intValue(),
337 is(satValue * 100 / HueStateColorBulb.MAX_SAT));