2 * Copyright (c) 2010-2024 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.transform.basicprofiles.internal.profiles;
15 import static org.hamcrest.Matchers.*;
16 import static org.junit.jupiter.api.Assertions.assertEquals;
17 import static org.mockito.ArgumentMatchers.*;
18 import static org.mockito.ArgumentMatchers.any;
19 import static org.mockito.ArgumentMatchers.eq;
20 import static org.mockito.Mockito.*;
21 import static org.mockito.Mockito.reset;
22 import static org.mockito.Mockito.times;
23 import static org.mockito.Mockito.verify;
24 import static org.mockito.Mockito.when;
26 import java.util.Hashtable;
27 import java.util.List;
29 import java.util.stream.Stream;
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.junit.jupiter.api.BeforeEach;
33 import org.junit.jupiter.api.Test;
34 import org.junit.jupiter.api.extension.ExtendWith;
35 import org.junit.jupiter.params.ParameterizedTest;
36 import org.junit.jupiter.params.provider.Arguments;
37 import org.junit.jupiter.params.provider.MethodSource;
38 import org.mockito.Mock;
39 import org.mockito.Mockito;
40 import org.mockito.junit.jupiter.MockitoExtension;
41 import org.mockito.junit.jupiter.MockitoSettings;
42 import org.mockito.quality.Strictness;
43 import org.openhab.core.config.core.Configuration;
44 import org.openhab.core.i18n.UnitProvider;
45 import org.openhab.core.internal.i18n.I18nProviderImpl;
46 import org.openhab.core.items.GenericItem;
47 import org.openhab.core.items.Item;
48 import org.openhab.core.items.ItemNotFoundException;
49 import org.openhab.core.items.ItemRegistry;
50 import org.openhab.core.library.items.*;
51 import org.openhab.core.library.types.*;
52 import org.openhab.core.library.unit.SIUnits;
53 import org.openhab.core.thing.link.ItemChannelLink;
54 import org.openhab.core.thing.profiles.ProfileCallback;
55 import org.openhab.core.thing.profiles.ProfileContext;
56 import org.openhab.core.types.State;
57 import org.openhab.core.types.UnDefType;
58 import org.osgi.framework.BundleContext;
59 import org.osgi.service.component.ComponentContext;
62 * Basic unit tests for {@link StateFilterProfile}.
64 * @author Arne Seime - Initial contribution
66 @ExtendWith(MockitoExtension.class)
67 @MockitoSettings(strictness = Strictness.WARN)
69 public class StateFilterProfileTest {
71 private @Mock @NonNullByDefault({}) ProfileCallback mockCallback;
72 private @Mock @NonNullByDefault({}) ProfileContext mockContext;
73 private @Mock @NonNullByDefault({}) ItemRegistry mockItemRegistry;
74 private @Mock @NonNullByDefault({}) ItemChannelLink mockItemChannelLink;
76 private static final UnitProvider UNIT_PROVIDER;
79 ComponentContext context = Mockito.mock(ComponentContext.class);
80 BundleContext bundleContext = Mockito.mock(BundleContext.class);
81 Hashtable<String, Object> properties = new Hashtable<>();
82 properties.put("measurementSystem", SIUnits.MEASUREMENT_SYSTEM_NAME);
83 when(context.getProperties()).thenReturn(properties);
84 when(context.getBundleContext()).thenReturn(bundleContext);
85 UNIT_PROVIDER = new I18nProviderImpl(context);
89 public void setup() throws ItemNotFoundException {
92 reset(mockItemChannelLink);
93 when(mockCallback.getItemChannelLink()).thenReturn(mockItemChannelLink);
94 when(mockItemRegistry.getItem("")).thenThrow(ItemNotFoundException.class);
98 public void testNoConditions() {
99 when(mockContext.getConfiguration()).thenReturn(new Configuration(Map.of("conditions", "")));
100 StateFilterProfile profile = new StateFilterProfile(mockCallback, mockContext, mockItemRegistry);
102 State expectation = OnOffType.ON;
103 profile.onStateUpdateFromHandler(expectation);
104 verify(mockCallback, times(0)).sendUpdate(eq(expectation));
108 public void testMalformedConditions() {
109 when(mockContext.getConfiguration()).thenReturn(new Configuration(Map.of("conditions", "ItemName invalid")));
110 StateFilterProfile profile = new StateFilterProfile(mockCallback, mockContext, mockItemRegistry);
112 State expectation = OnOffType.ON;
113 profile.onStateUpdateFromHandler(expectation);
114 verify(mockCallback, times(0)).sendUpdate(eq(expectation));
118 public void testInvalidComparatorConditions() throws ItemNotFoundException {
119 when(mockContext.getConfiguration()).thenReturn(new Configuration(Map.of("conditions", "ItemName is Value")));
120 StateFilterProfile profile = new StateFilterProfile(mockCallback, mockContext, mockItemRegistry);
121 when(mockItemRegistry.getItem("ItemName")).thenReturn(stringItemWithState("ItemName", "Value"));
123 State expectation = OnOffType.ON;
124 profile.onStateUpdateFromHandler(expectation);
125 verify(mockCallback, times(0)).sendUpdate(eq(expectation));
129 public void testInvalidItemConditions() throws ItemNotFoundException {
130 when(mockContext.getConfiguration()).thenReturn(new Configuration(Map.of("conditions", "ItemName eq Value")));
131 StateFilterProfile profile = new StateFilterProfile(mockCallback, mockContext, mockItemRegistry);
133 when(mockItemRegistry.getItem(any())).thenThrow(ItemNotFoundException.class);
134 State expectation = OnOffType.ON;
135 profile.onStateUpdateFromHandler(expectation);
136 verify(mockCallback, times(0)).sendUpdate(eq(expectation));
140 public void testInvalidMultipleConditions() throws ItemNotFoundException {
141 when(mockContext.getConfiguration())
142 .thenReturn(new Configuration(Map.of("conditions", "ItemName eq Value,itemname invalid")));
143 StateFilterProfile profile = new StateFilterProfile(mockCallback, mockContext, mockItemRegistry);
144 when(mockItemRegistry.getItem(any())).thenThrow(ItemNotFoundException.class);
146 State expectation = OnOffType.ON;
147 profile.onStateUpdateFromHandler(expectation);
148 verify(mockCallback, times(0)).sendUpdate(eq(expectation));
152 public void testSingleConditionMatch() throws ItemNotFoundException {
153 when(mockContext.getConfiguration()).thenReturn(new Configuration(Map.of("conditions", "ItemName eq 'Value'")));
154 when(mockItemRegistry.getItem("ItemName")).thenReturn(stringItemWithState("ItemName", "Value"));
156 StateFilterProfile profile = new StateFilterProfile(mockCallback, mockContext, mockItemRegistry);
158 State expectation = new StringType("NewValue");
159 profile.onStateUpdateFromHandler(expectation);
160 verify(mockCallback, times(1)).sendUpdate(eq(expectation));
164 public void testSingleConditionMatchQuoted() throws ItemNotFoundException {
165 when(mockContext.getConfiguration()).thenReturn(new Configuration(Map.of("conditions", "ItemName eq 'Value'")));
166 when(mockItemRegistry.getItem("ItemName")).thenReturn(stringItemWithState("ItemName", "Value"));
168 StateFilterProfile profile = new StateFilterProfile(mockCallback, mockContext, mockItemRegistry);
170 State expectation = new StringType("NewValue");
171 profile.onStateUpdateFromHandler(expectation);
172 verify(mockCallback, times(1)).sendUpdate(eq(expectation));
175 private Item stringItemWithState(String itemName, String value) {
176 StringItem item = new StringItem(itemName);
177 item.setState(new StringType(value));
181 private Item numberItemWithState(String itemType, String itemName, State value) {
182 NumberItem item = new NumberItem(itemType, itemName, null);
183 item.setState(value);
188 public void testMultipleCondition_AllMatch() throws ItemNotFoundException {
189 when(mockContext.getConfiguration())
190 .thenReturn(new Configuration(Map.of("conditions", "ItemName eq 'Value', ItemName2 eq 'Value2'")));
191 when(mockItemRegistry.getItem("ItemName")).thenReturn(stringItemWithState("ItemName", "Value"));
192 when(mockItemRegistry.getItem("ItemName2")).thenReturn(stringItemWithState("ItemName2", "Value2"));
194 StateFilterProfile profile = new StateFilterProfile(mockCallback, mockContext, mockItemRegistry);
196 State expectation = new StringType("NewValue");
197 profile.onStateUpdateFromHandler(expectation);
198 verify(mockCallback, times(1)).sendUpdate(eq(expectation));
202 public void testMultipleCondition_SingleMatch() throws ItemNotFoundException {
203 when(mockContext.getConfiguration())
204 .thenReturn(new Configuration(Map.of("conditions", "ItemName eq Value, ItemName2 eq Value2")));
205 when(mockItemRegistry.getItem("ItemName")).thenReturn(stringItemWithState("ItemName", "Value"));
206 when(mockItemRegistry.getItem("ItemName2")).thenThrow(ItemNotFoundException.class);
208 StateFilterProfile profile = new StateFilterProfile(mockCallback, mockContext, mockItemRegistry);
210 State expectation = new StringType("NewValue");
211 profile.onStateUpdateFromHandler(expectation);
212 verify(mockCallback, times(0)).sendUpdate(eq(expectation));
216 public void testFailingConditionWithMismatchState() throws ItemNotFoundException {
217 when(mockContext.getConfiguration())
218 .thenReturn(new Configuration(Map.of("conditions", "ItemName eq Value", "mismatchState", "UNDEF")));
219 when(mockContext.getAcceptedDataTypes()).thenReturn(List.of(UnDefType.class, StringType.class));
220 when(mockItemRegistry.getItem("ItemName")).thenReturn(stringItemWithState("ItemName", "Mismatch"));
222 StateFilterProfile profile = new StateFilterProfile(mockCallback, mockContext, mockItemRegistry);
224 profile.onStateUpdateFromHandler(new StringType("ToBeDiscarded"));
225 verify(mockCallback, times(1)).sendUpdate(eq(UnDefType.UNDEF));
229 public void testFailingConditionWithMismatchStateQuoted() throws ItemNotFoundException {
230 when(mockContext.getConfiguration())
231 .thenReturn(new Configuration(Map.of("conditions", "ItemName eq Value", "mismatchState", "'UNDEF'")));
232 when(mockContext.getAcceptedDataTypes()).thenReturn(List.of(UnDefType.class, StringType.class));
233 when(mockItemRegistry.getItem("ItemName")).thenReturn(stringItemWithState("ItemName", "Mismatch"));
235 StateFilterProfile profile = new StateFilterProfile(mockCallback, mockContext, mockItemRegistry);
237 profile.onStateUpdateFromHandler(new StringType("ToBeDiscarded"));
238 verify(mockCallback, times(1)).sendUpdate(eq(new StringType("UNDEF")));
242 void testParseStateNonQuotes() {
243 List<Class<? extends State>> acceptedDataTypes = List.of(UnDefType.class, OnOffType.class, StringType.class);
245 when(mockContext.getAcceptedDataTypes()).thenReturn(acceptedDataTypes);
246 when(mockContext.getConfiguration()).thenReturn(new Configuration(Map.of("conditions", "")));
248 StateFilterProfile profile = new StateFilterProfile(mockCallback, mockContext, mockItemRegistry);
249 assertEquals(UnDefType.UNDEF, profile.parseState("UNDEF", acceptedDataTypes));
250 assertEquals(new StringType("UNDEF"), profile.parseState("'UNDEF'", acceptedDataTypes));
251 assertEquals(OnOffType.ON, profile.parseState("ON", acceptedDataTypes));
252 assertEquals(new StringType("ON"), profile.parseState("'ON'", acceptedDataTypes));
255 public static Stream<Arguments> testComparingItemWithValue() {
256 NumberItem powerItem = new NumberItem("Number:Power", "ItemName", UNIT_PROVIDER);
257 NumberItem decimalItem = new NumberItem("ItemName");
258 StringItem stringItem = new StringItem("ItemName");
259 SwitchItem switchItem = new SwitchItem("ItemName");
260 DimmerItem dimmerItem = new DimmerItem("ItemName");
261 ContactItem contactItem = new ContactItem("ItemName");
262 RollershutterItem rollershutterItem = new RollershutterItem("ItemName");
264 QuantityType q_1500W = QuantityType.valueOf("1500 W");
265 DecimalType d_1500 = DecimalType.valueOf("1500");
266 StringType s_foo = StringType.valueOf("foo");
267 StringType s_NULL = StringType.valueOf("NULL");
268 StringType s_UNDEF = StringType.valueOf("UNDEF");
269 StringType s_OPEN = StringType.valueOf("OPEN");
272 // We should be able to check item state is/isn't UNDEF/NULL
274 // First, when the item state is actually an UnDefType
275 // An unquoted value UNDEF/NULL should be treated as an UnDefType
276 // Only equality comparisons against the matching UnDefType will return true
277 // Any other comparisons should return false
278 Arguments.of(stringItem, UnDefType.UNDEF, "==", "UNDEF", true), //
279 Arguments.of(dimmerItem, UnDefType.UNDEF, "==", "UNDEF", true), //
280 Arguments.of(dimmerItem, UnDefType.NULL, "==", "NULL", true), //
281 Arguments.of(dimmerItem, UnDefType.NULL, "==", "UNDEF", false), //
282 Arguments.of(decimalItem, UnDefType.NULL, ">", "10", false), //
283 Arguments.of(decimalItem, UnDefType.NULL, "<", "10", false), //
284 Arguments.of(decimalItem, UnDefType.NULL, "==", "10", false), //
285 Arguments.of(decimalItem, UnDefType.NULL, ">=", "10", false), //
286 Arguments.of(decimalItem, UnDefType.NULL, "<=", "10", false), //
288 // A quoted value (String) isn't UnDefType
289 Arguments.of(stringItem, UnDefType.UNDEF, "==", "'UNDEF'", false), //
290 Arguments.of(stringItem, UnDefType.UNDEF, "!=", "'UNDEF'", true), //
291 Arguments.of(stringItem, UnDefType.NULL, "==", "'NULL'", false), //
292 Arguments.of(stringItem, UnDefType.NULL, "!=", "'NULL'", true), //
294 // When the item state is not an UnDefType
295 // UnDefType is special. When unquoted and comparing against a StringItem,
296 // don't treat it as a string
297 Arguments.of(stringItem, s_NULL, "==", "'NULL'", true), // Comparing String to String
298 Arguments.of(stringItem, s_NULL, "==", "NULL", false), // String state != UnDefType
299 Arguments.of(stringItem, s_NULL, "!=", "NULL", true), //
300 Arguments.of(stringItem, s_UNDEF, "==", "'UNDEF'", true), // Comparing String to String
301 Arguments.of(stringItem, s_UNDEF, "==", "UNDEF", false), // String state != UnDefType
302 Arguments.of(stringItem, s_UNDEF, "!=", "UNDEF", true), //
304 Arguments.of(dimmerItem, PercentType.HUNDRED, "==", "UNDEF", false), //
305 Arguments.of(dimmerItem, PercentType.HUNDRED, "!=", "UNDEF", true), //
306 Arguments.of(dimmerItem, PercentType.HUNDRED, "==", "NULL", false), //
307 Arguments.of(dimmerItem, PercentType.HUNDRED, "!=", "NULL", true), //
309 // Check for OPEN/CLOSED
310 Arguments.of(contactItem, OpenClosedType.OPEN, "==", "OPEN", true), //
311 Arguments.of(contactItem, OpenClosedType.OPEN, "!=", "'OPEN'", true), // String != Enum
312 Arguments.of(contactItem, OpenClosedType.OPEN, "!=", "CLOSED", true), //
313 Arguments.of(contactItem, OpenClosedType.OPEN, "==", "CLOSED", false), //
314 Arguments.of(contactItem, OpenClosedType.CLOSED, "==", "CLOSED", true), //
315 Arguments.of(contactItem, OpenClosedType.CLOSED, "!=", "OPEN", true), //
318 Arguments.of(switchItem, OnOffType.ON, "==", "ON", true), //
319 Arguments.of(switchItem, OnOffType.ON, "!=", "ON", false), //
320 Arguments.of(switchItem, OnOffType.ON, "!=", "OFF", true), //
321 Arguments.of(switchItem, OnOffType.ON, "!=", "UNDEF", true), //
322 Arguments.of(switchItem, UnDefType.UNDEF, "==", "UNDEF", true), //
323 Arguments.of(switchItem, OnOffType.ON, "==", "'ON'", false), // it's not a string
324 Arguments.of(switchItem, OnOffType.ON, "!=", "'ON'", true), // incompatible types
326 // Enum types != String
327 Arguments.of(contactItem, OpenClosedType.OPEN, "==", "'OPEN'", false), //
328 Arguments.of(contactItem, OpenClosedType.OPEN, "!=", "'CLOSED'", true), //
329 Arguments.of(contactItem, OpenClosedType.OPEN, "!=", "'OPEN'", true), //
330 Arguments.of(contactItem, OpenClosedType.OPEN, "==", "'CLOSED'", false), //
331 Arguments.of(contactItem, OpenClosedType.CLOSED, "==", "'CLOSED'", false), //
332 Arguments.of(contactItem, OpenClosedType.CLOSED, "!=", "'CLOSED'", true), //
334 // non UnDefType checks
335 // String constants must be quoted
336 Arguments.of(stringItem, s_foo, "==", "'foo'", true), //
337 Arguments.of(stringItem, s_foo, "==", "foo", false), //
338 Arguments.of(stringItem, s_foo, "!=", "foo", true), // not quoted -> not a string
339 Arguments.of(stringItem, s_foo, "<>", "foo", true), //
340 Arguments.of(stringItem, s_foo, " <>", "foo", true), //
341 Arguments.of(stringItem, s_foo, "<> ", "foo", true), //
342 Arguments.of(stringItem, s_foo, " <> ", "foo", true), //
343 Arguments.of(stringItem, s_foo, "!=", "'foo'", false), //
344 Arguments.of(stringItem, s_foo, "<>", "'foo'", false), //
345 Arguments.of(stringItem, s_foo, " <>", "'foo'", false), //
347 Arguments.of(dimmerItem, PercentType.HUNDRED, "==", "100", true), //
348 Arguments.of(dimmerItem, PercentType.HUNDRED, ">=", "100", true), //
349 Arguments.of(dimmerItem, PercentType.HUNDRED, ">", "50", true), //
350 Arguments.of(dimmerItem, PercentType.HUNDRED, ">=", "50", true), //
351 Arguments.of(dimmerItem, PercentType.ZERO, "<", "50", true), //
352 Arguments.of(dimmerItem, PercentType.ZERO, ">=", "50", false), //
353 Arguments.of(dimmerItem, PercentType.ZERO, ">=", "0", true), //
354 Arguments.of(dimmerItem, PercentType.ZERO, "<", "0", false), //
355 Arguments.of(dimmerItem, PercentType.ZERO, "<=", "0", true), //
357 // Numeric vs Strings aren't comparable
358 Arguments.of(rollershutterItem, PercentType.HUNDRED, "==", "'100'", false), //
359 Arguments.of(rollershutterItem, PercentType.HUNDRED, "!=", "'100'", true), //
360 Arguments.of(rollershutterItem, PercentType.HUNDRED, ">", "'10'", false), //
361 Arguments.of(powerItem, q_1500W, "==", "'1500 W'", false), // QuantityType vs String => fail
362 Arguments.of(decimalItem, d_1500, "==", "'1500'", false), //
364 // Compatible type castings are supported
365 Arguments.of(dimmerItem, PercentType.ZERO, "==", "OFF", true), //
366 Arguments.of(dimmerItem, PercentType.ZERO, "==", "ON", false), //
367 Arguments.of(dimmerItem, PercentType.ZERO, "!=", "ON", true), //
368 Arguments.of(dimmerItem, PercentType.ZERO, "!=", "OFF", false), //
369 Arguments.of(dimmerItem, PercentType.HUNDRED, "==", "ON", true), //
370 Arguments.of(dimmerItem, PercentType.HUNDRED, "==", "OFF", false), //
371 Arguments.of(dimmerItem, PercentType.HUNDRED, "!=", "ON", false), //
372 Arguments.of(dimmerItem, PercentType.HUNDRED, "!=", "OFF", true), //
374 // UpDownType gets converted to PercentType for comparison
375 Arguments.of(rollershutterItem, PercentType.HUNDRED, "==", "DOWN", true), //
376 Arguments.of(rollershutterItem, PercentType.HUNDRED, "==", "UP", false), //
377 Arguments.of(rollershutterItem, PercentType.HUNDRED, "!=", "UP", true), //
378 Arguments.of(rollershutterItem, PercentType.ZERO, "==", "UP", true), //
379 Arguments.of(rollershutterItem, PercentType.ZERO, "!=", "DOWN", true), //
381 Arguments.of(decimalItem, d_1500, " eq ", "1500", true), //
382 Arguments.of(decimalItem, d_1500, " eq ", "1500", true), //
383 Arguments.of(decimalItem, d_1500, "==", "1500", true), //
384 Arguments.of(decimalItem, d_1500, " ==", "1500", true), //
385 Arguments.of(decimalItem, d_1500, "== ", "1500", true), //
386 Arguments.of(decimalItem, d_1500, " == ", "1500", true), //
388 Arguments.of(powerItem, q_1500W, " eq ", "1500", false), // no unit => fail
389 Arguments.of(powerItem, q_1500W, "==", "1500", false), // no unit => fail
390 Arguments.of(powerItem, q_1500W, " eq ", "1500 cm", false), // wrong unit
391 Arguments.of(powerItem, q_1500W, "==", "1500 cm", false), // wrong unit
393 Arguments.of(powerItem, q_1500W, " eq ", "1500 W", true), //
394 Arguments.of(powerItem, q_1500W, " eq ", "1.5 kW", true), //
395 Arguments.of(powerItem, q_1500W, " eq ", "2 kW", false), //
396 Arguments.of(powerItem, q_1500W, "==", "1500 W", true), //
397 Arguments.of(powerItem, q_1500W, "==", "1.5 kW", true), //
398 Arguments.of(powerItem, q_1500W, "==", "2 kW", false), //
400 Arguments.of(powerItem, q_1500W, " neq ", "500 W", true), //
401 Arguments.of(powerItem, q_1500W, " neq ", "1500", true), // Not the same type, so not equal
402 Arguments.of(powerItem, q_1500W, " neq ", "1500 W", false), //
403 Arguments.of(powerItem, q_1500W, " neq ", "1.5 kW", false), //
404 Arguments.of(powerItem, q_1500W, "!=", "500 W", true), //
405 Arguments.of(powerItem, q_1500W, "!=", "1500", true), // not the same type
406 Arguments.of(powerItem, q_1500W, "!=", "1500 W", false), //
407 Arguments.of(powerItem, q_1500W, "!=", "1.5 kW", false), //
409 Arguments.of(powerItem, q_1500W, " GT ", "100 W", true), //
410 Arguments.of(powerItem, q_1500W, " GT ", "1 kW", true), //
411 Arguments.of(powerItem, q_1500W, " GT ", "2 kW", false), //
412 Arguments.of(powerItem, q_1500W, ">", "100 W", true), //
413 Arguments.of(powerItem, q_1500W, ">", "1 kW", true), //
414 Arguments.of(powerItem, q_1500W, ">", "2 kW", false), //
415 Arguments.of(powerItem, q_1500W, " GTE ", "1500 W", true), //
416 Arguments.of(powerItem, q_1500W, " GTE ", "1 kW", true), //
417 Arguments.of(powerItem, q_1500W, " GTE ", "1.5 kW", true), //
418 Arguments.of(powerItem, q_1500W, " GTE ", "2 kW", false), //
419 Arguments.of(powerItem, q_1500W, " GTE ", "2000 mW", true), //
420 Arguments.of(powerItem, q_1500W, " GTE ", "20", false), // no unit
421 Arguments.of(powerItem, q_1500W, ">=", "1.5 kW", true), //
422 Arguments.of(powerItem, q_1500W, ">=", "2 kW", false), //
423 Arguments.of(powerItem, q_1500W, " LT ", "2 kW", true), //
424 Arguments.of(powerItem, q_1500W, "<", "2 kW", true), //
425 Arguments.of(powerItem, q_1500W, " LTE ", "2 kW", true), //
426 Arguments.of(powerItem, q_1500W, "<=", "2 kW", true), //
427 Arguments.of(powerItem, q_1500W, "<=", "1 kW", false), //
428 Arguments.of(powerItem, q_1500W, " LTE ", "1.5 kW", true), //
429 Arguments.of(powerItem, q_1500W, "<=", "1.5 kW", true) //
435 public void testComparingItemWithValue(GenericItem item, State state, String operator, String value,
436 boolean expected) throws ItemNotFoundException {
437 String itemName = item.getName();
438 item.setState(state);
440 when(mockContext.getConfiguration())
441 .thenReturn(new Configuration(Map.of("conditions", itemName + operator + value)));
442 when(mockItemRegistry.getItem(itemName)).thenReturn(item);
444 StateFilterProfile profile = new StateFilterProfile(mockCallback, mockContext, mockItemRegistry);
446 State inputData = new StringType("NewValue");
447 profile.onStateUpdateFromHandler(inputData);
448 verify(mockCallback, times(expected ? 1 : 0)).sendUpdate(eq(inputData));
451 public static Stream<Arguments> testComparingItemWithOtherItem() {
452 NumberItem powerItem = new NumberItem("Number:Power", "powerItem", UNIT_PROVIDER);
453 NumberItem powerItem2 = new NumberItem("Number:Power", "powerItem2", UNIT_PROVIDER);
454 NumberItem decimalItem = new NumberItem("decimalItem");
455 NumberItem decimalItem2 = new NumberItem("decimalItem2");
456 StringItem stringItem = new StringItem("stringItem");
457 StringItem stringItem2 = new StringItem("stringItem2");
458 ContactItem contactItem = new ContactItem("contactItem");
459 ContactItem contactItem2 = new ContactItem("contactItem2");
461 QuantityType q_1500W = QuantityType.valueOf("1500 W");
462 QuantityType q_1_5kW = QuantityType.valueOf("1.5 kW");
463 QuantityType q_10kW = QuantityType.valueOf("10 kW");
465 DecimalType d_1500 = DecimalType.valueOf("1500");
466 DecimalType d_2000 = DecimalType.valueOf("2000");
467 StringType s_1500 = StringType.valueOf("1500");
468 StringType s_foo = StringType.valueOf("foo");
469 StringType s_NULL = StringType.valueOf("NULL");
472 Arguments.of(stringItem, s_foo, "==", stringItem2, s_foo, true), //
473 Arguments.of(stringItem, s_foo, "!=", stringItem2, s_foo, false), //
474 Arguments.of(stringItem, s_foo, "==", stringItem2, s_NULL, false), //
475 Arguments.of(stringItem, s_foo, "!=", stringItem2, s_NULL, true), //
477 Arguments.of(decimalItem, d_1500, "==", decimalItem2, d_1500, true), //
478 Arguments.of(decimalItem, d_1500, "==", decimalItem2, d_1500, true), //
480 // UNDEF/NULL are equals regardless of item type
481 Arguments.of(decimalItem, UnDefType.UNDEF, "==", stringItem, UnDefType.UNDEF, true), //
482 Arguments.of(decimalItem, UnDefType.NULL, "==", stringItem, UnDefType.NULL, true), //
483 Arguments.of(decimalItem, UnDefType.NULL, "==", stringItem, UnDefType.UNDEF, false), //
485 Arguments.of(contactItem, OpenClosedType.OPEN, "==", contactItem2, OpenClosedType.OPEN, true), //
486 Arguments.of(contactItem, OpenClosedType.OPEN, "==", contactItem2, OpenClosedType.CLOSED, false), //
488 Arguments.of(decimalItem, d_1500, "==", decimalItem2, d_1500, true), //
489 Arguments.of(decimalItem, d_1500, "<", decimalItem2, d_2000, true), //
490 Arguments.of(decimalItem, d_1500, ">", decimalItem2, d_2000, false), //
491 Arguments.of(decimalItem, d_1500, ">", stringItem, s_1500, false), //
492 Arguments.of(powerItem, q_1500W, "<", powerItem2, q_10kW, true), //
493 Arguments.of(powerItem, q_1500W, ">", powerItem2, q_10kW, false), //
494 Arguments.of(powerItem, q_1500W, "==", powerItem2, q_1_5kW, true), //
495 Arguments.of(powerItem, q_1500W, ">=", powerItem2, q_1_5kW, true), //
496 Arguments.of(powerItem, q_1500W, ">", powerItem2, q_1_5kW, false), //
498 // Incompatible types
499 Arguments.of(decimalItem, d_1500, "==", stringItem, s_1500, false), //
500 Arguments.of(powerItem, q_1500W, "==", decimalItem, d_1500, false), // DecimalType != QuantityType
501 Arguments.of(decimalItem, d_1500, "==", powerItem, q_1500W, false) //
507 public void testComparingItemWithOtherItem(GenericItem item, State state, String operator, GenericItem item2,
508 State state2, boolean expected) throws ItemNotFoundException {
509 String itemName = item.getName();
510 item.setState(state);
512 String itemName2 = item2.getName();
513 item2.setState(state2);
515 if (item.equals(item2)) {
517 // When using the same items, it doesn't make sense for their states to be different
518 assertEquals(state, state2);
521 when(mockContext.getConfiguration())
522 .thenReturn(new Configuration(Map.of("conditions", itemName + operator + itemName2)));
523 when(mockItemRegistry.getItem(itemName)).thenReturn(item);
524 when(mockItemRegistry.getItem(itemName2)).thenReturn(item2);
526 StateFilterProfile profile = new StateFilterProfile(mockCallback, mockContext, mockItemRegistry);
528 State inputData = new StringType("NewValue");
529 profile.onStateUpdateFromHandler(inputData);
530 verify(mockCallback, times(expected ? 1 : 0)).sendUpdate(eq(inputData));
533 public static Stream<Arguments> testComparingInputStateWithValue() {
534 NumberItem powerItem = new NumberItem("Number:Power", "ItemName", UNIT_PROVIDER);
535 NumberItem decimalItem = new NumberItem("ItemName");
536 StringItem stringItem = new StringItem("ItemName");
537 DimmerItem dimmerItem = new DimmerItem("ItemName");
539 QuantityType q_1500W = QuantityType.valueOf("1500 W");
540 DecimalType d_1500 = DecimalType.valueOf("1500");
541 StringType s_foo = StringType.valueOf("foo");
544 // We should be able to check that input state is/isn't UNDEF/NULL
546 // First, when the input state is actually an UnDefType
547 // An unquoted value UNDEF/NULL should be treated as an UnDefType
548 Arguments.of(stringItem, UnDefType.UNDEF, "==", "UNDEF", true), //
549 Arguments.of(dimmerItem, UnDefType.NULL, "==", "NULL", true), //
550 Arguments.of(dimmerItem, UnDefType.NULL, "==", "UNDEF", false), //
552 // A quoted value (String) isn't UnDefType
553 Arguments.of(stringItem, UnDefType.UNDEF, "==", "'UNDEF'", false), //
554 Arguments.of(stringItem, UnDefType.UNDEF, "!=", "'UNDEF'", true), //
555 Arguments.of(stringItem, UnDefType.NULL, "==", "'NULL'", false), //
556 Arguments.of(stringItem, UnDefType.NULL, "!=", "'NULL'", true), //
558 // String values must be quoted
559 Arguments.of(stringItem, s_foo, "==", "'foo'", true), //
560 Arguments.of(stringItem, s_foo, "!=", "'foo'", false), //
561 Arguments.of(stringItem, s_foo, "==", "'bar'", false), //
562 // Unquoted string values are not compatible
563 // always returns false
564 Arguments.of(stringItem, s_foo, "==", "foo", false), //
565 Arguments.of(stringItem, s_foo, "!=", "foo", true), // not quoted -> not equal to string
567 Arguments.of(decimalItem, d_1500, "==", "1500", true), //
568 Arguments.of(decimalItem, d_1500, "!=", "1500", false), //
569 Arguments.of(decimalItem, d_1500, "==", "1000", false), //
570 Arguments.of(decimalItem, d_1500, "!=", "1000", true), //
571 Arguments.of(decimalItem, d_1500, ">", "1000", true), //
572 Arguments.of(decimalItem, d_1500, ">=", "1000", true), //
573 Arguments.of(decimalItem, d_1500, ">=", "1500", true), //
574 Arguments.of(decimalItem, d_1500, "<", "1600", true), //
575 Arguments.of(decimalItem, d_1500, "<=", "1600", true), //
576 Arguments.of(decimalItem, d_1500, "<", "1000", false), //
577 Arguments.of(decimalItem, d_1500, "<=", "1000", false), //
578 Arguments.of(decimalItem, d_1500, "<", "1500", false), //
579 Arguments.of(decimalItem, d_1500, "<=", "1500", true), //
581 // named operators - must have a trailing space
582 Arguments.of(decimalItem, d_1500, "LT ", "2000", true), //
583 Arguments.of(decimalItem, d_1500, "LTE ", "1500", true), //
584 Arguments.of(decimalItem, d_1500, " LTE ", "1500", true), //
585 Arguments.of(decimalItem, d_1500, " LTE ", "1500", true), //
587 Arguments.of(powerItem, q_1500W, "==", "1500 W", true), //
588 Arguments.of(powerItem, q_1500W, "==", "'1500 W'", false), // QuantityType != String
589 Arguments.of(powerItem, q_1500W, "==", "1.5 kW", true), //
590 Arguments.of(powerItem, q_1500W, ">", "2000 mW", true) //
596 public void testComparingInputStateWithValue(GenericItem linkedItem, State inputState, String operator,
597 String value, boolean expected) throws ItemNotFoundException {
599 String linkedItemName = linkedItem.getName();
601 when(mockContext.getConfiguration()).thenReturn(new Configuration(Map.of("conditions", operator + value)));
602 when(mockItemRegistry.getItem(linkedItemName)).thenReturn(linkedItem);
603 when(mockItemChannelLink.getItemName()).thenReturn(linkedItemName);
605 StateFilterProfile profile = new StateFilterProfile(mockCallback, mockContext, mockItemRegistry);
607 profile.onStateUpdateFromHandler(inputState);
608 verify(mockCallback, times(expected ? 1 : 0)).sendUpdate(eq(inputState));
612 @MethodSource("testComparingItemWithOtherItem")
613 public void testComparingInputStateWithItem(GenericItem linkedItem, State inputState, String operator,
614 GenericItem item, State state, boolean expected) throws ItemNotFoundException {
615 String linkedItemName = linkedItem.getName();
617 String itemName = item.getName();
618 item.setState(state);
620 when(mockContext.getConfiguration()).thenReturn(new Configuration(Map.of("conditions", operator + itemName)));
621 when(mockItemRegistry.getItem(itemName)).thenReturn(item);
622 when(mockItemRegistry.getItem(linkedItemName)).thenReturn(linkedItem);
623 when(mockItemChannelLink.getItemName()).thenReturn(linkedItemName);
625 StateFilterProfile profile = new StateFilterProfile(mockCallback, mockContext, mockItemRegistry);
627 profile.onStateUpdateFromHandler(inputState);
628 verify(mockCallback, times(expected ? 1 : 0)).sendUpdate(eq(inputState));