]> git.basschouten.com Git - openhab-addons.git/blob
0ec53175ab604979f252af80287df13b5fbb499f
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.io.hueemulation.internal.rest;
14
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;
20
21 import java.io.IOException;
22
23 import javax.ws.rs.client.Entity;
24 import javax.ws.rs.core.Response;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.glassfish.jersey.server.ResourceConfig;
28 import org.junit.jupiter.api.AfterEach;
29 import org.junit.jupiter.api.BeforeEach;
30 import org.junit.jupiter.api.Test;
31 import org.openhab.core.events.Event;
32 import org.openhab.core.items.GroupItem;
33 import org.openhab.core.items.ItemRegistry;
34 import org.openhab.core.items.events.ItemCommandEvent;
35 import org.openhab.core.library.items.ColorItem;
36 import org.openhab.core.library.items.SwitchItem;
37 import org.openhab.core.library.types.HSBType;
38 import org.openhab.core.library.types.OnOffType;
39 import org.openhab.io.hueemulation.internal.ConfigStore;
40 import org.openhab.io.hueemulation.internal.DeviceType;
41 import org.openhab.io.hueemulation.internal.dto.HueGroupEntry;
42 import org.openhab.io.hueemulation.internal.dto.HueLightEntry;
43 import org.openhab.io.hueemulation.internal.dto.HueStateColorBulb;
44 import org.openhab.io.hueemulation.internal.dto.HueStatePlug;
45 import org.openhab.io.hueemulation.internal.rest.mocks.DummyItemRegistry;
46
47 /**
48  * Tests for {@link LightsAndGroups}.
49  *
50  * @author David Graeff - Initial contribution
51  */
52 @NonNullByDefault
53 public class LightsAndGroupsTests {
54     protected @NonNullByDefault({}) CommonSetup commonSetup;
55     protected @NonNullByDefault({}) ItemRegistry itemRegistry;
56     protected @NonNullByDefault({}) ConfigStore cs;
57
58     LightsAndGroups subject = new LightsAndGroups();
59
60     @BeforeEach
61     public void setUp() throws IOException {
62         commonSetup = new CommonSetup(false);
63         itemRegistry = new DummyItemRegistry();
64
65         this.cs = commonSetup.cs;
66
67         subject.cs = cs;
68         subject.eventPublisher = commonSetup.eventPublisher;
69         subject.userManagement = commonSetup.userManagement;
70         subject.itemRegistry = itemRegistry;
71         subject.activate();
72
73         // Add simulated lights
74         cs.ds.lights.put("1", new HueLightEntry(new SwitchItem("switch"), "switch", DeviceType.SwitchType));
75         cs.ds.lights.put("2", new HueLightEntry(new ColorItem("color"), "color", DeviceType.ColorType));
76         cs.ds.lights.put("3", new HueLightEntry(new ColorItem("white"), "white", DeviceType.WhiteTemperatureType));
77
78         // Add group item
79         cs.ds.groups.put("10",
80                 new HueGroupEntry("name", new GroupItem("white", new SwitchItem("switch")), DeviceType.SwitchType));
81
82         commonSetup.start(new ResourceConfig().registerInstances(subject));
83     }
84
85     @AfterEach
86     public void tearDown() throws Exception {
87         commonSetup.dispose();
88     }
89
90     @Test
91     public void addSwitchableByCategory() {
92         SwitchItem item = new SwitchItem("switch1");
93         item.setCategory("Light");
94         itemRegistry.add(item);
95         HueLightEntry device = cs.ds.lights.get(cs.mapItemUIDtoHueID(item));
96         assertThat(device.item, is(item));
97         assertThat(device.state, is(instanceOf(HueStatePlug.class)));
98     }
99
100     @Test
101     public void addSwitchableByTag() {
102         SwitchItem item = new SwitchItem("switch1");
103         item.addTag("Switchable");
104         itemRegistry.add(item);
105         HueLightEntry device = cs.ds.lights.get(cs.mapItemUIDtoHueID(item));
106         assertThat(device.item, is(item));
107         assertThat(device.state, is(instanceOf(HueStatePlug.class)));
108     }
109
110     @Test
111     public void ignoreByTag() {
112         SwitchItem item = new SwitchItem("switch1");
113         item.addTags("Switchable", "internal"); // The ignore tag will win
114         itemRegistry.add(item);
115         HueLightEntry device = cs.ds.lights.get(cs.mapItemUIDtoHueID(item));
116         assertThat(device, is(nullValue()));
117     }
118
119     @Test
120     public void addGroupSwitchableByTag() {
121         GroupItem item = new GroupItem("group1", new SwitchItem("switch1"));
122         item.addTag("Switchable");
123         itemRegistry.add(item);
124         HueGroupEntry device = cs.ds.groups.get(cs.mapItemUIDtoHueID(item));
125         assertThat(device.groupItem, is(item));
126         assertThat(device.action, is(instanceOf(HueStatePlug.class)));
127     }
128
129     @Test
130     public void addDeviceAsGroupSwitchableByTag() {
131         GroupItem item = new GroupItem("group1", new SwitchItem("switch1"));
132         item.addTag("Switchable");
133         item.addTag("Huelight");
134         itemRegistry.add(item);
135         HueLightEntry device = cs.ds.lights.get(cs.mapItemUIDtoHueID(item));
136         assertThat(device.item, is(item));
137         assertThat(device.state, is(instanceOf(HueStatePlug.class)));
138     }
139
140     @Test
141     public void addGroupWithoutTypeByTag() {
142         GroupItem item = new GroupItem("group1", null);
143         item.addTag("Switchable");
144
145         itemRegistry.add(item);
146
147         HueGroupEntry device = cs.ds.groups.get(cs.mapItemUIDtoHueID(item));
148         assertThat(device.groupItem, is(item));
149         assertThat(device.action, is(instanceOf(HueStatePlug.class)));
150         assertThat(cs.ds.groups.get(cs.mapItemUIDtoHueID(item)).groupItem, is(item));
151     }
152
153     @Test
154     public void removeGroupWithoutTypeAndTag() {
155         String groupName = "group1";
156         GroupItem item = new GroupItem(groupName, null);
157         item.addTag("Switchable");
158         itemRegistry.add(item);
159
160         String hueID = cs.mapItemUIDtoHueID(item);
161         assertThat(cs.ds.groups.get(hueID), notNullValue());
162
163         subject.updated(item, new GroupItem(groupName, null));
164
165         assertThat(cs.ds.groups.get(hueID), nullValue());
166     }
167
168     @Test
169     public void updateSwitchable() {
170         SwitchItem item = new SwitchItem("switch1");
171         item.setLabel("labelOld");
172         item.addTag("Switchable");
173         itemRegistry.add(item);
174         String hueID = cs.mapItemUIDtoHueID(item);
175         HueLightEntry device = cs.ds.lights.get(hueID);
176         assertThat(device.item, is(item));
177         assertThat(device.state, is(instanceOf(HueStatePlug.class)));
178         assertThat(device.name, is("labelOld"));
179
180         SwitchItem newitem = new SwitchItem("switch1");
181         newitem.setLabel("labelNew");
182         newitem.addTag("Switchable");
183         subject.updated(item, newitem);
184         device = cs.ds.lights.get(hueID);
185         assertThat(device.item, is(newitem));
186         assertThat(device.state, is(instanceOf(HueStatePlug.class)));
187         assertThat(device.name, is("labelNew"));
188
189         // Update with an item that has no tags anymore -> should be removed
190         SwitchItem newitemWithoutTag = new SwitchItem("switch1");
191         newitemWithoutTag.setLabel("labelNew2");
192         subject.updated(newitem, newitemWithoutTag);
193
194         device = cs.ds.lights.get(hueID);
195         assertThat(device, nullValue());
196     }
197
198     @Test
199     public void changeSwitchState() {
200         assertThat(((HueStatePlug) cs.ds.lights.get("1").state).on, is(false));
201
202         String body = "{'on':true}";
203         Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/lights/1/state").request()
204                 .put(Entity.json(body));
205         assertEquals(200, response.getStatus());
206         assertThat(response.readEntity(String.class), containsString("success"));
207         assertThat(((HueStatePlug) cs.ds.lights.get("1").state).on, is(true));
208         verify(commonSetup.eventPublisher).post(argThat((Event t) -> {
209             assertThat(t.getPayload(), is("{\"type\":\"OnOff\",\"value\":\"ON\"}"));
210             return true;
211         }));
212     }
213
214     @Test
215     public void changeGroupItemSwitchState() {
216         assertThat(((HueStatePlug) cs.ds.groups.get("10").action).on, is(false));
217
218         String body = "{'on':true}";
219         Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/groups/10/action").request()
220                 .put(Entity.json(body));
221         assertEquals(200, response.getStatus());
222         assertThat(response.readEntity(String.class), containsString("success"));
223         assertThat(((HueStatePlug) cs.ds.groups.get("10").action).on, is(true));
224         verify(commonSetup.eventPublisher).post(argThat((Event t) -> {
225             assertThat(t.getPayload(), is("{\"type\":\"OnOff\",\"value\":\"ON\"}"));
226             return true;
227         }));
228     }
229
230     @Test
231     public void changeOnValue() {
232         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(false));
233
234         String body = "{'on':true}";
235         Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/lights/2/state").request()
236                 .put(Entity.json(body));
237         assertEquals(200, response.getStatus());
238         String entity = response.readEntity(String.class);
239         assertThat(entity, is("[{\"success\":{\"/lights/2/state/on\":true}}]"));
240         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(true));
241     }
242
243     @Test
244     public void changeOnAndBriValues() {
245         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(false));
246         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).bri, is(1));
247
248         String body = "{'on':true,'bri':200}";
249         Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/lights/2/state").request()
250                 .put(Entity.json(body));
251         assertEquals(200, response.getStatus());
252         assertThat(response.readEntity(String.class), containsString("success"));
253         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(true));
254         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).bri, is(200));
255     }
256
257     @Test
258     public void changeHueSatValues() {
259         HueLightEntry hueDevice = cs.ds.lights.get("2");
260         hueDevice.item.setState(OnOffType.ON);
261         hueDevice.state.as(HueStateColorBulb.class).on = true;
262
263         String body = "{'hue':1000,'sat':50}";
264         Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/lights/2/state").request()
265                 .put(Entity.json(body));
266         assertEquals(200, response.getStatus());
267         assertThat(response.readEntity(String.class), containsString("success"));
268         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(true));
269         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).hue, is(1000));
270         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).sat, is(50));
271
272         verify(commonSetup.eventPublisher).post(argThat(ce -> assertHueValue((ItemCommandEvent) ce, 1000)));
273     }
274
275     /**
276      * Amazon echos are setting ct only, if commanded to turn a light white.
277      */
278     @Test
279     public void changeCtValue() {
280         HueLightEntry hueDevice = cs.ds.lights.get("2");
281         hueDevice.item.setState(OnOffType.ON);
282         hueDevice.state.as(HueStateColorBulb.class).on = true;
283
284         String body = "{'ct':500}";
285         Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/lights/2/state").request()
286                 .put(Entity.json(body));
287         assertEquals(200, response.getStatus());
288         body = response.readEntity(String.class);
289         assertThat(body, containsString("success"));
290         assertThat(body, containsString("ct"));
291         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(true));
292         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).ct, is(500));
293         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).sat, is(0));
294
295         // Saturation is expected to be 0 -> white light
296         verify(commonSetup.eventPublisher).post(argThat(ce -> assertSatValue((ItemCommandEvent) ce, 0)));
297     }
298
299     @Test
300     public void switchOnWithXY() {
301         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(false));
302         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).bri, is(1));
303
304         String body = "{'on':true,'bri':200,'xy':[0.5119,0.4147]}";
305         Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/lights/2/state").request()
306                 .put(Entity.json(body));
307         assertEquals(200, response.getStatus());
308         assertThat(response.readEntity(String.class), containsString("success"));
309         assertThat(response.readEntity(String.class), containsString("xy"));
310         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(true));
311         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).bri, is(200));
312         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).xy[0], is(0.5119));
313         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).xy[1], is(0.4147));
314         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).colormode, is(HueStateColorBulb.ColorMode.xy));
315         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).toHSBType().getHue().intValue(),
316                 is((int) 27.47722590981918));
317         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).toHSBType().getSaturation().intValue(), is(88));
318         assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).toHSBType().getBrightness().intValue(), is(78));
319     }
320
321     @Test
322     public void allLightsAndSingleLight() {
323         Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/lights").request().get();
324         assertEquals(200, response.getStatus());
325
326         String body = response.readEntity(String.class);
327
328         assertThat(body, containsString("switch"));
329         assertThat(body, containsString("color"));
330         assertThat(body, containsString("white"));
331
332         // Single light access test
333         response = commonSetup.client.target(commonSetup.basePath + "/testuser/lights/2").request().get();
334         assertEquals(200, response.getStatus());
335         body = response.readEntity(String.class);
336         assertThat(body, containsString("color"));
337     }
338
339     private boolean assertHueValue(ItemCommandEvent ce, int hueValue) {
340         assertThat(((HSBType) ce.getItemCommand()).getHue().intValue(), is(hueValue * 360 / HueStateColorBulb.MAX_HUE));
341         return true;
342     }
343
344     private boolean assertSatValue(ItemCommandEvent ce, int satValue) {
345         assertThat(((HSBType) ce.getItemCommand()).getSaturation().intValue(),
346                 is(satValue * 100 / HueStateColorBulb.MAX_SAT));
347         return true;
348     }
349 }