[aWATTar] push test coverage and improve code readability

Signed-off-by: Thomas Leber <thomas@tl-photography.at>
This commit is contained in:
Thomas Leber 2024-11-16 00:40:50 +01:00
parent 2cd8902017
commit 71b17c018e
5 changed files with 150 additions and 43 deletions

View File

@ -12,6 +12,8 @@
*/ */
package org.openhab.binding.awattar.internal; package org.openhab.binding.awattar.internal;
import java.time.Instant;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
@ -47,7 +49,18 @@ public abstract class AwattarBestPriceResult {
} }
} }
public abstract boolean isActive(); /**
* Returns true if the best price is active.
*
* @param now the current time
* @return true if the best price is active, false otherwise
*/
public abstract boolean isActive(Instant now);
/**
* Returns the hours of the best price.
*
* @return the hours of the best price as a string
*/
public abstract String getHours(); public abstract String getHours();
} }

View File

@ -76,8 +76,8 @@ public class AwattarConsecutiveBestPriceResult extends AwattarBestPriceResult {
} }
@Override @Override
public boolean isActive() { public boolean isActive(Instant now) {
return contains(Instant.now().toEpochMilli()); return contains(now.toEpochMilli());
} }
public boolean contains(long timestamp) { public boolean contains(long timestamp) {

View File

@ -64,8 +64,8 @@ public class AwattarNonConsecutiveBestPriceResult extends AwattarBestPriceResult
} }
@Override @Override
public boolean isActive() { public boolean isActive(Instant now) {
return members.stream().anyMatch(x -> x.timerange().contains(Instant.now().toEpochMilli())); return members.stream().anyMatch(x -> x.timerange().contains(now.toEpochMilli()));
} }
@Override @Override
@ -73,16 +73,9 @@ public class AwattarNonConsecutiveBestPriceResult extends AwattarBestPriceResult
return String.format("NonConsecutiveBestpriceResult with %s", members.toString()); return String.format("NonConsecutiveBestpriceResult with %s", members.toString());
} }
private void sort() {
if (!sorted) {
members.sort(Comparator.comparingLong(p -> p.timerange().start()));
}
}
@Override @Override
public String getHours() { public String getHours() {
boolean second = false; boolean second = false;
sort();
StringBuilder res = new StringBuilder(); StringBuilder res = new StringBuilder();
for (AwattarPrice price : members) { for (AwattarPrice price : members) {

View File

@ -23,7 +23,6 @@ import static org.openhab.binding.awattar.internal.AwattarUtil.getCalendarForHou
import static org.openhab.binding.awattar.internal.AwattarUtil.getDuration; import static org.openhab.binding.awattar.internal.AwattarUtil.getDuration;
import static org.openhab.binding.awattar.internal.AwattarUtil.getMillisToNextMinute; import static org.openhab.binding.awattar.internal.AwattarUtil.getMillisToNextMinute;
import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.ArrayList; import java.util.ArrayList;
@ -163,7 +162,7 @@ public class AwattarBestPriceHandler extends BaseThingHandler {
long diff; long diff;
switch (channelId) { switch (channelId) {
case CHANNEL_ACTIVE: case CHANNEL_ACTIVE:
state = OnOffType.from(result.isActive()); state = OnOffType.from(result.isActive(getNow(zoneId).toInstant()));
break; break;
case CHANNEL_START: case CHANNEL_START:
state = new DateTimeType(Instant.ofEpochMilli(result.getStart())); state = new DateTimeType(Instant.ofEpochMilli(result.getStart()));
@ -172,7 +171,7 @@ public class AwattarBestPriceHandler extends BaseThingHandler {
state = new DateTimeType(Instant.ofEpochMilli(result.getEnd())); state = new DateTimeType(Instant.ofEpochMilli(result.getEnd()));
break; break;
case CHANNEL_COUNTDOWN: case CHANNEL_COUNTDOWN:
diff = result.getStart() - Instant.now().toEpochMilli(); diff = result.getStart() - getNow(zoneId).toInstant().toEpochMilli();
if (diff >= 0) { if (diff >= 0) {
state = getDuration(diff); state = getDuration(diff);
} else { } else {
@ -180,8 +179,8 @@ public class AwattarBestPriceHandler extends BaseThingHandler {
} }
break; break;
case CHANNEL_REMAINING: case CHANNEL_REMAINING:
if (result.isActive()) { if (result.isActive(getNow(zoneId).toInstant())) {
diff = result.getEnd() - Instant.now().toEpochMilli(); diff = result.getEnd() - getNow(zoneId).toInstant().toEpochMilli();
state = getDuration(diff); state = getDuration(diff);
} else { } else {
state = QuantityType.valueOf(0, Units.MINUTE); state = QuantityType.valueOf(0, Units.MINUTE);
@ -216,20 +215,49 @@ public class AwattarBestPriceHandler extends BaseThingHandler {
return result; return result;
} }
/**
* Returns the time range for the given start hour and duration.
*
* @param start the start hour (0-23)
* @param duration the duration in hours
* @param zoneId the time zone to use
* @return the range
*/
protected TimeRange getRange(int start, int duration, ZoneId zoneId) { protected TimeRange getRange(int start, int duration, ZoneId zoneId) {
ZonedDateTime startCal = getCalendarForHour(start, zoneId); ZonedDateTime startTime = getStarTime(start, zoneId);
ZonedDateTime endCal = startCal.plusHours(duration); ZonedDateTime endTime = startTime.plusHours(duration);
ZonedDateTime now = ZonedDateTime.now(zoneId); ZonedDateTime now = getNow(zoneId);
if (now.getHour() < start) { if (now.getHour() < start) {
// we are before the range, so we might be still within the last range // we are before the range, so we might be still within the last range
startCal = startCal.minusDays(1); startTime = startTime.minusDays(1);
endCal = endCal.minusDays(1); endTime = endTime.minusDays(1);
} }
if (endCal.toInstant().toEpochMilli() < Instant.now().toEpochMilli()) { if (endTime.toInstant().toEpochMilli() < now.toInstant().toEpochMilli()) {
// span is in the past, add one day // span is in the past, add one day
startCal = startCal.plusDays(1); startTime = startTime.plusDays(1);
endCal = endCal.plusDays(1); endTime = endTime.plusDays(1);
} }
return new TimeRange(startCal.toInstant().toEpochMilli(), endCal.toInstant().toEpochMilli()); return new TimeRange(startTime.toInstant().toEpochMilli(), endTime.toInstant().toEpochMilli());
}
/**
* Returns the start time for the given hour.
*
* @param start the hour. Must be between 0 and 23.
* @param zoneId the time zone
* @return the start time
*/
protected ZonedDateTime getStarTime(int start, ZoneId zoneId) {
return getCalendarForHour(start, zoneId);
}
/**
* Returns the current time.
*
* @param zoneId the time zone
* @return the current time
*/
protected ZonedDateTime getNow(ZoneId zoneId) {
return ZonedDateTime.now(zoneId);
} }
} }

View File

@ -20,14 +20,18 @@ import static org.hamcrest.Matchers.nullValue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_ACTIVE;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_COUNTDOWN;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_END; import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_END;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_HOURS; import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_HOURS;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_REMAINING;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_START; import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_START;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -58,6 +62,8 @@ import org.openhab.binding.awattar.internal.dto.AwattarApiData;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
import org.openhab.core.i18n.TimeZoneProvider; import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
import org.openhab.core.test.java.JavaTest; import org.openhab.core.test.java.JavaTest;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
@ -166,31 +172,98 @@ public class AwattarBridgeHandlerTest extends JavaTest {
public static Stream<Arguments> testBestpriceHandler() { public static Stream<Arguments> testBestpriceHandler() {
return Stream.of( // return Stream.of( //
Arguments.of(1, true, CHANNEL_START, new DateTimeType("2024-06-15T14:00:00.000+0200")), Arguments.of(24, 1, true, CHANNEL_START, new DateTimeType("2024-06-15T14:00:00.000+0200")),
Arguments.of(1, true, CHANNEL_END, new DateTimeType("2024-06-15T15:00:00.000+0200")), Arguments.of(24, 1, true, CHANNEL_END, new DateTimeType("2024-06-15T15:00:00.000+0200")),
Arguments.of(1, true, CHANNEL_HOURS, new StringType("14")), Arguments.of(24, 1, true, CHANNEL_HOURS, new StringType("14")),
Arguments.of(1, false, CHANNEL_START, new DateTimeType("2024-06-15T14:00:00.000+0200")), Arguments.of(24, 1, false, CHANNEL_START, new DateTimeType("2024-06-15T14:00:00.000+0200")),
Arguments.of(1, false, CHANNEL_END, new DateTimeType("2024-06-15T15:00:00.000+0200")), Arguments.of(24, 1, false, CHANNEL_END, new DateTimeType("2024-06-15T15:00:00.000+0200")),
Arguments.of(1, false, CHANNEL_HOURS, new StringType("14")), Arguments.of(24, 1, false, CHANNEL_HOURS, new StringType("14")),
Arguments.of(2, true, CHANNEL_START, new DateTimeType("2024-06-15T13:00:00.000+0200")), Arguments.of(24, 2, true, CHANNEL_START, new DateTimeType("2024-06-15T13:00:00.000+0200")),
Arguments.of(2, true, CHANNEL_END, new DateTimeType("2024-06-15T15:00:00.000+0200")), Arguments.of(24, 2, true, CHANNEL_END, new DateTimeType("2024-06-15T15:00:00.000+0200")),
Arguments.of(2, true, CHANNEL_HOURS, new StringType("13,14")), Arguments.of(24, 2, true, CHANNEL_HOURS, new StringType("13,14")),
Arguments.of(2, false, CHANNEL_START, new DateTimeType("2024-06-15T13:00:00.000+0200")), Arguments.of(24, 2, false, CHANNEL_START, new DateTimeType("2024-06-15T13:00:00.000+0200")),
Arguments.of(2, false, CHANNEL_END, new DateTimeType("2024-06-15T15:00:00.000+0200")), Arguments.of(24, 2, false, CHANNEL_END, new DateTimeType("2024-06-15T15:00:00.000+0200")),
Arguments.of(2, false, CHANNEL_HOURS, new StringType("13,14"))); Arguments.of(24, 2, false, CHANNEL_HOURS, new StringType("13,14")),
Arguments.of(34, 4, false, CHANNEL_START, new DateTimeType("2024-06-15T12:00:00.000+0200")),
Arguments.of(34, 4, false, CHANNEL_END, new DateTimeType("2024-06-15T16:00:00.000+0200")),
Arguments.of(34, 4, false, CHANNEL_HOURS, new StringType("12,13,14,15")),
Arguments.of(34, 8, false, CHANNEL_START, new DateTimeType("2024-06-15T12:00:00.000+0200")),
Arguments.of(34, 8, false, CHANNEL_END, new DateTimeType("2024-06-16T16:00:00.000+0200")),
Arguments.of(34, 8, false, CHANNEL_HOURS, new StringType("12,13,14,15,16,13,14,15")));
} }
@ParameterizedTest @ParameterizedTest
@MethodSource @MethodSource
void testBestpriceHandler(int length, boolean consecutive, String channelId, State expectedState) { void testBestpriceHandler(int rangeDuration, int length, boolean consecutive, String channelId,
State expectedState) {
ThingUID bestPriceUid = new ThingUID(AwattarBindingConstants.THING_TYPE_BESTPRICE, "foo"); ThingUID bestPriceUid = new ThingUID(AwattarBindingConstants.THING_TYPE_BESTPRICE, "foo");
Map<String, Object> config = Map.of("length", length, "consecutive", consecutive); Map<String, Object> config = Map.of("rangeDuration", rangeDuration, "length", length, "consecutive",
consecutive);
when(bestpriceMock.getConfiguration()).thenReturn(new Configuration(config)); when(bestpriceMock.getConfiguration()).thenReturn(new Configuration(config));
AwattarBestPriceHandler handler = new AwattarBestPriceHandler(bestpriceMock, timeZoneProviderMock) { AwattarBestPriceHandler handler = new AwattarBestPriceHandler(bestpriceMock, timeZoneProviderMock) {
@Override protected ZonedDateTime getStarTime(int start, ZoneId zoneId) {
protected TimeRange getRange(int start, int duration, ZoneId zoneId) { return ZonedDateTime.of(2024, 6, 15, 12, 0, 0, 0, zoneId);
return new TimeRange(1718402400000L, 1718488800000L); }
protected ZonedDateTime getNow(ZoneId zoneId) {
return ZonedDateTime.of(2024, 6, 15, 12, 0, 0, 0, zoneId);
}
};
handler.setCallback(bestPriceCallbackMock);
ChannelUID channelUID = new ChannelUID(bestPriceUid, channelId);
handler.refreshChannel(channelUID);
verify(bestPriceCallbackMock).stateUpdated(channelUID, expectedState);
}
public static Stream<Arguments> testBestpriceHandler_channels() {
return Stream.of( //
Arguments.of(12, 0, 24, 1, true, CHANNEL_HOURS, new StringType("14")),
Arguments.of(12, 0, 24, 1, true, CHANNEL_ACTIVE, OnOffType.from(false)),
Arguments.of(12, 0, 24, 1, true, CHANNEL_COUNTDOWN, new QuantityType<>("120 min")),
Arguments.of(12, 0, 24, 1, true, CHANNEL_REMAINING, new QuantityType<>("0 min")),
Arguments.of(13, 59, 24, 1, true, CHANNEL_COUNTDOWN, new QuantityType<>("1 min")),
Arguments.of(13, 59, 24, 1, true, CHANNEL_REMAINING, new QuantityType<>("0 min")),
Arguments.of(13, 59, 24, 1, false, CHANNEL_ACTIVE, OnOffType.from(false)),
Arguments.of(13, 59, 24, 1, true, CHANNEL_ACTIVE, OnOffType.from(false)),
Arguments.of(14, 01, 24, 1, true, CHANNEL_COUNTDOWN, new QuantityType<>("0 min")),
Arguments.of(14, 01, 24, 1, true, CHANNEL_REMAINING, new QuantityType<>("59 min")),
Arguments.of(14, 01, 24, 1, false, CHANNEL_ACTIVE, OnOffType.from(true)),
Arguments.of(14, 01, 24, 1, true, CHANNEL_ACTIVE, OnOffType.from(true)),
Arguments.of(14, 59, 24, 1, true, CHANNEL_COUNTDOWN, new QuantityType<>("0 min")),
Arguments.of(14, 59, 24, 1, true, CHANNEL_REMAINING, new QuantityType<>("1 min")),
Arguments.of(14, 59, 24, 1, false, CHANNEL_ACTIVE, OnOffType.from(true)),
Arguments.of(14, 59, 24, 1, true, CHANNEL_ACTIVE, OnOffType.from(true)),
Arguments.of(15, 00, 24, 1, true, CHANNEL_COUNTDOWN, new QuantityType<>("0 min")),
Arguments.of(15, 00, 24, 1, true, CHANNEL_REMAINING, new QuantityType<>("0 min")),
Arguments.of(15, 00, 24, 1, false, CHANNEL_ACTIVE, OnOffType.from(false)),
Arguments.of(15, 00, 24, 1, true, CHANNEL_ACTIVE, OnOffType.from(false)),
Arguments.of(12, 0, 24, 1, true, CHANNEL_REMAINING, new QuantityType<>("0 min")));
}
@ParameterizedTest
@MethodSource
void testBestpriceHandler_channels(int currentHour, int currentMinute, int rangeDuration, int length,
boolean consecutive, String channelId, State expectedState) {
ThingUID bestPriceUid = new ThingUID(AwattarBindingConstants.THING_TYPE_BESTPRICE, "foo");
Map<String, Object> config = Map.of("rangeDuration", rangeDuration, "length", length, "consecutive",
consecutive);
when(bestpriceMock.getConfiguration()).thenReturn(new Configuration(config));
AwattarBestPriceHandler handler = new AwattarBestPriceHandler(bestpriceMock, timeZoneProviderMock) {
protected ZonedDateTime getStarTime(int start, ZoneId zoneId) {
return ZonedDateTime.of(2024, 6, 15, 0, 0, 0, 0, zoneId);
}
protected ZonedDateTime getNow(ZoneId zoneId) {
return ZonedDateTime.of(2024, 6, 15, currentHour, currentMinute, 0, 0, zoneId);
} }
}; };