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