--- /dev/null
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.hue.internal.clip2;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.openhab.binding.hue.internal.api.dto.clip2.ColorTemperature;
+import org.openhab.binding.hue.internal.api.dto.clip2.Dimming;
+import org.openhab.binding.hue.internal.api.dto.clip2.Effects;
+import org.openhab.binding.hue.internal.api.dto.clip2.OnState;
+import org.openhab.binding.hue.internal.api.dto.clip2.Resource;
+import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType;
+import org.openhab.binding.hue.internal.api.dto.clip2.helper.Setters;
+import org.openhab.binding.hue.internal.exceptions.DTOPresentButEmptyException;
+
+/**
+ * Tests for {@link Setters}.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public class SettersTest {
+
+ /**
+ * Tests merging of on state and dimming for same resource.
+ *
+ * Input:
+ * - Resource 1: type=light/grouped_light, sparse, id=1, on=on
+ * - Resource 2: type=light/grouped_light, sparse, id=1, dimming=50
+ *
+ * Expected output:
+ * - Resource 1: type=light/grouped_light, sparse, id=1, on=on, dimming=50
+ *
+ * @throws DTOPresentButEmptyException
+ */
+ @ParameterizedTest
+ @MethodSource("provideLightResourceTypes")
+ void mergeLightResourcesMergeOnStateAndDimmingWhenSparseAndSameId(ResourceType resourceType)
+ throws DTOPresentButEmptyException {
+ List<Resource> resources = new ArrayList<>();
+
+ Resource resource1 = createResource(resourceType, "1");
+ resource1.setOnState(createOnState(true));
+ resources.add(resource1);
+
+ Resource resource2 = createResource(resourceType, "1");
+ resource2.setDimming(createDimming(50));
+ resources.add(resource2);
+
+ Setters.mergeLightResources(resources);
+
+ assertThat(resources.size(), is(equalTo(1)));
+ Resource mergedResource = resources.get(0);
+ assertThat(mergedResource.getId(), is(equalTo(resource1.getId())));
+ assertThat(mergedResource.getType(), is(equalTo(resourceType)));
+ assertThat(mergedResource.hasFullState(), is(false));
+ OnState actualOnState = mergedResource.getOnState();
+ assertThat(actualOnState, is(notNullValue()));
+ if (actualOnState != null) {
+ assertThat(actualOnState.isOn(), is(true));
+ }
+ Dimming actualDimming = mergedResource.getDimming();
+ assertThat(actualDimming, is(notNullValue()));
+ if (actualDimming != null) {
+ assertThat(actualDimming.getBrightness(), is(equalTo(50.0)));
+ }
+ }
+
+ private static Stream<Arguments> provideLightResourceTypes() {
+ return Stream.of(Arguments.of(ResourceType.LIGHT), Arguments.of(ResourceType.GROUPED_LIGHT));
+ }
+
+ /**
+ * Tests merging of dimming for same resource where last value wins.
+ *
+ * Input:
+ * - Resource 1: type=light, sparse, id=1, dimming=49
+ * - Resource 2: type=light, sparse, id=1, dimming=50
+ *
+ * Expected output:
+ * - Resource 1: type=light, sparse, id=1, dimming=50
+ *
+ * @throws DTOPresentButEmptyException
+ */
+ @Test
+ void mergeLightResourcesMergeDimmingToLatestValueWhenSparseAndSameId() throws DTOPresentButEmptyException {
+ List<Resource> resources = new ArrayList<>();
+
+ Resource resource1 = createResource(ResourceType.LIGHT, "1");
+ resource1.setDimming(createDimming(49));
+ resources.add(resource1);
+
+ Resource resource2 = createResource(ResourceType.LIGHT, "1");
+ resource2.setDimming(createDimming(50));
+ resources.add(resource2);
+
+ Setters.mergeLightResources(resources);
+
+ assertThat(resources.size(), is(equalTo(1)));
+ Resource mergedResource = resources.get(0);
+ assertThat(mergedResource.hasFullState(), is(false));
+ Dimming actualDimming = mergedResource.getDimming();
+ assertThat(actualDimming, is(notNullValue()));
+ if (actualDimming != null) {
+ assertThat(actualDimming.getBrightness(), is(equalTo(50.0)));
+ }
+ }
+
+ /**
+ * Tests merging of HSB type fields while keeping resource with effects.
+ *
+ * Input:
+ * - Resource 1: type=light, sparse, id=1, dimming=49
+ * - Resource 2: type=light, sparse, id=1, on=on, dimming=50, effect=xxx
+ *
+ * Expected output:
+ * - Resource 1: type=light, sparse, id=1, on=on, dimming=50
+ * - Resource 2: type=light, sparse, id=1, effect=xxx
+ *
+ * @throws DTOPresentButEmptyException
+ */
+ @Test
+ void mergeLightResourcesMergeHSBFieldsDoNotRemoveResourceWithEffect() throws DTOPresentButEmptyException {
+ List<Resource> resources = new ArrayList<>();
+
+ Resource resource1 = createResource(ResourceType.LIGHT, "1");
+ resource1.setDimming(createDimming(49));
+ resources.add(resource1);
+
+ Resource resource2 = createResource(ResourceType.LIGHT, "1");
+ resource2.setDimming(createDimming(50));
+ resource2.setOnState(createOnState(true));
+ resource2.setFixedEffects(new Effects());
+ resources.add(resource2);
+
+ Setters.mergeLightResources(resources);
+
+ assertThat(resources.size(), is(equalTo(2)));
+ Resource mergedResource = resources.get(0);
+ assertThat(mergedResource.hasFullState(), is(false));
+ OnState actualOnState = mergedResource.getOnState();
+ assertThat(actualOnState, is(notNullValue()));
+ if (actualOnState != null) {
+ assertThat(actualOnState.isOn(), is(true));
+ }
+ Dimming actualDimming = mergedResource.getDimming();
+ assertThat(actualDimming, is(notNullValue()));
+ if (actualDimming != null) {
+ assertThat(actualDimming.getBrightness(), is(equalTo(50.0)));
+ }
+
+ Resource effectsResource = resources.get(1);
+ assertThat(effectsResource.hasFullState(), is(false));
+ assertThat(effectsResource.getFixedEffects(), is(notNullValue()));
+ }
+
+ /**
+ * Tests leaving different resources separated.
+ *
+ * Input:
+ * - Resource 1: type=light, sparse, id=1, on=on
+ * - Resource 2: type=light, sparse, id=2, dimming=50
+ *
+ * Expected output:
+ * - Resource 1: type=light, sparse, id=1, on=on
+ * - Resource 2: type=light, sparse, id=2, dimming=50
+ *
+ * @throws DTOPresentButEmptyException
+ */
+ @Test
+ void mergeLightResourcesDoNotMergeOnStateAndDimmingWhenSparseAndDifferentId() throws DTOPresentButEmptyException {
+ List<Resource> resources = new ArrayList<>();
+
+ Resource resource1 = createResource(ResourceType.LIGHT, "1");
+ resource1.setOnState(createOnState(true));
+ resources.add(resource1);
+
+ Resource resource2 = createResource(ResourceType.LIGHT, "2");
+ resource2.setDimming(createDimming(50));
+ resources.add(resource2);
+
+ Setters.mergeLightResources(resources);
+
+ assertThat(resources.size(), is(equalTo(2)));
+ Resource firstResource = resources.get(0);
+ OnState actualOnState = firstResource.getOnState();
+ assertThat(actualOnState, is(notNullValue()));
+ if (actualOnState != null) {
+ assertThat(actualOnState.isOn(), is(true));
+ }
+ assertThat(firstResource.getDimming(), is(nullValue()));
+
+ Resource secondResource = resources.get(1);
+ assertThat(secondResource.getOnState(), is(nullValue()));
+ Dimming actualDimming = secondResource.getDimming();
+ assertThat(actualDimming, is(notNullValue()));
+ if (actualDimming != null) {
+ assertThat(actualDimming.getBrightness(), is(equalTo(50.0)));
+ }
+ }
+
+ /**
+ * Tests merging of on state and dimming for same resource when full is first.
+ *
+ * Input:
+ * - Resource 1: type=light, full, id=1, on=on
+ *
+ * Expected output:
+ * - Exception thrown, full state is not supported/expected.
+ *
+ * @throws DTOPresentButEmptyException
+ */
+ @Test
+ void mergeLightResourcesMergeOnStateAndDimmingWhenFullStateFirstAndSameId() throws DTOPresentButEmptyException {
+ List<Resource> resources = new ArrayList<>();
+
+ Resource resource = new Resource(ResourceType.LIGHT);
+ resource.setId("1");
+
+ resources.add(resource);
+
+ assertThrows(IllegalStateException.class, () -> Setters.mergeLightResources(resources));
+ }
+
+ /**
+ * Tests leaving resources with on state and color temperature separated.
+ * In this case they could be merged, but it's not needed.
+ *
+ * Input:
+ * - Resource 1: type=light, sparse, id=1, on=on
+ * - Resource 2: type=light, sparse, id=1, color temperature=370 mirek
+ *
+ * Expected output:
+ * - Resource 1: type=light, sparse, id=1, on=on
+ * - Resource 2: type=light, sparse, id=1, color temperature=370 mirek
+ *
+ * @throws DTOPresentButEmptyException
+ */
+ @Test
+ void mergeLightResourcesDoNotMergeOnStateAndColorTemperatureWhenSparseAndSameId()
+ throws DTOPresentButEmptyException {
+ List<Resource> resources = new ArrayList<>();
+
+ Resource resource1 = createResource(ResourceType.LIGHT, "1");
+ resource1.setOnState(createOnState(true));
+ resources.add(resource1);
+
+ Resource resource2 = createResource(ResourceType.LIGHT, "1");
+ resource2.setColorTemperature(createColorTemperature(370));
+ resources.add(resource2);
+
+ Setters.mergeLightResources(resources);
+
+ assertThat(resources.size(), is(equalTo(2)));
+ Resource firstResource = resources.get(0);
+ OnState actualOnState = firstResource.getOnState();
+ assertThat(actualOnState, is(notNullValue()));
+ if (actualOnState != null) {
+ assertThat(actualOnState.isOn(), is(true));
+ }
+ assertThat(firstResource.getColorTemperature(), is(nullValue()));
+
+ Resource secondResource = resources.get(1);
+ assertThat(secondResource.getOnState(), is(nullValue()));
+ ColorTemperature actualColorTemperature = secondResource.getColorTemperature();
+ assertThat(actualColorTemperature, is(notNullValue()));
+ if (actualColorTemperature != null) {
+ assertThat(actualColorTemperature.getMirek(), is(equalTo(370L)));
+ }
+ }
+
+ /**
+ * Tests merging resources with on state/color and leaving color temperature separated.
+ * In this case they could be merged, but it's not needed.
+ *
+ * Input:
+ * - Resource 1: type=light, sparse, id=1, on=on
+ * - Resource 2: type=light, sparse, id=1, dimming=50, color temperature=370 mirek
+ *
+ * Expected output:
+ * - Resource 1: type=light, sparse, id=1, on=on, dimming=50
+ * - Resource 2: type=light, sparse, id=1, color temperature=370 mirek
+ *
+ * @throws DTOPresentButEmptyException
+ */
+ @Test
+ void mergeLightResourcesMergeOnStateAndDimmingButNotColorTemperatureWhenSparseAndSameId()
+ throws DTOPresentButEmptyException {
+ List<Resource> resources = new ArrayList<>();
+
+ Resource resource1 = createResource(ResourceType.LIGHT, "1");
+ resource1.setOnState(createOnState(true));
+ resources.add(resource1);
+
+ Resource resource2 = createResource(ResourceType.LIGHT, "1");
+ resource2.setDimming(createDimming(50));
+ resource2.setColorTemperature(createColorTemperature(370));
+ resources.add(resource2);
+
+ Setters.mergeLightResources(resources);
+
+ assertThat(resources.size(), is(equalTo(2)));
+ Resource firstResource = resources.get(0);
+ OnState actualOnState = firstResource.getOnState();
+ assertThat(actualOnState, is(notNullValue()));
+ if (actualOnState != null) {
+ assertThat(actualOnState.isOn(), is(true));
+ }
+ Dimming actualDimming = firstResource.getDimming();
+ assertThat(actualDimming, is(notNullValue()));
+ if (actualDimming != null) {
+ assertThat(actualDimming.getBrightness(), is(equalTo(50.0)));
+ }
+ assertThat(firstResource.getColorTemperature(), is(nullValue()));
+
+ Resource secondResource = resources.get(1);
+ assertThat(secondResource.getOnState(), is(nullValue()));
+ assertThat(secondResource.getDimming(), is(nullValue()));
+ ColorTemperature actualColorTemperature = secondResource.getColorTemperature();
+ assertThat(actualColorTemperature, is(notNullValue()));
+ if (actualColorTemperature != null) {
+ assertThat(actualColorTemperature.getMirek(), is(equalTo(370L)));
+ }
+ }
+
+ /**
+ * Tests preserving resource with on state and color temperature.
+ *
+ * Input:
+ * - Resource 1: type=light, sparse, id=1, on=on, color temperature=370
+ *
+ * Expected output:
+ * - Resource 1: type=light, sparse, id=1, on=on, color temperature=370
+ *
+ * @throws DTOPresentButEmptyException
+ */
+ @Test
+ void mergeLightResourcesSeparateOnStateAndColorTemperatureWhenSparseAndSameId() throws DTOPresentButEmptyException {
+ List<Resource> resources = new ArrayList<>();
+
+ Resource resource = createResource(ResourceType.LIGHT, "1");
+ resource.setOnState(createOnState(true));
+ resource.setColorTemperature(createColorTemperature(370));
+ resources.add(resource);
+
+ Setters.mergeLightResources(resources);
+
+ assertThat(resources.size(), is(equalTo(1)));
+
+ Resource firstResource = resources.get(0);
+ OnState actualOnState = firstResource.getOnState();
+ assertThat(actualOnState, is(notNullValue()));
+ if (actualOnState != null) {
+ assertThat(actualOnState.isOn(), is(true));
+ }
+ ColorTemperature actualColorTemperature = firstResource.getColorTemperature();
+ assertThat(actualColorTemperature, is(notNullValue()));
+ if (actualColorTemperature != null) {
+ assertThat(actualColorTemperature.getMirek(), is(equalTo(370L)));
+ }
+ }
+
+ /**
+ * Tests that resources that are not light or grouped_light will not throw.
+ *
+ * Input:
+ * - Resource 1: type=motion, sparse, id=1
+ *
+ * Expected output:
+ * - Resource 1: type=motion, sparse, id=1
+ *
+ * @throws DTOPresentButEmptyException
+ */
+ @Test
+ void mergeLightResourcesNonLightResourceShouldNotThrow() throws DTOPresentButEmptyException {
+ List<Resource> resources = new ArrayList<>();
+
+ Resource resource = createResource(ResourceType.MOTION, "1");
+ resources.add(resource);
+
+ Setters.mergeLightResources(resources);
+
+ assertThat(resources.size(), is(equalTo(1)));
+
+ Resource firstResource = resources.get(0);
+ assertThat(firstResource.getType(), is(equalTo(ResourceType.MOTION)));
+ }
+
+ private OnState createOnState(boolean on) {
+ OnState onState = new OnState();
+ onState.setOn(on);
+
+ return onState;
+ }
+
+ private Dimming createDimming(double brightness) {
+ Dimming dimming = new Dimming();
+ dimming.setBrightness(brightness);
+
+ return dimming;
+ }
+
+ private ColorTemperature createColorTemperature(double mirek) {
+ ColorTemperature colorTemperature = new ColorTemperature();
+ colorTemperature.setMirek(mirek);
+
+ return colorTemperature;
+ }
+
+ private Resource createResource(ResourceType resourceType, String id) {
+ Resource resource = new Resource(resourceType);
+ resource.setId(id);
+ resource.markAsSparse();
+
+ return resource;
+ }
+}