2 * Copyright (c) 2010-2022 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.hamcrest.MatcherAssert.assertThat;
17 import static org.junit.jupiter.api.Assertions.*;
19 import java.io.IOException;
20 import java.lang.reflect.Type;
22 import java.util.Map.Entry;
23 import java.util.Random;
25 import javax.ws.rs.client.Entity;
26 import javax.ws.rs.core.Response;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.glassfish.jersey.server.ResourceConfig;
30 import org.junit.jupiter.api.AfterEach;
31 import org.junit.jupiter.api.BeforeEach;
32 import org.junit.jupiter.api.Test;
33 import org.openhab.core.automation.Condition;
34 import org.openhab.core.automation.Rule;
35 import org.openhab.core.automation.RuleRegistry;
36 import org.openhab.core.automation.Trigger;
37 import org.openhab.core.automation.util.RuleBuilder;
38 import org.openhab.core.items.GenericItem;
39 import org.openhab.core.items.ItemRegistry;
40 import org.openhab.core.library.items.ColorItem;
41 import org.openhab.core.library.items.SwitchItem;
42 import org.openhab.core.library.types.HSBType;
43 import org.openhab.core.library.types.OnOffType;
44 import org.openhab.core.types.State;
45 import org.openhab.io.hueemulation.internal.ConfigStore;
46 import org.openhab.io.hueemulation.internal.RuleUtils;
47 import org.openhab.io.hueemulation.internal.dto.HueRuleEntry;
48 import org.openhab.io.hueemulation.internal.dto.HueRuleEntry.Operator;
49 import org.openhab.io.hueemulation.internal.dto.HueSceneEntry;
50 import org.openhab.io.hueemulation.internal.dto.changerequest.HueCommand;
51 import org.openhab.io.hueemulation.internal.rest.mocks.DummyItemRegistry;
52 import org.openhab.io.hueemulation.internal.rest.mocks.DummyRuleRegistry;
54 import com.google.gson.Gson;
55 import com.google.gson.reflect.TypeToken;
58 * Tests for various rules API endpoints.
60 * @author David Graeff - Initial contribution
63 public class RulesTests {
65 protected @NonNullByDefault({}) CommonSetup commonSetup;
66 protected @NonNullByDefault({}) ConfigStore cs;
67 protected @NonNullByDefault({}) ItemRegistry itemRegistry;
68 protected @NonNullByDefault({}) RuleRegistry ruleRegistry;
70 Rules subject = new Rules();
71 LightsAndGroups lightsAndGroups = new LightsAndGroups();
73 private void addItemToReg(GenericItem item, State state, String tag, String label) {
77 itemRegistry.add(item);
81 public void setUp() throws IOException {
82 commonSetup = new CommonSetup(false);
83 this.cs = commonSetup.cs;
85 itemRegistry = new DummyItemRegistry();
86 ruleRegistry = new DummyRuleRegistry();
88 subject.cs = commonSetup.cs;
89 subject.userManagement = commonSetup.userManagement;
90 subject.ruleRegistry = ruleRegistry;
91 subject.itemRegistry = itemRegistry;
94 // We need the LightsAndGroups class to convert registry entries into HueDatastore
96 lightsAndGroups.cs = cs;
97 lightsAndGroups.eventPublisher = commonSetup.eventPublisher;
98 lightsAndGroups.userManagement = commonSetup.userManagement;
99 lightsAndGroups.itemRegistry = itemRegistry;
100 lightsAndGroups.activate();
102 addItemToReg(new SwitchItem("switch1"), OnOffType.ON, "Switchable", "name1");
103 addItemToReg(new SwitchItem("switch2"), OnOffType.ON, "Switchable", "name2");
104 addItemToReg(new ColorItem("color1"), HSBType.BLUE, "ColorLighting", "");
106 commonSetup.start(new ResourceConfig().registerInstances(subject));
110 public void tearDown() throws Exception {
111 RuleUtils.random = new Random();
112 commonSetup.dispose();
116 public void addUpdateRemoveScheduleToRegistry() {
117 assertThat(cs.ds.lights.get("switch1"), is(notNullValue()));
119 HueCommand command = new HueCommand("/api/testuser/lights/switch1/state", "PUT", "{'on':true}");
120 HueRuleEntry.Condition condition = new HueRuleEntry.Condition("/lights/switch1/state/on", Operator.dx, null);
122 Entry<Trigger, Condition> triggerCond = Rules.hueConditionToAutomation(command.address.replace("/", "-"),
123 condition, itemRegistry);
125 Rule rule = RuleBuilder.create("demo1").withName("test name").withTags(Rules.RULES_TAG) //
126 .withActions(RuleUtils.createHttpAction(command, "command")) //
127 .withTriggers(triggerCond.getKey()).withConditions(triggerCond.getValue()).build();
129 ruleRegistry.add(rule);
132 HueRuleEntry entry = cs.ds.rules.get("demo1");
133 assertThat(entry.conditions.get(0).address, is("/lights/switch1/state/on"));
134 assertThat(entry.conditions.get(0).operator, is(Operator.dx));
135 assertThat(entry.actions.get(0).address, is("/lights/switch1/state"));
136 assertThat(entry.actions.get(0).method, is("PUT"));
137 assertThat(entry.actions.get(0).body, is("{'on':true}"));
140 command = new HueCommand("/api/testuser/lights/switch2/state", "PUT", "{'on':false}");
141 rule = RuleBuilder.create("demo1").withName("name2").withTags(Rules.RULES_TAG) //
142 .withActions(RuleUtils.createHttpAction(command, "command")) //
143 .withTriggers(triggerCond.getKey()).withConditions(triggerCond.getValue()).build();
144 ruleRegistry.update(rule);
146 entry = cs.ds.rules.get("demo1");
147 assertThat(entry.actions.get(0).address, is("/lights/switch2/state"));
148 assertThat(entry.actions.get(0).method, is("PUT"));
149 assertThat(entry.actions.get(0).body, is("{'on':false}"));
150 assertThat(entry.name, is("name2"));
154 ruleRegistry.remove("demo1");
155 entry = cs.ds.rules.get("demo1");
156 assertThat(entry, nullValue());
159 @SuppressWarnings("null")
161 public void addGetRemoveRuleViaRest() {
163 String body = "{\"name\":\"test name\",\"description\":\"\",\"owner\":\"\",\"conditions\":[{\"address\":\"/lights/switch1/state/on\",\"operator\":\"dx\"}],\"actions\":[{\"address\":\"/lights/switch1/state\",\"method\":\"PUT\",\"body\":\"{\\u0027on\\u0027:true}\"}]}";
164 Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/rules").request()
165 .post(Entity.json(body));
166 assertEquals(200, response.getStatus());
167 assertThat(response.readEntity(String.class), containsString("success"));
169 // 1.1 Check for entry
170 Entry<String, HueRuleEntry> idAndEntry = cs.ds.rules.entrySet().stream().findAny().get();
171 HueRuleEntry entry = idAndEntry.getValue();
172 assertThat(entry.name, is("test name"));
173 assertThat(entry.actions.get(0).address, is("/lights/switch1/state"));
174 assertThat(entry.conditions.get(0).address, is("/lights/switch1/state/on"));
176 // 1.2 Check for rule
177 Rule rule = ruleRegistry.get(idAndEntry.getKey());
178 assertThat(rule.getName(), is("test name"));
179 assertThat(rule.getActions().get(0).getId(), is("-api-testuser-lights-switch1-state"));
180 assertThat(rule.getActions().get(0).getTypeUID(), is("rules.HttpAction"));
183 response = commonSetup.client.target(commonSetup.basePath + "/testuser/rules/" + idAndEntry.getKey()).request()
185 assertEquals(200, response.getStatus());
186 HueSceneEntry fromJson = new Gson().fromJson(response.readEntity(String.class), HueSceneEntry.class);
187 assertThat(fromJson.name, is(idAndEntry.getValue().name));
190 response = commonSetup.client.target(commonSetup.basePath + "/testuser/rules/" + idAndEntry.getKey()).request()
192 assertEquals(200, response.getStatus());
193 assertTrue(cs.ds.rules.isEmpty());
197 public void updateRuleViaRest() {
198 HueCommand command = new HueCommand("/api/testuser/lights/switch1/state", "PUT", "{'on':true}");
199 HueRuleEntry.Condition condition = new HueRuleEntry.Condition("/lights/switch1/state/on", Operator.dx, null);
201 Entry<Trigger, Condition> triggerCond = Rules.hueConditionToAutomation(command.address.replace("/", "-"),
202 condition, itemRegistry);
204 Rule rule = RuleBuilder.create("demo1").withName("test name").withTags(Rules.RULES_TAG) //
205 .withActions(RuleUtils.createHttpAction(command, "command")) //
206 .withTriggers(triggerCond.getKey()).withConditions(triggerCond.getValue()).build();
208 ruleRegistry.add(rule);
210 // Modify (just the name)
211 String body = "{ 'name':'A new name'}";
212 Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/rules/demo1").request()
213 .put(Entity.json(body));
214 assertEquals(200, response.getStatus());
215 assertThat(response.readEntity(String.class), containsString("name"));
217 Entry<String, HueRuleEntry> idAndEntry = cs.ds.rules.entrySet().stream().findAny().get();
218 HueRuleEntry entry = idAndEntry.getValue();
219 assertThat(entry.name, is("A new name"));
220 assertThat(entry.actions.get(0).address, is("/lights/switch1/state"));
221 assertThat(entry.conditions.get(0).address, is("/lights/switch1/state/on"));
224 rule = RuleBuilder.create("demo1").withName("test name").withTags(Rules.RULES_TAG) //
225 .withActions(RuleUtils.createHttpAction(command, "command")) //
226 .withTriggers(triggerCond.getKey()).withConditions(triggerCond.getValue()).build();
228 ruleRegistry.update(rule); // Reset rule
230 idAndEntry = cs.ds.rules.entrySet().stream().findAny().get();
232 // Modify (Change condition)
233 body = "{\"conditions\":[{\"address\":\"/lights/switch1/state/on\",\"operator\":\"ddx\"}]}";
234 response = commonSetup.client.target(commonSetup.basePath + "/testuser/rules/demo1").request()
235 .put(Entity.json(body));
236 assertEquals(200, response.getStatus());
237 assertThat(response.readEntity(String.class), containsString("conditions"));
239 idAndEntry = cs.ds.rules.entrySet().stream().findAny().get();
240 entry = idAndEntry.getValue();
241 assertThat(entry.name, is("test name")); // should not have changed
242 assertThat(entry.conditions.get(0).operator, is(Operator.ddx));
244 // Modify (Change action)
245 body = "{\"actions\":[{\"address\":\"/lights/switch2/state\",\"method\":\"PUT\",\"body\":\"{\\u0027on\\u0027:false}\"}]}";
246 response = commonSetup.client.target(commonSetup.basePath + "/testuser/rules/demo1").request()
247 .put(Entity.json(body));
248 assertEquals(200, response.getStatus());
249 assertThat(response.readEntity(String.class), containsString("actions"));
251 idAndEntry = cs.ds.rules.entrySet().stream().findAny().get();
252 entry = idAndEntry.getValue();
253 assertThat(entry.name, is("test name")); // should not have changed
254 assertThat(entry.actions.get(0).address, is("/lights/switch2/state"));
258 public void getAll() {
259 HueCommand command = new HueCommand("/api/testuser/lights/switch1/state", "PUT", "{'on':true}");
260 HueRuleEntry.Condition condition = new HueRuleEntry.Condition("/lights/switch1/state/on", Operator.dx, null);
262 Entry<Trigger, Condition> triggerCond = Rules.hueConditionToAutomation(command.address.replace("/", "-"),
263 condition, itemRegistry);
265 Rule rule = RuleBuilder.create("demo1").withName("test name").withTags(Rules.RULES_TAG) //
266 .withActions(RuleUtils.createHttpAction(command, "command")) //
267 .withTriggers(triggerCond.getKey()).withConditions(triggerCond.getValue()).build();
269 ruleRegistry.add(rule);
271 Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/rules").request().get();
272 Type type = new TypeToken<Map<String, HueRuleEntry>>() {
274 String body = response.readEntity(String.class);
275 Map<String, HueRuleEntry> fromJson = new Gson().fromJson(body, type);
276 HueRuleEntry entry = fromJson.get("demo1");
277 assertThat(entry.name, is("test name"));
278 assertThat(entry.actions.get(0).address, is("/lights/switch1/state"));
279 assertThat(entry.conditions.get(0).address, is("/lights/switch1/state/on"));