2 * Copyright (c) 2010-2022 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.persistence.dynamodb.internal;
15 import static org.junit.jupiter.api.Assertions.*;
17 import java.io.IOException;
18 import java.math.BigDecimal;
19 import java.time.Instant;
20 import java.time.ZoneId;
21 import java.time.ZonedDateTime;
22 import java.util.Objects;
23 import java.util.TimeZone;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.junit.jupiter.params.ParameterizedTest;
27 import org.junit.jupiter.params.provider.CsvSource;
28 import org.openhab.core.items.GenericItem;
29 import org.openhab.core.library.items.CallItem;
30 import org.openhab.core.library.items.ColorItem;
31 import org.openhab.core.library.items.ContactItem;
32 import org.openhab.core.library.items.DateTimeItem;
33 import org.openhab.core.library.items.DimmerItem;
34 import org.openhab.core.library.items.LocationItem;
35 import org.openhab.core.library.items.NumberItem;
36 import org.openhab.core.library.items.RollershutterItem;
37 import org.openhab.core.library.items.StringItem;
38 import org.openhab.core.library.items.SwitchItem;
39 import org.openhab.core.library.types.DateTimeType;
40 import org.openhab.core.library.types.DecimalType;
41 import org.openhab.core.library.types.HSBType;
42 import org.openhab.core.library.types.OnOffType;
43 import org.openhab.core.library.types.OpenClosedType;
44 import org.openhab.core.library.types.PercentType;
45 import org.openhab.core.library.types.PointType;
46 import org.openhab.core.library.types.StringListType;
47 import org.openhab.core.library.types.StringType;
48 import org.openhab.core.library.types.UpDownType;
49 import org.openhab.core.persistence.HistoricItem;
50 import org.openhab.core.types.State;
53 * Test for AbstractDynamoDBItem.fromState and AbstractDynamoDBItem.asHistoricItem for all kind of states
55 * @author Sami Salonen - Initial contribution
58 public class AbstractDynamoDBItemSerializationTest {
60 private final ZonedDateTime date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(400), ZoneId.systemDefault());
63 * Generic function testing serialization of GenericItem state to internal format in DB. In other words, conversion
65 * GenericItem with state to DynamoDBItem
67 * @param legacy whether we have legacy
68 * @param GenericItem item
69 * @param stateOverride state
70 * @param expectedState internal format in DB representing the GenericItem state
71 * @return dynamo db item
74 public DynamoDBItem<?> testSerializationToDTO(boolean legacy, GenericItem item, State stateOverride,
75 Object expectedState) throws IOException {
76 item.setState(stateOverride);
77 DynamoDBItem<?> dbItem = legacy ? AbstractDynamoDBItem.fromStateLegacy(item, date)
78 : AbstractDynamoDBItem.fromStateNew(item, date, null);
80 assertEquals("foo", dbItem.getName());
81 assertEquals(date, dbItem.getTime());
82 Object actualState = dbItem.getState();
83 assertNotNull(actualState);
84 Objects.requireNonNull(actualState);
85 if (expectedState instanceof BigDecimal) {
86 BigDecimal expectedRounded = DynamoDBBigDecimalItem.loseDigits(((BigDecimal) expectedState));
87 assertEquals(0, expectedRounded.compareTo((BigDecimal) actualState),
88 String.format("Expected state %s (%s but with some digits lost) did not match actual state %s",
89 expectedRounded, expectedState, actualState));
91 assertEquals(expectedState, actualState);
97 * Test state deserialization, that is DynamoDBItem conversion to HistoricItem
99 * @param dbItem dynamo db item
100 * @param GenericItem parameter for DynamoDBItem.asHistoricItem
101 * @param expectedState Expected state of the historic item. DecimalTypes are compared with reduced accuracy
103 * @throws IOException
105 public HistoricItem testAsHistoricGeneric(DynamoDBItem<?> dbItem, GenericItem item, Object expectedState)
107 HistoricItem historicItem = dbItem.asHistoricItem(item);
108 assertNotNull(historicItem);
109 assert historicItem != null; // getting rid off null pointer access warning
110 assertEquals("foo", historicItem.getName());
111 assertEquals(date, historicItem.getTimestamp());
112 assertEquals(expectedState.getClass(), historicItem.getState().getClass());
113 if (expectedState.getClass() == DecimalType.class) {
114 // serialization loses accuracy, take this into consideration
115 BigDecimal expectedRounded = DynamoDBBigDecimalItem
116 .loseDigits(((DecimalType) expectedState).toBigDecimal());
117 BigDecimal actual = ((DecimalType) historicItem.getState()).toBigDecimal();
118 assertEquals(0, expectedRounded.compareTo(actual),
119 String.format("Expected state %s (%s but with some digits lost) did not match actual state %s",
120 expectedRounded, expectedState, actual));
122 assertEquals(expectedState, historicItem.getState());
128 @CsvSource({ "true", "false" })
129 public void testCallTypeWithCallItemLegacy(boolean legacy) throws IOException {
130 GenericItem item = new CallItem("foo");
131 final DynamoDBItem<?> dbitem = testSerializationToDTO(legacy, item, new StringListType("origNum", "destNum"),
133 testAsHistoricGeneric(dbitem, item, new StringListType("origNum", "destNum"));
137 @CsvSource({ "true", "false" })
138 public void testOpenClosedTypeWithContactItem(boolean legacy) throws IOException {
139 GenericItem item = new ContactItem("foo");
140 final DynamoDBItem<?> dbitemOpen = testSerializationToDTO(legacy, item, OpenClosedType.CLOSED, BigDecimal.ZERO);
141 testAsHistoricGeneric(dbitemOpen, item, OpenClosedType.CLOSED);
143 final DynamoDBItem<?> dbitemClosed = testSerializationToDTO(legacy, item, OpenClosedType.OPEN, BigDecimal.ONE);
144 testAsHistoricGeneric(dbitemClosed, item, OpenClosedType.OPEN);
148 @CsvSource({ "true", "false" })
149 public void testDateTimeTypeWithDateTimeItem(boolean legacy) throws IOException {
150 GenericItem item = new DateTimeItem("foo");
151 ZonedDateTime zdt = ZonedDateTime.parse("2016-05-01T13:46:00.050Z");
152 DynamoDBItem<?> dbitem = testSerializationToDTO(legacy, item, new DateTimeType(zdt.toString()),
153 "2016-05-01T13:46:00.050Z");
154 testAsHistoricGeneric(dbitem, item, new DateTimeType(zdt.withZoneSameInstant(ZoneId.systemDefault())));
158 @CsvSource({ "true", "false" })
159 public void testDateTimeTypeWithStringItem(boolean legacy) throws IOException {
160 GenericItem item = new StringItem("foo");
161 DynamoDBItem<?> dbitem = testSerializationToDTO(legacy, item,
162 new DateTimeType(ZonedDateTime.parse("2016-05-01T13:46:00.050Z")), "2016-05-01T13:46:00.050Z");
163 testAsHistoricGeneric(dbitem, item, new StringType("2016-05-01T13:46:00.050Z"));
167 @CsvSource({ "true", "false" })
168 public void testDateTimeTypeLocalWithDateTimeItem(boolean legacy) throws IOException {
169 GenericItem item = new DateTimeItem("foo");
170 ZonedDateTime expectedZdt = Instant.ofEpochMilli(1468773487050L).atZone(ZoneId.systemDefault());
171 DynamoDBItem<?> dbitem = testSerializationToDTO(legacy, item, new DateTimeType("2016-07-17T19:38:07.050+0300"),
172 "2016-07-17T16:38:07.050Z");
173 testAsHistoricGeneric(dbitem, item, new DateTimeType(expectedZdt));
177 @CsvSource({ "true", "false" })
178 public void testDateTimeTypeLocalWithStringItem(boolean legacy) throws IOException {
179 GenericItem item = new StringItem("foo");
180 Instant instant = Instant.ofEpochMilli(1468773487050L); // GMT: Sun, 17 Jul 2016 16:38:07.050 GMT
181 ZonedDateTime zdt = instant.atZone(TimeZone.getTimeZone("GMT+03:00").toZoneId());
182 DynamoDBItem<?> dbitem = testSerializationToDTO(legacy, item, new DateTimeType(zdt),
183 "2016-07-17T16:38:07.050Z");
184 testAsHistoricGeneric(dbitem, item, new StringType("2016-07-17T16:38:07.050Z"));
188 @CsvSource({ "true", "false" })
189 public void testPointTypeWithLocationItem(boolean legacy) throws IOException {
190 GenericItem item = new LocationItem("foo");
191 final PointType point = new PointType(new DecimalType(60.3), new DecimalType(30.2), new DecimalType(510.90));
192 String expected = point.getLatitude().toBigDecimal().toString() + ","
193 + point.getLongitude().toBigDecimal().toString() + "," + point.getAltitude().toBigDecimal().toString();
194 DynamoDBItem<?> dbitem = testSerializationToDTO(legacy, item, point, expected);
195 testAsHistoricGeneric(dbitem, item, point);
199 @CsvSource({ "true", "false" })
200 public void testDecimalTypeWithNumberItem(boolean legacy) throws IOException {
201 GenericItem item = new NumberItem("foo");
202 DynamoDBItem<?> dbitem = testSerializationToDTO(legacy, item, new DecimalType("3.2"), new BigDecimal("3.2"));
203 testAsHistoricGeneric(dbitem, item, new DecimalType("3.2"));
207 @CsvSource({ "true", "false" })
208 public void testPercentTypeWithColorItem(boolean legacy) throws IOException {
209 GenericItem item = new ColorItem("foo");
210 DynamoDBItem<?> dbitem = testSerializationToDTO(legacy, item, new PercentType(new BigDecimal("3.2")),
212 testAsHistoricGeneric(dbitem, item, new HSBType(DecimalType.ZERO, PercentType.ZERO, new PercentType("3.2")));
216 @CsvSource({ "true", "false" })
217 public void testPercentTypeWithDimmerItem(boolean legacy) throws IOException {
218 GenericItem item = new DimmerItem("foo");
219 DynamoDBItem<?> dbitem = testSerializationToDTO(legacy, item, new PercentType(new BigDecimal("3.2")),
220 new BigDecimal("3.2"));
221 testAsHistoricGeneric(dbitem, item, new PercentType(new BigDecimal("3.2")));
225 @CsvSource({ "true", "false" })
226 public void testPercentTypeWithRollerShutterItem(boolean legacy) throws IOException {
227 GenericItem item = new RollershutterItem("foo");
228 DynamoDBItem<?> dbitem = testSerializationToDTO(legacy, item, new PercentType(81), new BigDecimal("81"));
229 testAsHistoricGeneric(dbitem, item, new PercentType(81));
233 @CsvSource({ "true", "false" })
234 public void testUpDownTypeWithRollershutterItem(boolean legacy) throws IOException {
235 GenericItem item = new RollershutterItem("foo");
236 // note: comes back as PercentType instead of the original UpDownType
238 // down == 1.0 = 100%
239 State expectedDeserializedState = PercentType.HUNDRED;
240 DynamoDBItem<?> dbItemDown = testSerializationToDTO(legacy, item, UpDownType.DOWN, new BigDecimal(100));
241 testAsHistoricGeneric(dbItemDown, item, expectedDeserializedState);
242 assertEquals(UpDownType.DOWN, expectedDeserializedState.as(UpDownType.class));
247 State expectedDeserializedState = PercentType.ZERO;
248 DynamoDBItem<?> dbItemUp = testSerializationToDTO(legacy, item, UpDownType.UP, BigDecimal.ZERO);
249 testAsHistoricGeneric(dbItemUp, item, expectedDeserializedState);
250 assertEquals(UpDownType.UP, expectedDeserializedState.as(UpDownType.class));
255 @CsvSource({ "true", "false" })
256 public void testStringTypeWithStringItem(boolean legacy) throws IOException {
257 GenericItem item = new StringItem("foo");
258 DynamoDBItem<?> dbitem = testSerializationToDTO(legacy, item, new StringType("foo bar"), "foo bar");
259 testAsHistoricGeneric(dbitem, item, new StringType("foo bar"));
263 @CsvSource({ "true", "false" })
264 public void testOnOffTypeWithColorItem(boolean legacy) throws IOException {
265 GenericItem item = new ColorItem("foo");
266 DynamoDBItem<?> dbitemOff = testSerializationToDTO(legacy, item, OnOffType.OFF, "0,0,0");
267 testAsHistoricGeneric(dbitemOff, item, HSBType.BLACK);
269 DynamoDBItem<?> dbitemOn = testSerializationToDTO(legacy, item, OnOffType.ON, "0,0,100");
270 testAsHistoricGeneric(dbitemOn, item, HSBType.WHITE);
274 @CsvSource({ "true", "false" })
275 public void testOnOffTypeWithDimmerItem(boolean legacy) throws IOException {
276 GenericItem item = new DimmerItem("foo");
278 State expectedDeserializedState = PercentType.ZERO;
279 DynamoDBItem<?> dbitemOff = testSerializationToDTO(legacy, item, OnOffType.OFF, BigDecimal.ZERO);
280 testAsHistoricGeneric(dbitemOff, item, expectedDeserializedState);
281 assertEquals(OnOffType.OFF, expectedDeserializedState.as(OnOffType.class));
285 State expectedDeserializedState = PercentType.HUNDRED;
286 DynamoDBItem<?> dbitemOn = testSerializationToDTO(legacy, item, OnOffType.ON, new BigDecimal(100));
287 testAsHistoricGeneric(dbitemOn, item, expectedDeserializedState);
288 assertEquals(OnOffType.ON, expectedDeserializedState.as(OnOffType.class));
293 @CsvSource({ "true", "false" })
294 public void testOnOffTypeWithSwitchItem(boolean legacy) throws IOException {
295 GenericItem item = new SwitchItem("foo");
296 DynamoDBItem<?> dbitemOff = testSerializationToDTO(legacy, item, OnOffType.OFF, BigDecimal.ZERO);
297 testAsHistoricGeneric(dbitemOff, item, OnOffType.OFF);
299 DynamoDBItem<?> dbitemOn = testSerializationToDTO(legacy, item, OnOffType.ON, BigDecimal.ONE);
300 testAsHistoricGeneric(dbitemOn, item, OnOffType.ON);
304 @CsvSource({ "true", "false" })
305 public void testHSBTypeWithColorItem(boolean legacy) throws IOException {
306 GenericItem item = new ColorItem("foo");
307 HSBType hsb = new HSBType(new DecimalType(1.5), new PercentType(new BigDecimal(2.5)),
308 new PercentType(new BigDecimal(3.5)));
309 DynamoDBItem<?> dbitem = testSerializationToDTO(legacy, item, hsb, "1.5,2.5,3.5");
310 testAsHistoricGeneric(dbitem, item, hsb);