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