2 * Copyright (c) 2010-2020 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.junit.Assert.*;
17 import static org.mockito.ArgumentMatchers.argThat;
18 import static org.mockito.Mockito.verify;
20 import java.io.IOException;
22 import javax.ws.rs.client.Entity;
23 import javax.ws.rs.core.Response;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.openhab.core.events.Event;
27 import org.openhab.core.items.GroupItem;
28 import org.openhab.core.items.ItemRegistry;
29 import org.openhab.core.items.events.ItemCommandEvent;
30 import org.openhab.core.library.items.ColorItem;
31 import org.openhab.core.library.items.SwitchItem;
32 import org.openhab.core.library.types.HSBType;
33 import org.openhab.core.library.types.OnOffType;
34 import org.glassfish.jersey.server.ResourceConfig;
35 import org.junit.After;
36 import org.junit.Before;
37 import org.junit.Test;
38 import org.openhab.io.hueemulation.internal.ConfigStore;
39 import org.openhab.io.hueemulation.internal.DeviceType;
40 import org.openhab.io.hueemulation.internal.dto.HueGroupEntry;
41 import org.openhab.io.hueemulation.internal.dto.HueLightEntry;
42 import org.openhab.io.hueemulation.internal.dto.HueStateColorBulb;
43 import org.openhab.io.hueemulation.internal.dto.HueStatePlug;
44 import org.openhab.io.hueemulation.internal.rest.mocks.DummyItemRegistry;
47 * Tests for {@link LightsAndGroups}.
49 * @author David Graeff - Initial contribution
52 public class LightsAndGroupsTests {
53 protected @NonNullByDefault({}) CommonSetup commonSetup;
54 protected @NonNullByDefault({}) ItemRegistry itemRegistry;
55 protected @NonNullByDefault({}) ConfigStore cs;
57 LightsAndGroups subject = new LightsAndGroups();
60 public void setUp() throws IOException {
61 commonSetup = new CommonSetup(false);
62 itemRegistry = new DummyItemRegistry();
64 this.cs = commonSetup.cs;
67 subject.eventPublisher = commonSetup.eventPublisher;
68 subject.userManagement = commonSetup.userManagement;
69 subject.itemRegistry = itemRegistry;
72 // Add simulated lights
73 cs.ds.lights.put("1", new HueLightEntry(new SwitchItem("switch"), "switch", DeviceType.SwitchType));
74 cs.ds.lights.put("2", new HueLightEntry(new ColorItem("color"), "color", DeviceType.ColorType));
75 cs.ds.lights.put("3", new HueLightEntry(new ColorItem("white"), "white", DeviceType.WhiteTemperatureType));
78 cs.ds.groups.put("10",
79 new HueGroupEntry("name", new GroupItem("white", new SwitchItem("switch")), DeviceType.SwitchType));
81 commonSetup.start(new ResourceConfig().registerInstances(subject));
85 public void tearDown() {
86 commonSetup.dispose();
90 public void addSwitchableByCategory() {
91 SwitchItem item = new SwitchItem("switch1");
92 item.setCategory("Light");
93 itemRegistry.add(item);
94 HueLightEntry device = cs.ds.lights.get(cs.mapItemUIDtoHueID(item));
95 assertThat(device.item, is(item));
96 assertThat(device.state, is(instanceOf(HueStatePlug.class)));
100 public void addSwitchableByTag() {
101 SwitchItem item = new SwitchItem("switch1");
102 item.addTag("Switchable");
103 itemRegistry.add(item);
104 HueLightEntry device = cs.ds.lights.get(cs.mapItemUIDtoHueID(item));
105 assertThat(device.item, is(item));
106 assertThat(device.state, is(instanceOf(HueStatePlug.class)));
110 public void ignoreByTag() {
111 SwitchItem item = new SwitchItem("switch1");
112 item.addTags("Switchable", "internal"); // The ignore tag will win
113 itemRegistry.add(item);
114 HueLightEntry device = cs.ds.lights.get(cs.mapItemUIDtoHueID(item));
115 assertThat(device, is(nullValue()));
119 public void addGroupSwitchableByTag() {
120 GroupItem item = new GroupItem("group1", new SwitchItem("switch1"));
121 item.addTag("Switchable");
122 itemRegistry.add(item);
123 HueGroupEntry device = cs.ds.groups.get(cs.mapItemUIDtoHueID(item));
124 assertThat(device.groupItem, is(item));
125 assertThat(device.action, is(instanceOf(HueStatePlug.class)));
129 public void addDeviceAsGroupSwitchableByTag() {
130 GroupItem item = new GroupItem("group1", new SwitchItem("switch1"));
131 item.addTag("Switchable");
132 item.addTag("Huelight");
133 itemRegistry.add(item);
134 HueLightEntry device = cs.ds.lights.get(cs.mapItemUIDtoHueID(item));
135 assertThat(device.item, is(item));
136 assertThat(device.state, is(instanceOf(HueStatePlug.class)));
140 public void addGroupWithoutTypeByTag() {
141 GroupItem item = new GroupItem("group1", null);
142 item.addTag("Switchable");
144 itemRegistry.add(item);
146 HueGroupEntry device = cs.ds.groups.get(cs.mapItemUIDtoHueID(item));
147 assertThat(device.groupItem, is(item));
148 assertThat(device.action, is(instanceOf(HueStatePlug.class)));
149 assertThat(cs.ds.groups.get(cs.mapItemUIDtoHueID(item)).groupItem, is(item));
153 public void removeGroupWithoutTypeAndTag() {
154 String groupName = "group1";
155 GroupItem item = new GroupItem(groupName, null);
156 item.addTag("Switchable");
157 itemRegistry.add(item);
159 String hueID = cs.mapItemUIDtoHueID(item);
160 assertThat(cs.ds.groups.get(hueID), notNullValue());
162 subject.updated(item, new GroupItem(groupName, null));
164 assertThat(cs.ds.groups.get(hueID), nullValue());
168 public void updateSwitchable() {
169 SwitchItem item = new SwitchItem("switch1");
170 item.setLabel("labelOld");
171 item.addTag("Switchable");
172 itemRegistry.add(item);
173 String hueID = cs.mapItemUIDtoHueID(item);
174 HueLightEntry device = cs.ds.lights.get(hueID);
175 assertThat(device.item, is(item));
176 assertThat(device.state, is(instanceOf(HueStatePlug.class)));
177 assertThat(device.name, is("labelOld"));
179 SwitchItem newitem = new SwitchItem("switch1");
180 newitem.setLabel("labelNew");
181 newitem.addTag("Switchable");
182 subject.updated(item, newitem);
183 device = cs.ds.lights.get(hueID);
184 assertThat(device.item, is(newitem));
185 assertThat(device.state, is(instanceOf(HueStatePlug.class)));
186 assertThat(device.name, is("labelNew"));
188 // Update with an item that has no tags anymore -> should be removed
189 SwitchItem newitemWithoutTag = new SwitchItem("switch1");
190 newitemWithoutTag.setLabel("labelNew2");
191 subject.updated(newitem, newitemWithoutTag);
193 device = cs.ds.lights.get(hueID);
194 assertThat(device, nullValue());
198 public void changeSwitchState() {
199 assertThat(((HueStatePlug) cs.ds.lights.get("1").state).on, is(false));
201 String body = "{'on':true}";
202 Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/lights/1/state").request()
203 .put(Entity.json(body));
204 assertEquals(200, response.getStatus());
205 assertThat(response.readEntity(String.class), containsString("success"));
206 assertThat(((HueStatePlug) cs.ds.lights.get("1").state).on, is(true));
207 verify(commonSetup.eventPublisher).post(argThat((Event t) -> {
208 assertThat(t.getPayload(), is("{\"type\":\"OnOff\",\"value\":\"ON\"}"));
214 public void changeGroupItemSwitchState() {
215 assertThat(((HueStatePlug) cs.ds.groups.get("10").action).on, is(false));
217 String body = "{'on':true}";
218 Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/groups/10/action").request()
219 .put(Entity.json(body));
220 assertEquals(200, response.getStatus());
221 assertThat(response.readEntity(String.class), containsString("success"));
222 assertThat(((HueStatePlug) cs.ds.groups.get("10").action).on, is(true));
223 verify(commonSetup.eventPublisher).post(argThat((Event t) -> {
224 assertThat(t.getPayload(), is("{\"type\":\"OnOff\",\"value\":\"ON\"}"));
230 public void changeOnValue() {
231 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(false));
233 String body = "{'on':true}";
234 Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/lights/2/state").request()
235 .put(Entity.json(body));
236 assertEquals(200, response.getStatus());
237 String entity = response.readEntity(String.class);
238 assertThat(entity, is("[{\"success\":{\"/lights/2/state/on\":true}}]"));
239 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(true));
243 public void changeOnAndBriValues() {
244 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(false));
245 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).bri, is(1));
247 String body = "{'on':true,'bri':200}";
248 Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/lights/2/state").request()
249 .put(Entity.json(body));
250 assertEquals(200, response.getStatus());
251 assertThat(response.readEntity(String.class), containsString("success"));
252 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(true));
253 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).bri, is(200));
257 public void changeHueSatValues() {
258 HueLightEntry hueDevice = cs.ds.lights.get("2");
259 hueDevice.item.setState(OnOffType.ON);
260 hueDevice.state.as(HueStateColorBulb.class).on = true;
262 String body = "{'hue':1000,'sat':50}";
263 Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/lights/2/state").request()
264 .put(Entity.json(body));
265 assertEquals(200, response.getStatus());
266 assertThat(response.readEntity(String.class), containsString("success"));
267 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(true));
268 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).hue, is(1000));
269 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).sat, is(50));
271 verify(commonSetup.eventPublisher).post(argThat(ce -> assertHueValue((ItemCommandEvent) ce, 1000)));
275 * Amazon echos are setting ct only, if commanded to turn a light white.
278 public void changeCtValue() {
279 HueLightEntry hueDevice = cs.ds.lights.get("2");
280 hueDevice.item.setState(OnOffType.ON);
281 hueDevice.state.as(HueStateColorBulb.class).on = true;
283 String body = "{'ct':500}";
284 Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/lights/2/state").request()
285 .put(Entity.json(body));
286 assertEquals(200, response.getStatus());
287 body = response.readEntity(String.class);
288 assertThat(body, containsString("success"));
289 assertThat(body, containsString("ct"));
290 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(true));
291 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).ct, is(500));
292 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).sat, is(0));
294 // Saturation is expected to be 0 -> white light
295 verify(commonSetup.eventPublisher).post(argThat(ce -> assertSatValue((ItemCommandEvent) ce, 0)));
299 public void switchOnWithXY() {
300 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(false));
301 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).bri, is(1));
303 String body = "{'on':true,'bri':200,'xy':[0.5119,0.4147]}";
304 Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/lights/2/state").request()
305 .put(Entity.json(body));
306 assertEquals(200, response.getStatus());
307 assertThat(response.readEntity(String.class), containsString("success"));
308 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(true));
309 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).bri, is(200));
310 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).xy[0], is(0.5119));
311 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).xy[1], is(0.4147));
312 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).colormode, is(HueStateColorBulb.ColorMode.xy));
313 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).toHSBType().getHue().intValue(),
314 is((int) 27.47722590981918));
315 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).toHSBType().getSaturation().intValue(), is(88));
316 assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).toHSBType().getBrightness().intValue(), is(78));
320 public void allLightsAndSingleLight() {
321 Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/lights").request().get();
322 assertEquals(200, response.getStatus());
324 String body = response.readEntity(String.class);
326 assertThat(body, containsString("switch"));
327 assertThat(body, containsString("color"));
328 assertThat(body, containsString("white"));
330 // Single light access test
331 response = commonSetup.client.target(commonSetup.basePath + "/testuser/lights/2").request().get();
332 assertEquals(200, response.getStatus());
333 body = response.readEntity(String.class);
334 assertThat(body, containsString("color"));
337 private boolean assertHueValue(ItemCommandEvent ce, int hueValue) {
338 assertThat(((HSBType) ce.getItemCommand()).getHue().intValue(), is(hueValue * 360 / HueStateColorBulb.MAX_HUE));
342 private boolean assertSatValue(ItemCommandEvent ce, int satValue) {
343 assertThat(((HSBType) ce.getItemCommand()).getSaturation().intValue(),
344 is(satValue * 100 / HueStateColorBulb.MAX_SAT));