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.binding.hue.internal.clip2;
15 import static org.junit.jupiter.api.Assertions.*;
17 import java.io.BufferedReader;
18 import java.io.FileReader;
19 import java.io.IOException;
20 import java.lang.reflect.Field;
21 import java.time.Duration;
22 import java.util.List;
23 import java.util.Optional;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.junit.jupiter.api.Test;
28 import org.openhab.binding.hue.internal.dto.clip2.ActionEntry;
29 import org.openhab.binding.hue.internal.dto.clip2.Alerts;
30 import org.openhab.binding.hue.internal.dto.clip2.Button;
31 import org.openhab.binding.hue.internal.dto.clip2.Dimming;
32 import org.openhab.binding.hue.internal.dto.clip2.Effects;
33 import org.openhab.binding.hue.internal.dto.clip2.Event;
34 import org.openhab.binding.hue.internal.dto.clip2.LightLevel;
35 import org.openhab.binding.hue.internal.dto.clip2.MetaData;
36 import org.openhab.binding.hue.internal.dto.clip2.MirekSchema;
37 import org.openhab.binding.hue.internal.dto.clip2.Motion;
38 import org.openhab.binding.hue.internal.dto.clip2.Power;
39 import org.openhab.binding.hue.internal.dto.clip2.ProductData;
40 import org.openhab.binding.hue.internal.dto.clip2.RelativeRotary;
41 import org.openhab.binding.hue.internal.dto.clip2.Resource;
42 import org.openhab.binding.hue.internal.dto.clip2.ResourceReference;
43 import org.openhab.binding.hue.internal.dto.clip2.Resources;
44 import org.openhab.binding.hue.internal.dto.clip2.Rotation;
45 import org.openhab.binding.hue.internal.dto.clip2.RotationEvent;
46 import org.openhab.binding.hue.internal.dto.clip2.Temperature;
47 import org.openhab.binding.hue.internal.dto.clip2.TimedEffects;
48 import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType;
49 import org.openhab.binding.hue.internal.dto.clip2.enums.Archetype;
50 import org.openhab.binding.hue.internal.dto.clip2.enums.BatteryStateType;
51 import org.openhab.binding.hue.internal.dto.clip2.enums.ButtonEventType;
52 import org.openhab.binding.hue.internal.dto.clip2.enums.DirectionType;
53 import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType;
54 import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType;
55 import org.openhab.binding.hue.internal.dto.clip2.enums.RotationEventType;
56 import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus;
57 import org.openhab.binding.hue.internal.dto.clip2.helper.Setters;
58 import org.openhab.core.library.types.DecimalType;
59 import org.openhab.core.library.types.HSBType;
60 import org.openhab.core.library.types.OnOffType;
61 import org.openhab.core.library.types.PercentType;
62 import org.openhab.core.library.types.QuantityType;
63 import org.openhab.core.library.types.StringType;
64 import org.openhab.core.types.State;
65 import org.openhab.core.types.UnDefType;
66 import org.openhab.core.util.ColorUtil;
68 import com.google.gson.Gson;
69 import com.google.gson.JsonElement;
70 import com.google.gson.JsonObject;
71 import com.google.gson.JsonParser;
72 import com.google.gson.JsonSyntaxException;
75 * JUnit test for CLIP 2 DTOs.
77 * @author Andrew Fiddian-Green - Initial contribution
82 private static final Gson GSON = new Gson();
83 private static final Double MINIMUM_DIMMING_LEVEL = Double.valueOf(12.34f);
86 * Load the test JSON payload string from a file
88 private String load(String fileName) {
89 try (FileReader file = new FileReader(String.format("src/test/resources/%s.json", fileName));
90 BufferedReader reader = new BufferedReader(file)) {
91 StringBuilder builder = new StringBuilder();
93 while ((line = reader.readLine()) != null) {
94 builder.append(line).append("\n");
96 return builder.toString();
97 } catch (IOException e) {
105 String json = load(ResourceType.BUTTON.name().toLowerCase());
106 Resources resources = GSON.fromJson(json, Resources.class);
107 assertNotNull(resources);
108 List<Resource> list = resources.getResources();
110 assertEquals(43, list.size());
111 Resource item = list.get(0);
112 assertEquals(ResourceType.BUTTON, item.getType());
113 Button button = item.getButton();
114 assertNotNull(button);
115 assertEquals(ButtonEventType.SHORT_RELEASE, button.getLastEvent());
120 String json = load(ResourceType.DEVICE.name().toLowerCase());
121 Resources resources = GSON.fromJson(json, Resources.class);
122 assertNotNull(resources);
123 List<Resource> list = resources.getResources();
125 assertEquals(34, list.size());
126 boolean itemFound = false;
127 for (Resource item : list) {
128 assertEquals(ResourceType.DEVICE, item.getType());
129 ProductData productData = item.getProductData();
130 assertNotNull(productData);
131 if (productData.getProductArchetype() == Archetype.BRIDGE_V2) {
133 assertEquals("BSB002", productData.getModelId());
134 assertEquals("Signify Netherlands B.V.", productData.getManufacturerName());
135 assertEquals("Philips hue", productData.getProductName());
136 assertNull(productData.getHardwarePlatformType());
137 assertTrue(productData.getCertified());
138 assertEquals("1.53.1953188020", productData.getSoftwareVersion());
142 assertTrue(itemFound);
146 void testDevicePower() {
147 String json = load(ResourceType.DEVICE_POWER.name().toLowerCase());
148 Resources resources = GSON.fromJson(json, Resources.class);
149 assertNotNull(resources);
150 List<Resource> list = resources.getResources();
152 assertEquals(16, list.size());
153 Resource item = list.get(0);
154 assertEquals(ResourceType.DEVICE_POWER, item.getType());
155 Power power = item.getPowerState();
156 assertNotNull(power);
157 assertEquals(60, power.getBatteryLevel());
158 assertEquals(BatteryStateType.NORMAL, power.getBatteryState());
162 void testGroupedLight() {
163 String json = load(ResourceType.GROUPED_LIGHT.name().toLowerCase());
164 Resources resources = GSON.fromJson(json, Resources.class);
165 assertNotNull(resources);
166 List<Resource> list = resources.getResources();
168 assertEquals(15, list.size());
170 for (Resource item : list) {
171 assertEquals(ResourceType.GROUPED_LIGHT, item.getType());
173 switch (item.getId()) {
174 case "db4fd630-3798-40de-b642-c1ef464bf770":
176 assertEquals(OnOffType.OFF, item.getOnOffState());
177 assertEquals(PercentType.ZERO, item.getBrightnessState());
178 alert = item.getAlerts();
179 assertNotNull(alert);
180 for (ActionType actionValue : alert.getActionValues()) {
181 assertEquals(ActionType.BREATHE, actionValue);
184 case "9228d710-3c54-4ae4-8c88-bfe57d8fd220":
186 assertEquals(OnOffType.ON, item.getOnOffState());
187 assertEquals(PercentType.HUNDRED, item.getBrightnessState());
188 alert = item.getAlerts();
189 assertNotNull(alert);
190 for (ActionType actionValue : alert.getActionValues()) {
191 assertEquals(ActionType.BREATHE, actionValue);
197 assertEquals(2, itemsFound);
202 String json = load(ResourceType.LIGHT.name().toLowerCase());
203 Resources resources = GSON.fromJson(json, Resources.class);
204 assertNotNull(resources);
205 List<Resource> list = resources.getResources();
207 assertEquals(17, list.size());
208 int itemFoundCount = 0;
209 for (Resource item : list) {
210 assertEquals(ResourceType.LIGHT, item.getType());
211 MetaData metaData = item.getMetaData();
212 assertNotNull(metaData);
213 String name = metaData.getName();
216 if (name.contains("Bay Window Lamp")) {
218 assertEquals(ResourceType.LIGHT, item.getType());
219 assertEquals(OnOffType.OFF, item.getOnOffState());
220 state = item.getBrightnessState();
221 assertTrue(state instanceof PercentType);
222 assertEquals(0, ((PercentType) state).doubleValue(), 0.1);
223 item.setOnOff(OnOffType.ON);
224 state = item.getBrightnessState();
225 assertTrue(state instanceof PercentType);
226 assertEquals(93.0, ((PercentType) state).doubleValue(), 0.1);
227 assertEquals(UnDefType.UNDEF, item.getColorTemperaturePercentState());
228 state = item.getColorState();
229 assertTrue(state instanceof HSBType);
230 double[] xy = ColorUtil.hsbToXY((HSBType) state);
231 assertEquals(0.6367, xy[0], 0.01); // note: rounding errors !!
232 assertEquals(0.3503, xy[1], 0.01); // note: rounding errors !!
233 assertEquals(item.getBrightnessState(), ((HSBType) state).getBrightness());
234 Alerts alert = item.getAlerts();
235 assertNotNull(alert);
236 for (ActionType actionValue : alert.getActionValues()) {
237 assertEquals(ActionType.BREATHE, actionValue);
240 if (name.contains("Table Lamp A")) {
242 assertEquals(ResourceType.LIGHT, item.getType());
243 assertEquals(OnOffType.OFF, item.getOnOffState());
244 state = item.getBrightnessState();
245 assertTrue(state instanceof PercentType);
246 assertEquals(0, ((PercentType) state).doubleValue(), 0.1);
247 item.setOnOff(OnOffType.ON);
248 state = item.getBrightnessState();
249 assertTrue(state instanceof PercentType);
250 assertEquals(56.7, ((PercentType) state).doubleValue(), 0.1);
251 MirekSchema mirekSchema = item.getMirekSchema();
252 assertNotNull(mirekSchema);
253 assertEquals(153, mirekSchema.getMirekMinimum());
254 assertEquals(454, mirekSchema.getMirekMaximum());
256 // test color temperature percent value on light's own scale
257 state = item.getColorTemperaturePercentState();
258 assertTrue(state instanceof PercentType);
259 assertEquals(96.3, ((PercentType) state).doubleValue(), 0.1);
260 state = item.getColorTemperatureAbsoluteState();
261 assertTrue(state instanceof QuantityType<?>);
262 assertEquals(2257.3, ((QuantityType<?>) state).doubleValue(), 0.1);
264 // test color temperature percent value on the default (full) scale
265 MirekSchema temp = item.getMirekSchema();
266 item.setMirekSchema(MirekSchema.DEFAULT_SCHEMA);
267 state = item.getColorTemperaturePercentState();
268 assertTrue(state instanceof PercentType);
269 assertEquals(83.6, ((PercentType) state).doubleValue(), 0.1);
270 state = item.getColorTemperatureAbsoluteState();
271 assertTrue(state instanceof QuantityType<?>);
272 assertEquals(2257.3, ((QuantityType<?>) state).doubleValue(), 0.1);
273 item.setMirekSchema(temp);
275 // change colour temperature percent to zero
276 Setters.setColorTemperaturePercent(item, PercentType.ZERO, null);
277 assertEquals(PercentType.ZERO, item.getColorTemperaturePercentState());
278 state = item.getColorTemperatureAbsoluteState();
279 assertTrue(state instanceof QuantityType<?>);
280 assertEquals(6535.9, ((QuantityType<?>) state).doubleValue(), 0.1);
282 // change colour temperature percent to 100
283 Setters.setColorTemperaturePercent(item, PercentType.HUNDRED, null);
284 assertEquals(PercentType.HUNDRED, item.getColorTemperaturePercentState());
285 state = item.getColorTemperatureAbsoluteState();
286 assertTrue(state instanceof QuantityType<?>);
287 assertEquals(2202.6, ((QuantityType<?>) state).doubleValue(), 0.1);
289 // change colour temperature kelvin to 4000 K
290 Setters.setColorTemperatureAbsolute(item, QuantityType.valueOf("4000 K"), null);
291 state = item.getColorTemperaturePercentState();
292 assertTrue(state instanceof PercentType);
293 assertEquals(32.2, ((PercentType) state).doubleValue(), 0.1);
294 assertEquals(QuantityType.valueOf("4000 K"), item.getColorTemperatureAbsoluteState());
296 assertEquals(UnDefType.NULL, item.getColorState());
297 Alerts alert = item.getAlerts();
298 assertNotNull(alert);
299 for (ActionType actionValue : alert.getActionValues()) {
300 assertEquals(ActionType.BREATHE, actionValue);
304 assertEquals(2, itemFoundCount);
308 void testLightLevel() {
309 String json = load(ResourceType.LIGHT_LEVEL.name().toLowerCase());
310 Resources resources = GSON.fromJson(json, Resources.class);
311 assertNotNull(resources);
312 List<Resource> list = resources.getResources();
314 assertEquals(1, list.size());
315 Resource item = list.get(0);
316 assertEquals(ResourceType.LIGHT_LEVEL, item.getType());
317 Boolean enabled = item.getEnabled();
318 assertNotNull(enabled);
320 LightLevel lightLevel = item.getLightLevel();
321 assertNotNull(lightLevel);
322 assertEquals(12725, lightLevel.getLightLevel());
323 assertTrue(lightLevel.isLightLevelValid());
327 void testRelativeRotary() {
328 String json = load(ResourceType.RELATIVE_ROTARY.name().toLowerCase());
329 Resources resources = GSON.fromJson(json, Resources.class);
330 assertNotNull(resources);
331 List<Resource> list = resources.getResources();
333 assertEquals(1, list.size());
334 Resource item = list.get(0);
335 assertEquals(ResourceType.RELATIVE_ROTARY, item.getType());
336 RelativeRotary relativeRotary = item.getRelativeRotary();
337 assertNotNull(relativeRotary);
338 RotationEvent rotationEvent = relativeRotary.getLastEvent();
339 assertNotNull(rotationEvent);
340 assertEquals(RotationEventType.REPEAT, rotationEvent.getAction());
341 Rotation rotation = rotationEvent.getRotation();
342 assertNotNull(rotation);
343 assertEquals(DirectionType.CLOCK_WISE, rotation.getDirection());
344 assertEquals(400, rotation.getDuration());
345 assertEquals(30, rotation.getSteps());
346 assertEquals(new DecimalType(30), relativeRotary.getStepsState());
347 assertEquals(new StringType(ButtonEventType.REPEAT.name()), relativeRotary.getActionState());
351 void testResourceMerging() {
352 // create resource one
353 Resource one = new Resource(ResourceType.LIGHT).setId("AARDVARK");
355 // preset the minimum dimming level
357 Dimming dimming = new Dimming().setMinimumDimmingLevel(MINIMUM_DIMMING_LEVEL);
358 Field dimming2 = one.getClass().getDeclaredField("dimming");
359 dimming2.setAccessible(true);
360 dimming2.set(one, dimming);
361 } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
364 Setters.setColorXy(one, HSBType.RED, null);
365 Setters.setDimming(one, PercentType.HUNDRED, null);
366 assertTrue(one.getColorState() instanceof HSBType);
367 assertEquals(PercentType.HUNDRED, one.getBrightnessState());
368 assertTrue(HSBType.RED.closeTo((HSBType) one.getColorState(), 0.01));
370 // switching off should change HSB and Brightness
371 one.setOnOff(OnOffType.OFF);
372 assertEquals(0, ((HSBType) one.getColorState()).getBrightness().doubleValue(), 0.01);
373 assertEquals(PercentType.ZERO, one.getBrightnessState());
374 one.setOnOff(OnOffType.ON);
376 // setting brightness to zero should change it to the minimum dimming level
377 Setters.setDimming(one, PercentType.ZERO, null);
378 assertEquals(MINIMUM_DIMMING_LEVEL, ((HSBType) one.getColorState()).getBrightness().doubleValue(), 0.01);
379 assertEquals(MINIMUM_DIMMING_LEVEL, ((PercentType) one.getBrightnessState()).doubleValue(), 0.01);
380 one.setOnOff(OnOffType.ON);
382 // null its Dimming field
384 Field dimming = one.getClass().getDeclaredField("dimming");
385 dimming.setAccessible(true);
386 dimming.set(one, null);
387 } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
391 // confirm that brightness is no longer valid, and therefore that color has also changed
392 assertEquals(UnDefType.NULL, one.getBrightnessState());
393 assertTrue(one.getColorState() instanceof HSBType);
394 assertTrue((new HSBType(DecimalType.ZERO, PercentType.HUNDRED, new PercentType(50)))
395 .closeTo((HSBType) one.getColorState(), 0.01));
397 PercentType testBrightness = new PercentType(42);
399 // create resource two
400 Resource two = new Resource(ResourceType.DEVICE).setId("ALLIGATOR");
402 Setters.setDimming(two, testBrightness, null);
403 assertEquals(UnDefType.NULL, two.getColorState());
404 assertEquals(testBrightness, two.getBrightnessState());
407 Setters.setResource(one, two);
409 // confirm that brightness and color are both once more valid
410 assertEquals("AARDVARK", one.getId());
411 assertEquals(ResourceType.LIGHT, one.getType());
412 assertEquals(testBrightness, one.getBrightnessState());
413 assertTrue(one.getColorState() instanceof HSBType);
414 assertTrue((new HSBType(DecimalType.ZERO, PercentType.HUNDRED, testBrightness))
415 .closeTo((HSBType) one.getColorState(), 0.01));
419 void testRoomGroup() {
420 String json = load(ResourceType.ROOM.name().toLowerCase());
421 Resources resources = GSON.fromJson(json, Resources.class);
422 assertNotNull(resources);
423 List<Resource> list = resources.getResources();
425 assertEquals(6, list.size());
426 Resource item = list.get(0);
427 assertEquals(ResourceType.ROOM, item.getType());
428 List<ResourceReference> children = item.getChildren();
429 assertEquals(2, children.size());
430 ResourceReference child = children.get(0);
431 assertNotNull(child);
432 assertEquals("0d47bd3d-d82b-4a21-893c-299bff18e22a", child.getId());
433 assertEquals(ResourceType.DEVICE, child.getType());
434 List<ResourceReference> services = item.getServiceReferences();
435 assertEquals(1, services.size());
436 ResourceReference service = services.get(0);
437 assertNotNull(service);
438 assertEquals("08947162-67be-4ed5-bfce-f42dade42416", service.getId());
439 assertEquals(ResourceType.GROUPED_LIGHT, service.getType());
444 String json = load(ResourceType.SCENE.name().toLowerCase());
445 Resources resources = GSON.fromJson(json, Resources.class);
446 assertNotNull(resources);
447 List<Resource> list = resources.getResources();
449 assertEquals(123, list.size());
450 Resource item = list.get(0);
451 List<ActionEntry> actions = item.getActions();
452 assertNotNull(actions);
453 assertEquals(3, actions.size());
454 ActionEntry actionEntry = actions.get(0);
455 assertNotNull(actionEntry);
456 Resource action = actionEntry.getAction();
457 assertNotNull(action);
458 assertEquals(OnOffType.ON, action.getOnOffState());
462 void testSmartScene() {
463 String json = load(ResourceType.SMART_SCENE.name().toLowerCase());
464 Resources resources = GSON.fromJson(json, Resources.class);
465 assertNotNull(resources);
466 List<Resource> list = resources.getResources();
468 assertEquals(1, list.size());
469 Resource item = list.get(0);
470 ResourceReference group = item.getGroup();
471 assertNotNull(group);
472 String groupId = group.getId();
473 assertNotNull(groupId);
474 assertFalse(groupId.isBlank());
475 ResourceType type = group.getType();
477 assertEquals(ResourceType.ROOM, type);
478 Optional<Boolean> state = item.getSmartSceneActive();
479 assertTrue(state.isPresent());
480 assertFalse(state.get());
484 void testSensor2Motion() {
485 String json = load(ResourceType.MOTION.name().toLowerCase());
486 Resources resources = GSON.fromJson(json, Resources.class);
487 assertNotNull(resources);
488 List<Resource> list = resources.getResources();
490 assertEquals(1, list.size());
491 Resource item = list.get(0);
492 assertEquals(ResourceType.MOTION, item.getType());
493 Boolean enabled = item.getEnabled();
494 assertNotNull(enabled);
496 Motion motion = item.getMotion();
497 assertNotNull(motion);
498 assertTrue(motion.isMotion());
499 assertTrue(motion.isMotionValid());
503 void testSetGetPureColors() {
504 Resource resource = new Resource(ResourceType.LIGHT);
505 assertNotNull(resource);
507 HSBType cyan = new HSBType("180,100,100");
508 HSBType yellow = new HSBType("60,100,100");
509 HSBType magenta = new HSBType("300,100,100");
511 for (HSBType color : Set.of(HSBType.WHITE, HSBType.RED, HSBType.GREEN, HSBType.BLUE, cyan, yellow, magenta)) {
512 Setters.setColorXy(resource, color, null);
513 State state = resource.getColorState();
514 assertTrue(state instanceof HSBType);
515 assertTrue(color.closeTo((HSBType) state, 0.01));
520 void testSseLightOrGroupEvent() {
521 String json = load("event");
522 List<Event> eventList = GSON.fromJson(json, Event.EVENT_LIST_TYPE);
523 assertNotNull(eventList);
524 assertEquals(3, eventList.size());
525 Event event = eventList.get(0);
526 List<Resource> resources = event.getData();
527 assertEquals(9, resources.size());
528 for (Resource r : resources) {
529 ResourceType type = r.getType();
530 assertTrue(ResourceType.LIGHT == type || ResourceType.GROUPED_LIGHT == type);
535 void testSseSceneEvent() {
536 String json = load("event");
537 List<Event> eventList = GSON.fromJson(json, Event.EVENT_LIST_TYPE);
538 assertNotNull(eventList);
539 assertEquals(3, eventList.size());
540 Event event = eventList.get(2);
541 List<Resource> resources = event.getData();
542 assertEquals(6, resources.size());
543 Resource resource = resources.get(1);
544 assertEquals(ResourceType.SCENE, resource.getType());
545 JsonObject status = resource.getStatus();
546 assertNotNull(status);
547 JsonElement active = status.get("active");
548 assertNotNull(active);
549 assertTrue(active.isJsonPrimitive());
550 assertEquals("inactive", active.getAsString());
551 Optional<Boolean> isActive = resource.getSceneActive();
552 assertTrue(isActive.isPresent());
553 assertEquals(Boolean.FALSE, isActive.get());
557 void testTemperature() {
558 String json = load(ResourceType.TEMPERATURE.name().toLowerCase());
559 Resources resources = GSON.fromJson(json, Resources.class);
560 assertNotNull(resources);
561 List<Resource> list = resources.getResources();
563 assertEquals(1, list.size());
564 Resource item = list.get(0);
565 assertEquals(ResourceType.TEMPERATURE, item.getType());
566 Temperature temperature = item.getTemperature();
567 assertNotNull(temperature);
568 assertEquals(17.2, temperature.getTemperature(), 0.1);
569 assertTrue(temperature.isTemperatureValid());
573 void testValidJson() {
574 for (ResourceType res : ResourceType.values()) {
575 if (!ResourceType.SSE_TYPES.contains(res)) {
577 String file = res.name().toLowerCase();
578 String json = load(file);
579 JsonElement jsonElement = JsonParser.parseString(json);
580 assertTrue(jsonElement.isJsonObject());
581 } catch (JsonSyntaxException e) {
589 void testZigbeeStatus() {
590 String json = load(ResourceType.ZIGBEE_CONNECTIVITY.name().toLowerCase());
591 Resources resources = GSON.fromJson(json, Resources.class);
592 assertNotNull(resources);
593 List<Resource> list = resources.getResources();
595 assertEquals(35, list.size());
596 Resource item = list.get(0);
597 assertEquals(ResourceType.ZIGBEE_CONNECTIVITY, item.getType());
598 ZigbeeStatus zigbeeStatus = item.getZigbeeStatus();
599 assertNotNull(zigbeeStatus);
600 assertEquals("Connected", zigbeeStatus.toString());
604 void testZoneGroup() {
605 String json = load(ResourceType.ZONE.name().toLowerCase());
606 Resources resources = GSON.fromJson(json, Resources.class);
607 assertNotNull(resources);
608 List<Resource> list = resources.getResources();
610 assertEquals(7, list.size());
611 Resource item = list.get(0);
612 assertEquals(ResourceType.ZONE, item.getType());
613 List<ResourceReference> children = item.getChildren();
614 assertEquals(1, children.size());
615 ResourceReference child = children.get(0);
616 assertNotNull(child);
617 assertEquals("bcad47a0-3f1f-498c-a8aa-3cf389965219", child.getId());
618 assertEquals(ResourceType.LIGHT, child.getType());
619 List<ResourceReference> services = item.getServiceReferences();
620 assertEquals(1, services.size());
621 ResourceReference service = services.get(0);
622 assertNotNull(service);
623 assertEquals("db4fd630-3798-40de-b642-c1ef464bf770", service.getId());
624 assertEquals(ResourceType.GROUPED_LIGHT, service.getType());
628 void testFixedEffectSetter() {
631 Effects resultEffect;
634 source = new Resource(ResourceType.LIGHT);
635 target = new Resource(ResourceType.LIGHT);
636 Setters.setResource(target, source);
637 assertNull(target.getFixedEffects());
639 // valid source fixed effects
640 source = new Resource(ResourceType.LIGHT).setFixedEffects(
641 new Effects().setStatusValues(List.of("NO_EFFECT", "SPARKLE", "CANDLE")).setEffect(EffectType.SPARKLE));
642 target = new Resource(ResourceType.LIGHT);
643 Setters.setResource(target, source);
644 resultEffect = target.getFixedEffects();
645 assertNotNull(resultEffect);
646 assertEquals(EffectType.SPARKLE, resultEffect.getEffect());
647 assertEquals(3, resultEffect.getStatusValues().size());
649 // valid but different source and target fixed effects
650 source = new Resource(ResourceType.LIGHT).setFixedEffects(
651 new Effects().setStatusValues(List.of("NO_EFFECT", "SPARKLE", "CANDLE")).setEffect(EffectType.SPARKLE));
652 target = new Resource(ResourceType.LIGHT).setFixedEffects(
653 new Effects().setStatusValues(List.of("NO_EFFECT", "FIRE")).setEffect(EffectType.FIRE));
654 Setters.setResource(target, source);
655 resultEffect = target.getFixedEffects();
656 assertNotNull(resultEffect);
657 assertNotEquals(EffectType.SPARKLE, resultEffect.getEffect());
658 assertEquals(3, resultEffect.getStatusValues().size());
660 // partly valid source fixed effects
661 source = new Resource(ResourceType.LIGHT).setFixedEffects(new Effects().setStatusValues(List.of("SPARKLE"))
662 .setEffect(EffectType.SPARKLE).setStatusValues(List.of()));
663 target = new Resource(ResourceType.LIGHT);
664 Setters.setResource(target, source);
665 resultEffect = target.getFixedEffects();
666 assertNotNull(resultEffect);
667 assertEquals(EffectType.SPARKLE, resultEffect.getEffect());
668 assertEquals(0, resultEffect.getStatusValues().size());
669 assertFalse(resultEffect.allows(EffectType.SPARKLE));
670 assertFalse(resultEffect.allows(EffectType.NO_EFFECT));
674 void testTimedEffectSetter() {
677 Effects resultEffect;
680 source = new Resource(ResourceType.LIGHT);
681 target = new Resource(ResourceType.LIGHT);
682 Setters.setResource(target, source);
683 assertNull(target.getTimedEffects());
685 // valid source timed effects
686 source = new Resource(ResourceType.LIGHT).setTimedEffects((TimedEffects) new TimedEffects()
687 .setStatusValues(List.of("NO_EFFECT", "SUNRISE")).setEffect(EffectType.NO_EFFECT));
688 target = new Resource(ResourceType.LIGHT);
689 Setters.setResource(target, source);
690 resultEffect = target.getTimedEffects();
691 assertNotNull(resultEffect);
692 assertEquals(EffectType.NO_EFFECT, resultEffect.getEffect());
693 assertEquals(2, resultEffect.getStatusValues().size());
695 // valid but different source and target timed effects
696 source = new Resource(ResourceType.LIGHT)
697 .setTimedEffects((TimedEffects) new TimedEffects().setDuration(Duration.ofMinutes(11))
698 .setStatusValues(List.of("NO_EFFECT", "SPARKLE", "CANDLE")).setEffect(EffectType.SPARKLE));
699 target = new Resource(ResourceType.LIGHT).setTimedEffects((TimedEffects) new TimedEffects()
700 .setStatusValues(List.of("NO_EFFECT", "FIRE")).setEffect(EffectType.FIRE));
701 Setters.setResource(target, source);
702 resultEffect = target.getTimedEffects();
703 assertNotNull(resultEffect);
704 assertNotEquals(EffectType.SPARKLE, resultEffect.getEffect());
705 assertEquals(3, resultEffect.getStatusValues().size());
706 assertTrue(resultEffect instanceof TimedEffects);
707 assertEquals(Duration.ofMinutes(11), ((TimedEffects) resultEffect).getDuration());
709 // partly valid source timed effects
710 source = new Resource(ResourceType.LIGHT).setTimedEffects((TimedEffects) new TimedEffects()
711 .setStatusValues(List.of("SUNRISE")).setEffect(EffectType.SUNRISE).setStatusValues(List.of()));
712 target = new Resource(ResourceType.LIGHT);
713 Setters.setResource(target, source);
714 resultEffect = target.getTimedEffects();
715 assertNotNull(resultEffect);
716 assertEquals(EffectType.SUNRISE, resultEffect.getEffect());
717 assertEquals(0, resultEffect.getStatusValues().size());
718 assertFalse(resultEffect.allows(EffectType.SPARKLE));
719 assertFalse(resultEffect.allows(EffectType.NO_EFFECT));
720 assertTrue(resultEffect instanceof TimedEffects);
721 assertNull(((TimedEffects) resultEffect).getDuration());
723 target.setTimedEffectsDuration(Duration.ofSeconds(22));
724 assertEquals(Duration.ofSeconds(22), ((TimedEffects) resultEffect).getDuration());
726 // source timed effect with duration
727 source = new Resource(ResourceType.LIGHT)
728 .setTimedEffects((TimedEffects) new TimedEffects().setDuration(Duration.ofMillis(44))
729 .setStatusValues(List.of("SUNRISE")).setEffect(EffectType.SUNRISE).setStatusValues(List.of()));
730 target = new Resource(ResourceType.LIGHT);
731 Setters.setResource(target, source);
732 resultEffect = target.getTimedEffects();
733 assertNotNull(resultEffect);
734 assertTrue(resultEffect instanceof TimedEffects);
735 assertEquals(Duration.ofMillis(44), ((TimedEffects) resultEffect).getDuration());