2 * Copyright (c) 2010-2023 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 org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jetty.client.api.ContentResponse;
27 import org.glassfish.jersey.server.ResourceConfig;
28 import org.junit.jupiter.api.AfterEach;
29 import org.junit.jupiter.api.BeforeEach;
30 import org.junit.jupiter.api.Test;
31 import org.openhab.core.automation.Condition;
32 import org.openhab.core.automation.Rule;
33 import org.openhab.core.automation.RuleRegistry;
34 import org.openhab.core.automation.Trigger;
35 import org.openhab.core.automation.util.RuleBuilder;
36 import org.openhab.core.items.GenericItem;
37 import org.openhab.core.items.ItemRegistry;
38 import org.openhab.core.library.items.ColorItem;
39 import org.openhab.core.library.items.SwitchItem;
40 import org.openhab.core.library.types.HSBType;
41 import org.openhab.core.library.types.OnOffType;
42 import org.openhab.core.types.State;
43 import org.openhab.io.hueemulation.internal.ConfigStore;
44 import org.openhab.io.hueemulation.internal.RuleUtils;
45 import org.openhab.io.hueemulation.internal.dto.HueRuleEntry;
46 import org.openhab.io.hueemulation.internal.dto.HueRuleEntry.Operator;
47 import org.openhab.io.hueemulation.internal.dto.HueSceneEntry;
48 import org.openhab.io.hueemulation.internal.dto.changerequest.HueCommand;
49 import org.openhab.io.hueemulation.internal.rest.mocks.DummyItemRegistry;
50 import org.openhab.io.hueemulation.internal.rest.mocks.DummyRuleRegistry;
52 import com.google.gson.Gson;
53 import com.google.gson.reflect.TypeToken;
56 * Tests for various rules API endpoints.
58 * @author David Graeff - Initial contribution
61 public class RulesTests {
63 protected @NonNullByDefault({}) CommonSetup commonSetup;
64 protected @NonNullByDefault({}) ConfigStore cs;
65 protected @NonNullByDefault({}) ItemRegistry itemRegistry;
66 protected @NonNullByDefault({}) RuleRegistry ruleRegistry;
68 Rules subject = new Rules();
69 LightsAndGroups lightsAndGroups = new LightsAndGroups();
71 private void addItemToReg(GenericItem item, State state, String tag, String label) {
75 itemRegistry.add(item);
79 public void setUp() throws IOException {
80 commonSetup = new CommonSetup(false);
81 this.cs = commonSetup.cs;
83 itemRegistry = new DummyItemRegistry();
84 ruleRegistry = new DummyRuleRegistry();
86 subject.cs = commonSetup.cs;
87 subject.userManagement = commonSetup.userManagement;
88 subject.ruleRegistry = ruleRegistry;
89 subject.itemRegistry = itemRegistry;
92 // We need the LightsAndGroups class to convert registry entries into HueDatastore
94 lightsAndGroups.cs = cs;
95 lightsAndGroups.eventPublisher = commonSetup.eventPublisher;
96 lightsAndGroups.userManagement = commonSetup.userManagement;
97 lightsAndGroups.itemRegistry = itemRegistry;
98 lightsAndGroups.activate();
100 addItemToReg(new SwitchItem("switch1"), OnOffType.ON, "Switchable", "name1");
101 addItemToReg(new SwitchItem("switch2"), OnOffType.ON, "Switchable", "name2");
102 addItemToReg(new ColorItem("color1"), HSBType.BLUE, "ColorLighting", "");
104 commonSetup.start(new ResourceConfig().registerInstances(subject));
108 public void tearDown() throws Exception {
109 RuleUtils.random = new Random();
110 commonSetup.dispose();
114 public void addUpdateRemoveScheduleToRegistry() {
115 assertThat(cs.ds.lights.get("switch1"), is(notNullValue()));
117 HueCommand command = new HueCommand("/api/testuser/lights/switch1/state", "PUT", "{'on':true}");
118 HueRuleEntry.Condition condition = new HueRuleEntry.Condition("/lights/switch1/state/on", Operator.dx, null);
120 Entry<Trigger, Condition> triggerCond = Rules.hueConditionToAutomation(command.address.replace("/", "-"),
121 condition, itemRegistry);
123 Rule rule = RuleBuilder.create("demo1").withName("test name").withTags(Rules.RULES_TAG) //
124 .withActions(RuleUtils.createHttpAction(command, "command")) //
125 .withTriggers(triggerCond.getKey()).withConditions(triggerCond.getValue()).build();
127 ruleRegistry.add(rule);
130 HueRuleEntry entry = cs.ds.rules.get("demo1");
131 assertThat(entry.conditions.get(0).address, is("/lights/switch1/state/on"));
132 assertThat(entry.conditions.get(0).operator, is(Operator.dx));
133 assertThat(entry.actions.get(0).address, is("/lights/switch1/state"));
134 assertThat(entry.actions.get(0).method, is("PUT"));
135 assertThat(entry.actions.get(0).body, is("{'on':true}"));
138 command = new HueCommand("/api/testuser/lights/switch2/state", "PUT", "{'on':false}");
139 rule = RuleBuilder.create("demo1").withName("name2").withTags(Rules.RULES_TAG) //
140 .withActions(RuleUtils.createHttpAction(command, "command")) //
141 .withTriggers(triggerCond.getKey()).withConditions(triggerCond.getValue()).build();
142 ruleRegistry.update(rule);
144 entry = cs.ds.rules.get("demo1");
145 assertThat(entry.actions.get(0).address, is("/lights/switch2/state"));
146 assertThat(entry.actions.get(0).method, is("PUT"));
147 assertThat(entry.actions.get(0).body, is("{'on':false}"));
148 assertThat(entry.name, is("name2"));
152 ruleRegistry.remove("demo1");
153 entry = cs.ds.rules.get("demo1");
154 assertThat(entry, nullValue());
157 @SuppressWarnings("null")
159 public void addGetRemoveRuleViaRest() throws Exception {
161 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}\"}]}";
162 ContentResponse response = commonSetup.sendPost("/testuser/rules", body);
163 assertEquals(200, response.getStatus());
164 assertThat(response.getContentAsString(), containsString("success"));
166 // 1.1 Check for entry
167 Entry<String, HueRuleEntry> idAndEntry = cs.ds.rules.entrySet().stream().findAny().get();
168 HueRuleEntry entry = idAndEntry.getValue();
169 assertThat(entry.name, is("test name"));
170 assertThat(entry.actions.get(0).address, is("/lights/switch1/state"));
171 assertThat(entry.conditions.get(0).address, is("/lights/switch1/state/on"));
173 // 1.2 Check for rule
174 Rule rule = ruleRegistry.get(idAndEntry.getKey());
175 assertThat(rule.getName(), is("test name"));
176 assertThat(rule.getActions().get(0).getId(), is("-api-testuser-lights-switch1-state"));
177 assertThat(rule.getActions().get(0).getTypeUID(), is("rules.HttpAction"));
180 response = commonSetup.sendGet("/testuser/rules/" + idAndEntry.getKey());
181 assertEquals(200, response.getStatus());
182 HueSceneEntry fromJson = new Gson().fromJson(response.getContentAsString(), HueSceneEntry.class);
183 assertThat(fromJson.name, is(idAndEntry.getValue().name));
186 response = commonSetup.sendDelete("/testuser/rules/" + idAndEntry.getKey());
187 assertEquals(200, response.getStatus());
188 assertTrue(cs.ds.rules.isEmpty());
192 public void updateRuleViaRest() throws Exception {
193 HueCommand command = new HueCommand("/api/testuser/lights/switch1/state", "PUT", "{'on':true}");
194 HueRuleEntry.Condition condition = new HueRuleEntry.Condition("/lights/switch1/state/on", Operator.dx, null);
196 Entry<Trigger, Condition> triggerCond = Rules.hueConditionToAutomation(command.address.replace("/", "-"),
197 condition, itemRegistry);
199 Rule rule = RuleBuilder.create("demo1").withName("test name").withTags(Rules.RULES_TAG) //
200 .withActions(RuleUtils.createHttpAction(command, "command")) //
201 .withTriggers(triggerCond.getKey()).withConditions(triggerCond.getValue()).build();
203 ruleRegistry.add(rule);
205 // Modify (just the name)
206 String body = "{ 'name':'A new name'}";
207 ContentResponse response = commonSetup.sendPut("/testuser/rules/demo1", body);
208 assertEquals(200, response.getStatus());
209 assertThat(response.getContentAsString(), containsString("name"));
211 Entry<String, HueRuleEntry> idAndEntry = cs.ds.rules.entrySet().stream().findAny().get();
212 HueRuleEntry entry = idAndEntry.getValue();
213 assertThat(entry.name, is("A new name"));
214 assertThat(entry.actions.get(0).address, is("/lights/switch1/state"));
215 assertThat(entry.conditions.get(0).address, is("/lights/switch1/state/on"));
218 rule = RuleBuilder.create("demo1").withName("test name").withTags(Rules.RULES_TAG) //
219 .withActions(RuleUtils.createHttpAction(command, "command")) //
220 .withTriggers(triggerCond.getKey()).withConditions(triggerCond.getValue()).build();
222 ruleRegistry.update(rule); // Reset rule
224 idAndEntry = cs.ds.rules.entrySet().stream().findAny().get();
226 // Modify (Change condition)
227 body = "{\"conditions\":[{\"address\":\"/lights/switch1/state/on\",\"operator\":\"ddx\"}]}";
228 response = commonSetup.sendPut("/testuser/rules/demo1", body);
229 assertEquals(200, response.getStatus());
230 assertThat(response.getContentAsString(), containsString("conditions"));
232 idAndEntry = cs.ds.rules.entrySet().stream().findAny().get();
233 entry = idAndEntry.getValue();
234 assertThat(entry.name, is("test name")); // should not have changed
235 assertThat(entry.conditions.get(0).operator, is(Operator.ddx));
237 // Modify (Change action)
238 body = "{\"actions\":[{\"address\":\"/lights/switch2/state\",\"method\":\"PUT\",\"body\":\"{\\u0027on\\u0027:false}\"}]}";
239 response = commonSetup.sendPut("/testuser/rules/demo1", body);
240 assertEquals(200, response.getStatus());
241 assertThat(response.getContentAsString(), containsString("actions"));
243 idAndEntry = cs.ds.rules.entrySet().stream().findAny().get();
244 entry = idAndEntry.getValue();
245 assertThat(entry.name, is("test name")); // should not have changed
246 assertThat(entry.actions.get(0).address, is("/lights/switch2/state"));
250 public void getAll() throws Exception {
251 HueCommand command = new HueCommand("/api/testuser/lights/switch1/state", "PUT", "{'on':true}");
252 HueRuleEntry.Condition condition = new HueRuleEntry.Condition("/lights/switch1/state/on", Operator.dx, null);
254 Entry<Trigger, Condition> triggerCond = Rules.hueConditionToAutomation(command.address.replace("/", "-"),
255 condition, itemRegistry);
257 Rule rule = RuleBuilder.create("demo1").withName("test name").withTags(Rules.RULES_TAG) //
258 .withActions(RuleUtils.createHttpAction(command, "command")) //
259 .withTriggers(triggerCond.getKey()).withConditions(triggerCond.getValue()).build();
261 ruleRegistry.add(rule);
263 ContentResponse response = commonSetup.sendGet("/testuser/rules");
264 Type type = new TypeToken<Map<String, HueRuleEntry>>() {
266 String body = response.getContentAsString();
267 Map<String, HueRuleEntry> fromJson = new Gson().fromJson(body, type);
268 HueRuleEntry entry = fromJson.get("demo1");
269 assertThat(entry.name, is("test name"));
270 assertThat(entry.actions.get(0).address, is("/lights/switch1/state"));
271 assertThat(entry.conditions.get(0).address, is("/lights/switch1/state/on"));