]> git.basschouten.com Git - openhab-addons.git/blob
e67b47b9c7b8f09dad32f9704d3b8650c26443d9
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.junit.Assert.*;
17 import static org.mockito.ArgumentMatchers.*;
18 import static org.mockito.Mockito.*;
19
20 import java.io.IOException;
21 import java.lang.reflect.Type;
22 import java.util.Collections;
23 import java.util.Map;
24 import java.util.Map.Entry;
25 import java.util.Random;
26
27 import javax.ws.rs.client.Entity;
28 import javax.ws.rs.core.Response;
29
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.openhab.core.config.core.Configuration;
32 import org.openhab.core.library.items.ColorItem;
33 import org.openhab.core.library.items.SwitchItem;
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.core.automation.Rule;
39 import org.openhab.core.automation.RuleManager;
40 import org.openhab.core.automation.RuleRegistry;
41 import org.openhab.core.automation.Trigger;
42 import org.openhab.core.automation.util.RuleBuilder;
43 import org.openhab.core.automation.util.TriggerBuilder;
44 import org.openhab.io.hueemulation.internal.ConfigStore;
45 import org.openhab.io.hueemulation.internal.DeviceType;
46 import org.openhab.io.hueemulation.internal.RuleUtils;
47 import org.openhab.io.hueemulation.internal.dto.HueLightEntry;
48 import org.openhab.io.hueemulation.internal.dto.HueSceneEntry;
49 import org.openhab.io.hueemulation.internal.dto.HueScheduleEntry;
50 import org.openhab.io.hueemulation.internal.dto.changerequest.HueCommand;
51 import org.openhab.io.hueemulation.internal.rest.mocks.DummyRuleRegistry;
52
53 import com.google.gson.Gson;
54 import com.google.gson.reflect.TypeToken;
55
56 /**
57  * Tests for various schedule API endpoints.
58  *
59  * @author David Graeff - Initial contribution
60  */
61 @NonNullByDefault
62 public class ScheduleTests {
63     protected @NonNullByDefault({}) CommonSetup commonSetup;
64     protected @NonNullByDefault({}) ConfigStore cs;
65     protected @NonNullByDefault({}) RuleRegistry ruleRegistry;
66
67     Schedules subject = new Schedules();
68
69     @Before
70     public void setUp() throws IOException {
71         commonSetup = new CommonSetup(false);
72         this.cs = commonSetup.cs;
73
74         ruleRegistry = new DummyRuleRegistry();
75
76         subject.cs = commonSetup.cs;
77         subject.userManagement = commonSetup.userManagement;
78         subject.ruleManager = mock(RuleManager.class);
79         when(subject.ruleManager.isEnabled(anyString())).thenReturn(true);
80         subject.ruleRegistry = ruleRegistry;
81         subject.activate();
82
83         // Add simulated lights
84         cs.ds.lights.put("1", new HueLightEntry(new SwitchItem("switch"), "switch", DeviceType.SwitchType));
85         cs.ds.lights.put("2", new HueLightEntry(new ColorItem("color"), "color", DeviceType.ColorType));
86         cs.ds.lights.put("3", new HueLightEntry(new ColorItem("white"), "white", DeviceType.WhiteTemperatureType));
87
88         commonSetup.start(new ResourceConfig().registerInstances(subject));
89
90         // Mock random -> always return int=10 or the highest possible int if bounded
91         Random random = mock(Random.class);
92         doReturn(10).when(random).nextInt();
93         doAnswer(a -> {
94             Integer bound = a.getArgument(0);
95             return bound - 1;
96         }).when(random).nextInt(anyInt());
97         RuleUtils.random = random;
98     }
99
100     @After
101     public void tearDown() {
102         RuleUtils.random = new Random();
103         commonSetup.dispose();
104     }
105
106     @SuppressWarnings("null")
107     @Test
108     public void addUpdateRemoveScheduleToRegistry() {
109         HueCommand command = new HueCommand("/api/testuser/lights/1/state", "PUT", "{'on':true}");
110         String localtime = "2020-02-01T12:12:00";
111
112         Rule rule = RuleBuilder.create("demo1").withName("test name").withTags(Schedules.SCHEDULE_TAG) //
113                 .withActions(RuleUtils.createHttpAction(command, "command")) //
114                 .withTriggers(RuleUtils.createTriggerForTimeString(localtime)).build();
115
116         ruleRegistry.add(rule);
117
118         // Check hue entry
119         HueScheduleEntry sceneEntry = cs.ds.schedules.get("demo1");
120         assertThat(sceneEntry.command.address, is("/api/testuser/lights/1/state"));
121         assertThat(sceneEntry.command.method, is("PUT"));
122         assertThat(sceneEntry.command.body, is("{'on':true}"));
123         assertThat(sceneEntry.localtime, is(localtime));
124
125         // Update
126         localtime = "2021-03-01T17:12:00";
127         rule = RuleBuilder.create("demo1").withName("test name").withTags(Schedules.SCHEDULE_TAG) //
128                 .withActions(RuleUtils.createHttpAction(command, "command")) //
129                 .withTriggers(RuleUtils.createTriggerForTimeString(localtime)).build();
130         ruleRegistry.update(rule);
131
132         sceneEntry = cs.ds.schedules.get("demo1");
133         assertThat(sceneEntry.command.address, is("/api/testuser/lights/1/state"));
134         assertThat(sceneEntry.localtime, is(localtime));
135
136         // Remove
137
138         ruleRegistry.remove("demo1");
139         sceneEntry = cs.ds.schedules.get("demo1");
140         assertThat(sceneEntry, nullValue());
141     }
142
143     @SuppressWarnings("null")
144     @Test
145     public void addGetRemoveScheduleViaRest() {
146         // 1. Create
147         String body = "{ 'name':'Wake up', 'description':'My wake up alarm', 'localtime':'2015-06-30T14:24:40'," + //
148                 "'command':{'address':'/api/testuser/lights/1/state','method':'PUT','body':'{\"on\":true}'} }";
149         Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/schedules").request()
150                 .post(Entity.json(body));
151         assertEquals(200, response.getStatus());
152         assertThat(response.readEntity(String.class), containsString("success"));
153
154         // 1.1 Check for entry
155         Entry<String, HueScheduleEntry> entry = cs.ds.schedules.entrySet().stream().findAny().get();
156         assertThat(entry.getValue().name, is("Wake up"));
157         assertThat(entry.getValue().command.address, is("/api/testuser/lights/1/state"));
158         assertThat(entry.getValue().command.method, is("PUT"));
159         assertThat(entry.getValue().command.body, is("{\"on\":true}"));
160         assertThat(entry.getValue().localtime, is("2015-06-30T14:24:40"));
161
162         // 1.2 Check for rule
163         Rule rule = ruleRegistry.get(entry.getKey());
164         assertThat(rule.getName(), is("Wake up"));
165         assertThat(rule.getActions().get(0).getId(), is("command"));
166         assertThat(rule.getActions().get(0).getTypeUID(), is("rules.HttpAction"));
167
168         // 2. Get
169         response = commonSetup.client.target(commonSetup.basePath + "/testuser/schedules/" + entry.getKey()).request()
170                 .get();
171         assertEquals(200, response.getStatus());
172         HueSceneEntry fromJson = new Gson().fromJson(response.readEntity(String.class), HueSceneEntry.class);
173         assertThat(fromJson.name, is(entry.getValue().name));
174
175         // 3. Remove
176         response = commonSetup.client.target(commonSetup.basePath + "/testuser/schedules/" + entry.getKey()).request()
177                 .delete();
178         assertEquals(200, response.getStatus());
179         assertTrue(cs.ds.schedules.isEmpty());
180     }
181
182     @SuppressWarnings("null")
183     @Test
184     public void updateScheduleViaRest() {
185         HueCommand command = new HueCommand("/api/testuser/lights/1/state", "PUT", "{'on':true}");
186         String localtime = "2020-02-01T12:12:00";
187
188         Rule rule = RuleBuilder.create("demo1").withName("test name").withTags(Schedules.SCHEDULE_TAG) //
189                 .withActions(RuleUtils.createHttpAction(command, "command")) //
190                 .withTriggers(RuleUtils.createTriggerForTimeString(localtime)).build();
191
192         ruleRegistry.add(rule);
193
194         // Modify (just the name)
195         String body = "{ 'name':'A new name'}";
196         Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/schedules/demo1").request()
197                 .put(Entity.json(body));
198         assertEquals(200, response.getStatus());
199         assertThat(response.readEntity(String.class), containsString("name"));
200
201         Entry<String, HueScheduleEntry> entry = cs.ds.schedules.entrySet().stream().findAny().get();
202         assertThat(entry.getValue().name, is("A new name"));
203         assertThat(entry.getValue().command.address, is("/api/testuser/lights/1/state")); // nothing else should have
204                                                                                           // changed
205         assertThat(entry.getValue().localtime, is(localtime));
206
207         // Reset
208         rule = RuleBuilder.create("demo1").withName("test name").withTags(Schedules.SCHEDULE_TAG) //
209                 .withActions(RuleUtils.createHttpAction(command, "command")) //
210                 .withTriggers(RuleUtils.createTriggerForTimeString(localtime)).build();
211
212         ruleRegistry.update(rule); // Reset rule
213
214         entry = cs.ds.schedules.entrySet().stream().findAny().get();
215         String uid = entry.getKey();
216
217         // Modify (Change time)
218         body = "{ 'localtime':'2015-06-30T14:24:40'}";
219         response = commonSetup.client.target(commonSetup.basePath + "/testuser/schedules/demo1").request()
220                 .put(Entity.json(body));
221         assertEquals(200, response.getStatus());
222         assertThat(response.readEntity(String.class), containsString("localtime"));
223
224         entry = cs.ds.schedules.entrySet().stream().findAny().get();
225         assertThat(entry.getValue().name, is("test name")); // should not have changed
226         assertThat(entry.getKey(), is(uid));
227         assertThat(entry.getValue().localtime, is("2015-06-30T14:24:40"));
228
229         // Modify (Change command)
230         body = "{ 'command':{'address':'/api/testuser/lights/2/state','method':'PUT','body':'{\"on\":true}'} }";
231         response = commonSetup.client.target(commonSetup.basePath + "/testuser/schedules/demo1").request()
232                 .put(Entity.json(body));
233         assertEquals(200, response.getStatus());
234         assertThat(response.readEntity(String.class), containsString("command"));
235
236         entry = cs.ds.schedules.entrySet().stream().findAny().get();
237         assertThat(entry.getValue().name, is("test name")); // should not have changed
238         assertThat(entry.getKey(), is(uid));
239         assertThat(entry.getValue().command.address, is("/api/testuser/lights/2/state"));
240     }
241
242     @Test
243     public void getAll() {
244         HueCommand command = new HueCommand("/api/testuser/lights/1/state", "POST", "{'on':true}");
245         String localtime = "2020-02-01T12:12:00";
246
247         Rule rule = RuleBuilder.create("demo1").withName("test name").withTags(Schedules.SCHEDULE_TAG) //
248                 .withActions(RuleUtils.createHttpAction(command, "command")) //
249                 .withTriggers(RuleUtils.createTriggerForTimeString(localtime)).build();
250
251         ruleRegistry.add(rule);
252
253         Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/schedules").request().get();
254         Type type = new TypeToken<Map<String, HueSceneEntry>>() {
255         }.getType();
256         Map<String, HueSceneEntry> fromJson = new Gson().fromJson(response.readEntity(String.class), type);
257         assertTrue(fromJson.containsKey("demo1"));
258     }
259
260     @Test
261     public void timeStringToTrigger() {
262         String timeString;
263         Trigger trigger;
264         Configuration configuration;
265
266         // absolute time
267         timeString = "2020-02-01T12:12:00";
268         trigger = RuleUtils.createTriggerForTimeString(timeString);
269         configuration = trigger.getConfiguration();
270
271         assertThat(trigger.getTypeUID(), is("timer.AbsoluteDateTimeTrigger"));
272         assertThat(configuration.get("date"), is("2020-02-01"));
273         assertThat(configuration.get("time"), is("12:12:00"));
274
275         // absolute randomized time
276         timeString = "2020-02-01T12:12:00A14:12:34";
277         trigger = RuleUtils.createTriggerForTimeString(timeString);
278         configuration = trigger.getConfiguration();
279
280         assertThat(trigger.getTypeUID(), is("timer.AbsoluteDateTimeTrigger"));
281         assertThat(configuration.get("date"), is("2020-02-01"));
282         assertThat(configuration.get("time"), is("12:12:00"));
283         assertThat(configuration.get("randomizeTime"), is("14:12:34"));
284
285         // Recurring times,Monday = 64, Tuesday = 32, Wednesday = 16, Thursday = 8, Friday = 4, Saturday = 2, Sunday= 1
286         // Cron expression: min hour day month weekdays
287         timeString = "W3/T12:15:17";
288         trigger = RuleUtils.createTriggerForTimeString(timeString);
289         configuration = trigger.getConfiguration();
290
291         assertThat(trigger.getTypeUID(), is("timer.GenericCronTrigger"));
292         assertThat(configuration.get("cronExpression"), is("15 12 * * 6,7"));
293
294         // Recurring randomized times
295         timeString = "W127/T12:15:17A14:12:34";
296         trigger = RuleUtils.createTriggerForTimeString(timeString);
297         configuration = trigger.getConfiguration();
298
299         assertThat(trigger.getTypeUID(), is("timer.GenericCronTrigger"));
300         assertThat(configuration.get("cronExpression"), is("15 14 * * 1,2,3,4,5,6,7"));
301
302         // Timer, expiring after given time
303         timeString = "PT12:12:00";
304         trigger = RuleUtils.createTriggerForTimeString(timeString);
305         configuration = trigger.getConfiguration();
306
307         assertThat(trigger.getTypeUID(), is("timer.TimerTrigger"));
308         assertThat(configuration.get("time"), is("12:12:00"));
309
310         // Timer with random element
311         timeString = "PT12:12:00A14:12:34";
312         trigger = RuleUtils.createTriggerForTimeString(timeString);
313         configuration = trigger.getConfiguration();
314
315         assertThat(trigger.getTypeUID(), is("timer.TimerTrigger"));
316         assertThat(configuration.get("time"), is("12:12:00"));
317         assertThat(configuration.get("randomizeTime"), is("14:12:34"));
318
319         // Timers, Recurring timer
320         timeString = "R/PT12:12:00";
321         trigger = RuleUtils.createTriggerForTimeString(timeString);
322         configuration = trigger.getConfiguration();
323
324         assertThat(trigger.getTypeUID(), is("timer.TimerTrigger"));
325         assertThat(configuration.get("time"), is("12:12:00"));
326         assertThat(configuration.get("repeat"), is("-1"));
327
328         // Recurring timer with random element
329         timeString = "R12/PT12:12:00A14:12:34";
330         trigger = RuleUtils.createTriggerForTimeString(timeString);
331         configuration = trigger.getConfiguration();
332
333         assertThat(trigger.getTypeUID(), is("timer.TimerTrigger"));
334         assertThat(configuration.get("time"), is("12:12:00"));
335         assertThat(configuration.get("randomizeTime"), is("14:12:34"));
336         assertThat(configuration.get("repeat"), is("12"));
337     }
338
339     @Test
340     public void triggerToTimestring() {
341         String timeString;
342         Trigger trigger;
343         Configuration configuration;
344
345         // absolute time
346         configuration = new Configuration();
347         configuration.put("date", "2020-02-01");
348         configuration.put("time", "12:12:00");
349         trigger = TriggerBuilder.create().withId("absolutetrigger").withTypeUID("timer.AbsoluteDateTimeTrigger")
350                 .withConfiguration(configuration).build();
351         timeString = RuleUtils.timeStringFromTrigger(Collections.singletonList(trigger));
352
353         assertThat(timeString, is("2020-02-01T12:12:00"));
354
355         // absolute randomized time
356         configuration = new Configuration();
357         configuration.put("date", "2020-02-01");
358         configuration.put("time", "12:12:00");
359         configuration.put("randomizeTime", "14:12:34");
360         trigger = TriggerBuilder.create().withId("absolutetrigger").withTypeUID("timer.AbsoluteDateTimeTrigger")
361                 .withConfiguration(configuration).build();
362         timeString = RuleUtils.timeStringFromTrigger(Collections.singletonList(trigger));
363
364         assertThat(timeString, is("2020-02-01T12:12:00A14:12:34"));
365
366         // Recurring times,Monday = 64, Tuesday = 32, Wednesday = 16, Thursday = 8, Friday = 4, Saturday = 2, Sunday= 1
367         // Cron expression: min hour day month weekdays
368         configuration = new Configuration();
369         configuration.put("cronExpression", "15 12 * * 6,7");
370         trigger = TriggerBuilder.create().withId("crontrigger").withTypeUID("timer.GenericCronTrigger")
371                 .withConfiguration(configuration).build();
372         timeString = RuleUtils.timeStringFromTrigger(Collections.singletonList(trigger));
373
374         assertThat(timeString, is("W3/T12:15:00"));
375
376         // Recurring randomized times (not possible, the cron rule has no way to store that info)
377         configuration = new Configuration();
378         configuration.put("cronExpression", "15 14 * * 1,2,3,4,5,6,7");
379         trigger = TriggerBuilder.create().withId("crontrigger").withTypeUID("timer.GenericCronTrigger")
380                 .withConfiguration(configuration).build();
381         timeString = RuleUtils.timeStringFromTrigger(Collections.singletonList(trigger));
382
383         assertThat(timeString, is("W127/T14:15:00"));
384
385         // Timer, expiring after given time
386         configuration = new Configuration();
387         configuration.put("time", "12:12:00");
388         trigger = TriggerBuilder.create().withId("timertrigger").withTypeUID("timer.TimerTrigger")
389                 .withConfiguration(configuration).build();
390         timeString = RuleUtils.timeStringFromTrigger(Collections.singletonList(trigger));
391
392         assertThat(timeString, is("PT12:12:00"));
393
394         // Timer with random element
395         configuration = new Configuration();
396         configuration.put("time", "12:12:00");
397         configuration.put("randomizeTime", "14:12:34");
398         trigger = TriggerBuilder.create().withId("timertrigger").withTypeUID("timer.TimerTrigger")
399                 .withConfiguration(configuration).build();
400         timeString = RuleUtils.timeStringFromTrigger(Collections.singletonList(trigger));
401
402         assertThat(timeString, is("PT12:12:00A14:12:34"));
403
404         // Timers, Recurring timer
405         configuration = new Configuration();
406         configuration.put("time", "12:12:00");
407         configuration.put("repeat", -1);
408         trigger = TriggerBuilder.create().withId("timertrigger").withTypeUID("timer.TimerTrigger")
409                 .withConfiguration(configuration).build();
410         timeString = RuleUtils.timeStringFromTrigger(Collections.singletonList(trigger));
411
412         assertThat(timeString, is("R/PT12:12:00"));
413
414         // Recurring timer with random element
415         configuration = new Configuration();
416         configuration.put("time", "12:12:00");
417         configuration.put("randomizeTime", "14:12:34");
418         configuration.put("repeat", 12);
419         trigger = TriggerBuilder.create().withId("timertrigger").withTypeUID("timer.TimerTrigger")
420                 .withConfiguration(configuration).build();
421         timeString = RuleUtils.timeStringFromTrigger(Collections.singletonList(trigger));
422
423         assertThat(timeString, is("R12/PT12:12:00A14:12:34"));
424     }
425 }