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.voice.actiontemplatehli.internal;
15 import static org.hamcrest.MatcherAssert.assertThat;
16 import static org.hamcrest.core.Is.is;
17 import static org.openhab.voice.actiontemplatehli.internal.ActionTemplateInterpreterConstants.SERVICE_ID;
19 import java.io.IOException;
20 import java.util.List;
21 import java.util.Locale;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.junit.jupiter.api.BeforeEach;
26 import org.junit.jupiter.api.Test;
27 import org.mockito.Mock;
28 import org.mockito.Mockito;
29 import org.mockito.MockitoAnnotations;
30 import org.openhab.core.events.EventPublisher;
31 import org.openhab.core.items.GroupItem;
32 import org.openhab.core.items.ItemRegistry;
33 import org.openhab.core.items.Metadata;
34 import org.openhab.core.items.MetadataKey;
35 import org.openhab.core.items.MetadataRegistry;
36 import org.openhab.core.items.events.ItemEventFactory;
37 import org.openhab.core.library.items.NumberItem;
38 import org.openhab.core.library.items.StringItem;
39 import org.openhab.core.library.items.SwitchItem;
40 import org.openhab.core.library.types.DecimalType;
41 import org.openhab.core.library.types.OnOffType;
42 import org.openhab.core.library.types.StringType;
43 import org.openhab.core.types.StateDescriptionFragmentBuilder;
44 import org.openhab.core.types.StateOption;
45 import org.openhab.core.voice.text.InterpretationException;
46 import org.openhab.voice.actiontemplatehli.internal.configuration.ActionTemplateConfiguration;
47 import org.openhab.voice.actiontemplatehli.internal.configuration.ActionTemplateGroupTargets;
48 import org.openhab.voice.actiontemplatehli.internal.configuration.ActionTemplatePlaceholder;
50 import com.fasterxml.jackson.databind.ObjectMapper;
53 * The {@link ActionTemplateInterpreterTest} class contains the tests for the interpreter
55 * @author Miguel Álvarez - Initial contribution
58 public class ActionTemplateInterpreterTest {
59 private @Mock @NonNullByDefault({}) ItemRegistry itemRegistryMock;
60 private @Mock @NonNullByDefault({}) MetadataRegistry metadataRegistryMock;
61 private @Mock @NonNullByDefault({}) EventPublisher eventPublisherMock;
62 private @NonNullByDefault({}) ActionTemplateInterpreter interpreter;
65 public void setUp() throws IOException {
66 MockitoAnnotations.openMocks(this);
67 ObjectMapper mapper = new ObjectMapper();
69 var switchItem = new SwitchItem("testSwitch");
70 switchItem.setState(OnOffType.OFF);
71 switchItem.setLabel("bedroom light");
72 switchItem.addTag("Light");
73 Mockito.when(itemRegistryMock.get(switchItem.getName())).thenReturn(switchItem);
74 // Prepare Switch Write action
75 var switchNPLWriteAction = new ActionTemplateConfiguration();
76 switchNPLWriteAction.template = "$onOff $itemLabel";
77 switchNPLWriteAction.value = "$onOff";
78 var onOffPlaceholder = new ActionTemplatePlaceholder();
79 onOffPlaceholder.label = "onOff";
80 onOffPlaceholder.nerStaticValues = new String[] { "turn on", "turn off" };
81 onOffPlaceholder.posStaticValues = Map.of("turn__on", "ON", "turn__off", "OFF");
82 switchNPLWriteAction.placeholders = List.of(onOffPlaceholder);
83 // Prepare Switch Read action
84 var switchNPLReadAction = new ActionTemplateConfiguration();
85 switchNPLReadAction.read = true;
86 switchNPLReadAction.template = "how is the $itemLabel";
87 switchNPLReadAction.value = "$itemLabel is $state";
89 var groupItem = new GroupItem("testGroup");
90 groupItem.setLabel("bedroom");
91 groupItem.addTag("Location");
92 Mockito.when(itemRegistryMock.get(groupItem.getName())).thenReturn(groupItem);
94 var numberItem = new NumberItem("testNumber");
95 numberItem.setState(DecimalType.valueOf("1"));
96 numberItem.setLabel("channel");
97 numberItem.addTag("tv_channel");
99 .setStateDescriptionService((text, locale) -> StateDescriptionFragmentBuilder
100 .create().withOptions(List.of(new StateOption("1", "channel one"),
101 new StateOption("2", "channel two"), new StateOption("3", "channel three")))
102 .build().toStateDescription());
103 Mockito.when(itemRegistryMock.get(numberItem.getName())).thenReturn(numberItem);
104 // Prepare Group Write action
105 var groupNPLWriteAction = new ActionTemplateConfiguration();
106 groupNPLWriteAction.template = "turn on $itemLabel lights";
107 groupNPLWriteAction.requiredItemTags = new String[] { "Location" };
108 groupNPLWriteAction.value = "ON";
109 groupNPLWriteAction.memberTargets = new ActionTemplateGroupTargets();
110 groupNPLWriteAction.memberTargets.itemType = "Switch";
111 groupNPLWriteAction.memberTargets.requiredItemTags = new String[] { "Light" };
112 // Prepare Group Read action
113 var groupNPLReadAction = new ActionTemplateConfiguration();
114 groupNPLReadAction.read = true;
115 groupNPLReadAction.requiredItemTags = new String[] { "Location" };
116 groupNPLReadAction.template = "how is the light in the $itemLabel";
117 groupNPLReadAction.value = "$itemLabel in $groupLabel is $state";
118 var statePlaceholder = new ActionTemplatePlaceholder();
119 statePlaceholder.label = "state";
120 statePlaceholder.posStaticValues = Map.of("ON", "on", "OFF", "off");
121 groupNPLReadAction.placeholders = List.of(statePlaceholder);
122 groupNPLReadAction.memberTargets = new ActionTemplateGroupTargets();
123 groupNPLReadAction.memberTargets.itemName = switchItem.getName();
124 groupNPLReadAction.memberTargets.requiredItemTags = new String[] { "Light" };
125 // Prepare group write action using item option
126 var groupNPLOptionWriteAction = new ActionTemplateConfiguration();
127 groupNPLOptionWriteAction.template = "set $itemLabel channel to $itemOption";
128 groupNPLOptionWriteAction.requiredItemTags = new String[] { "Location" };
129 groupNPLOptionWriteAction.value = "$itemOption";
130 groupNPLOptionWriteAction.memberTargets = new ActionTemplateGroupTargets();
131 groupNPLOptionWriteAction.memberTargets.itemType = "Number";
132 groupNPLOptionWriteAction.memberTargets.requiredItemTags = new String[] { "tv_channel" };
133 // Prepare group read action using item option
134 var groupNPLOptionReadAction = new ActionTemplateConfiguration();
135 groupNPLOptionReadAction.read = true;
136 groupNPLOptionReadAction.requiredItemTags = new String[] { "Location" };
137 groupNPLOptionReadAction.template = "what channel is on the $itemLabel tv";
138 groupNPLOptionReadAction.value = "$groupLabel tv is on $itemOption";
139 groupNPLOptionReadAction.memberTargets = new ActionTemplateGroupTargets();
140 groupNPLOptionReadAction.memberTargets.itemType = "Number";
141 groupNPLOptionReadAction.memberTargets.requiredItemTags = new String[] { "tv_channel" };
142 // Add switch member to group
143 groupItem.addMember(switchItem);
144 // Add number member to group
145 groupItem.addMember(numberItem);
147 var stringItem = new StringItem("testString");
148 stringItem.setLabel("message example");
149 Mockito.when(itemRegistryMock.get(stringItem.getName())).thenReturn(stringItem);
150 // Prepare string write action
151 var stringNPLWriteAction = new ActionTemplateConfiguration();
152 stringNPLWriteAction.template = "send message $* to $contact";
153 stringNPLWriteAction.value = "$contact:$*";
154 stringNPLWriteAction.silent = true;
155 var contactPlaceholder = new ActionTemplatePlaceholder();
156 contactPlaceholder.label = "contact";
157 contactPlaceholder.nerStaticValues = new String[] { "Mark", "Andrea" };
158 contactPlaceholder.posStaticValues = Map.of("Mark", "+34000000000", "Andrea", "+34000000001");
159 stringNPLWriteAction.placeholders = List.of(contactPlaceholder);
160 var stringConfig = mapper.readValue(mapper.writeValueAsString(stringNPLWriteAction), Map.class);
161 // Mock metadata for 'testString'
162 Mockito.when(metadataRegistryMock.get(new MetadataKey(SERVICE_ID, stringItem.getName())))
163 .thenReturn(new Metadata(new MetadataKey(SERVICE_ID, stringItem.getName()), "", stringConfig));
165 Mockito.when(itemRegistryMock.getAll()).thenReturn(List.of(switchItem, stringItem, groupItem, numberItem));
167 interpreter = new ActionTemplateInterpreter(itemRegistryMock, metadataRegistryMock, eventPublisherMock) {
169 protected ActionTemplateConfiguration[] getTypeActionConfigs(String itemType) {
170 // mock type actions for testing
171 if ("Switch".equals(itemType)) {
172 return new ActionTemplateConfiguration[] { switchNPLWriteAction, switchNPLReadAction };
174 if ("Group".equals(itemType)) {
175 return new ActionTemplateConfiguration[] { groupNPLWriteAction, groupNPLReadAction,
176 groupNPLOptionReadAction, groupNPLOptionWriteAction };
178 return new ActionTemplateConfiguration[] {};
184 * Test type write action
187 public void switchItemOnOffTest() throws InterpretationException {
188 var response = interpreter.interpret(Locale.ENGLISH, "turn on bedroom light");
189 assertThat(response, is("Done"));
190 Mockito.verify(eventPublisherMock).post(ItemEventFactory.createCommandEvent("testSwitch", OnOffType.ON));
191 response = interpreter.interpret(Locale.ENGLISH, "turn off bedroom light");
192 assertThat(response, is("Done"));
193 Mockito.verify(eventPublisherMock).post(ItemEventFactory.createCommandEvent("testSwitch", OnOffType.OFF));
197 * Test type read action
200 public void switchItemReadTest() throws InterpretationException {
201 var response = interpreter.interpret(Locale.ENGLISH, "how is the bedroom light");
202 assertThat(response, is("bedroom light is OFF"));
206 * Test group write action targeting members
209 public void groupItemMemberOnTest() throws InterpretationException {
210 var response = interpreter.interpret(Locale.ENGLISH, "turn on bedroom lights");
211 assertThat(response, is("Done"));
212 Mockito.verify(eventPublisherMock).post(ItemEventFactory.createCommandEvent("testSwitch", OnOffType.ON));
216 * Test group read action targeting members
219 public void groupItemMemberReadTest() throws InterpretationException {
220 var response = interpreter.interpret(Locale.ENGLISH, "how is the light in the bedroom");
221 assertThat(response, is("bedroom light in bedroom is off"));
225 * Test target a group member item using the itemOption placeholder
228 public void groupItemOptionTest() throws InterpretationException {
229 var response = interpreter.interpret(Locale.ENGLISH, "what channel is on the bedroom tv");
230 assertThat(response, is("bedroom tv is on channel one"));
231 response = interpreter.interpret(Locale.ENGLISH, "set bedroom channel to channel two");
232 assertThat(response, is("Done"));
233 Mockito.verify(eventPublisherMock)
234 .post(ItemEventFactory.createCommandEvent("testNumber", new DecimalType("2")));
238 * Test write action using the dynamic label
241 public void messageTest() throws InterpretationException {
242 var response = interpreter.interpret(Locale.ENGLISH, "send message please turn off the bedroom light to mark");
243 // silent mode is enabled so no response
244 assertThat(response, is(""));
245 Mockito.verify(eventPublisherMock).post(ItemEventFactory.createCommandEvent("testString",
246 new StringType("+34000000000:please turn off the bedroom light")));