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.*;
18 import static org.mockito.Mockito.*;
20 import java.io.IOException;
21 import java.lang.reflect.Type;
22 import java.util.Collections;
24 import java.util.Map.Entry;
25 import java.util.Random;
27 import javax.ws.rs.client.Entity;
28 import javax.ws.rs.core.Response;
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;
53 import com.google.gson.Gson;
54 import com.google.gson.reflect.TypeToken;
57 * Tests for various schedule API endpoints.
59 * @author David Graeff - Initial contribution
62 public class ScheduleTests {
63 protected @NonNullByDefault({}) CommonSetup commonSetup;
64 protected @NonNullByDefault({}) ConfigStore cs;
65 protected @NonNullByDefault({}) RuleRegistry ruleRegistry;
67 Schedules subject = new Schedules();
70 public void setUp() throws IOException {
71 commonSetup = new CommonSetup(false);
72 this.cs = commonSetup.cs;
74 ruleRegistry = new DummyRuleRegistry();
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;
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));
88 commonSetup.start(new ResourceConfig().registerInstances(subject));
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();
94 Integer bound = a.getArgument(0);
96 }).when(random).nextInt(anyInt());
97 RuleUtils.random = random;
101 public void tearDown() {
102 RuleUtils.random = new Random();
103 commonSetup.dispose();
106 @SuppressWarnings("null")
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";
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();
116 ruleRegistry.add(rule);
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));
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);
132 sceneEntry = cs.ds.schedules.get("demo1");
133 assertThat(sceneEntry.command.address, is("/api/testuser/lights/1/state"));
134 assertThat(sceneEntry.localtime, is(localtime));
138 ruleRegistry.remove("demo1");
139 sceneEntry = cs.ds.schedules.get("demo1");
140 assertThat(sceneEntry, nullValue());
143 @SuppressWarnings("null")
145 public void addGetRemoveScheduleViaRest() {
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"));
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"));
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"));
169 response = commonSetup.client.target(commonSetup.basePath + "/testuser/schedules/" + entry.getKey()).request()
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));
176 response = commonSetup.client.target(commonSetup.basePath + "/testuser/schedules/" + entry.getKey()).request()
178 assertEquals(200, response.getStatus());
179 assertTrue(cs.ds.schedules.isEmpty());
182 @SuppressWarnings("null")
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";
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();
192 ruleRegistry.add(rule);
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"));
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
205 assertThat(entry.getValue().localtime, is(localtime));
208 rule = RuleBuilder.create("demo1").withName("test name").withTags(Schedules.SCHEDULE_TAG) //
209 .withActions(RuleUtils.createHttpAction(command, "command")) //
210 .withTriggers(RuleUtils.createTriggerForTimeString(localtime)).build();
212 ruleRegistry.update(rule); // Reset rule
214 entry = cs.ds.schedules.entrySet().stream().findAny().get();
215 String uid = entry.getKey();
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"));
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"));
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"));
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"));
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";
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();
251 ruleRegistry.add(rule);
253 Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/schedules").request().get();
254 Type type = new TypeToken<Map<String, HueSceneEntry>>() {
256 Map<String, HueSceneEntry> fromJson = new Gson().fromJson(response.readEntity(String.class), type);
257 assertTrue(fromJson.containsKey("demo1"));
261 public void timeStringToTrigger() {
264 Configuration configuration;
267 timeString = "2020-02-01T12:12:00";
268 trigger = RuleUtils.createTriggerForTimeString(timeString);
269 configuration = trigger.getConfiguration();
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"));
275 // absolute randomized time
276 timeString = "2020-02-01T12:12:00A14:12:34";
277 trigger = RuleUtils.createTriggerForTimeString(timeString);
278 configuration = trigger.getConfiguration();
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"));
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();
291 assertThat(trigger.getTypeUID(), is("timer.GenericCronTrigger"));
292 assertThat(configuration.get("cronExpression"), is("15 12 * * 6,7"));
294 // Recurring randomized times
295 timeString = "W127/T12:15:17A14:12:34";
296 trigger = RuleUtils.createTriggerForTimeString(timeString);
297 configuration = trigger.getConfiguration();
299 assertThat(trigger.getTypeUID(), is("timer.GenericCronTrigger"));
300 assertThat(configuration.get("cronExpression"), is("15 14 * * 1,2,3,4,5,6,7"));
302 // Timer, expiring after given time
303 timeString = "PT12:12:00";
304 trigger = RuleUtils.createTriggerForTimeString(timeString);
305 configuration = trigger.getConfiguration();
307 assertThat(trigger.getTypeUID(), is("timer.TimerTrigger"));
308 assertThat(configuration.get("time"), is("12:12:00"));
310 // Timer with random element
311 timeString = "PT12:12:00A14:12:34";
312 trigger = RuleUtils.createTriggerForTimeString(timeString);
313 configuration = trigger.getConfiguration();
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"));
319 // Timers, Recurring timer
320 timeString = "R/PT12:12:00";
321 trigger = RuleUtils.createTriggerForTimeString(timeString);
322 configuration = trigger.getConfiguration();
324 assertThat(trigger.getTypeUID(), is("timer.TimerTrigger"));
325 assertThat(configuration.get("time"), is("12:12:00"));
326 assertThat(configuration.get("repeat"), is("-1"));
328 // Recurring timer with random element
329 timeString = "R12/PT12:12:00A14:12:34";
330 trigger = RuleUtils.createTriggerForTimeString(timeString);
331 configuration = trigger.getConfiguration();
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"));
340 public void triggerToTimestring() {
343 Configuration configuration;
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));
353 assertThat(timeString, is("2020-02-01T12:12:00"));
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));
364 assertThat(timeString, is("2020-02-01T12:12:00A14:12:34"));
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));
374 assertThat(timeString, is("W3/T12:15:00"));
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));
383 assertThat(timeString, is("W127/T14:15:00"));
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));
392 assertThat(timeString, is("PT12:12:00"));
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));
402 assertThat(timeString, is("PT12:12:00A14:12:34"));
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));
412 assertThat(timeString, is("R/PT12:12:00"));
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));
423 assertThat(timeString, is("R12/PT12:12:00A14:12:34"));