Skip to content

Commit

Permalink
[aWATTar] push test coverage and improve code readability
Browse files Browse the repository at this point in the history
Signed-off-by: Thomas Leber <[email protected]>
  • Loading branch information
tl-photography committed Nov 15, 2024
1 parent 9460eb0 commit 074b441
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/
package org.openhab.binding.awattar.internal;

import java.time.Instant;

import org.eclipse.jdt.annotation.NonNullByDefault;

/**
Expand Down Expand Up @@ -47,7 +49,18 @@ public void updateEnd(long end) {
}
}

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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ public AwattarConsecutiveBestPriceResult(List<AwattarPrice> prices, int length,
}

@Override
public boolean isActive() {
return contains(Instant.now().toEpochMilli());
public boolean isActive(Instant now) {
return contains(now.toEpochMilli());
}

public boolean contains(long timestamp) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,25 +64,18 @@ private void addMember(AwattarPrice member) {
}

@Override
public boolean isActive() {
return members.stream().anyMatch(x -> x.timerange().contains(Instant.now().toEpochMilli()));
public boolean isActive(Instant now) {
return members.stream().anyMatch(x -> x.timerange().contains(now.toEpochMilli()));
}

@Override
public String toString() {
return String.format("NonConsecutiveBestpriceResult with %s", members.toString());
}

private void sort() {
if (!sorted) {
members.sort(Comparator.comparingLong(p -> p.timerange().start()));
}
}

@Override
public String getHours() {
boolean second = false;
sort();
StringBuilder res = new StringBuilder();

for (AwattarPrice price : members) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import static org.openhab.binding.awattar.internal.AwattarUtil.getDuration;
import static org.openhab.binding.awattar.internal.AwattarUtil.getMillisToNextMinute;

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
Expand Down Expand Up @@ -163,7 +162,7 @@ public void refreshChannel(ChannelUID channelUID) {
long diff;
switch (channelId) {
case CHANNEL_ACTIVE:
state = OnOffType.from(result.isActive());
state = OnOffType.from(result.isActive(getNow(zoneId).toInstant()));
break;
case CHANNEL_START:
state = getDateTimeType(result.getStart(), timeZoneProvider);
Expand All @@ -172,16 +171,16 @@ public void refreshChannel(ChannelUID channelUID) {
state = getDateTimeType(result.getEnd(), timeZoneProvider);
break;
case CHANNEL_COUNTDOWN:
diff = result.getStart() - Instant.now().toEpochMilli();
diff = result.getStart() - getNow(zoneId).toInstant().toEpochMilli();
if (diff >= 0) {
state = getDuration(diff);
} else {
state = QuantityType.valueOf(0, Units.MINUTE);
}
break;
case CHANNEL_REMAINING:
if (result.isActive()) {
diff = result.getEnd() - Instant.now().toEpochMilli();
if (result.isActive(getNow(zoneId).toInstant())) {
diff = result.getEnd() - getNow(zoneId).toInstant().toEpochMilli();
state = getDuration(diff);
} else {
state = QuantityType.valueOf(0, Units.MINUTE);
Expand Down Expand Up @@ -216,20 +215,49 @@ private List<AwattarPrice> getPriceRange(AwattarBridgeHandler bridgeHandler, Tim
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) {
ZonedDateTime startCal = getCalendarForHour(start, zoneId);
ZonedDateTime endCal = startCal.plusHours(duration);
ZonedDateTime now = ZonedDateTime.now(zoneId);
ZonedDateTime startTime = getStarTime(start, zoneId);
ZonedDateTime endTime = startTime.plusHours(duration);
ZonedDateTime now = getNow(zoneId);
if (now.getHour() < start) {
// we are before the range, so we might be still within the last range
startCal = startCal.minusDays(1);
endCal = endCal.minusDays(1);
startTime = startTime.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
startCal = startCal.plusDays(1);
endCal = endCal.plusDays(1);
startTime = startTime.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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
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_HOURS;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_REMAINING;
import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_START;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -58,6 +62,8 @@
import org.openhab.core.config.core.Configuration;
import org.openhab.core.i18n.TimeZoneProvider;
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.test.java.JavaTest;
import org.openhab.core.thing.Bridge;
Expand Down Expand Up @@ -166,31 +172,98 @@ void testContainsPriceForRange() {

public static Stream<Arguments> testBestpriceHandler() {
return Stream.of( //
Arguments.of(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(1, true, CHANNEL_HOURS, new StringType("14")),
Arguments.of(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(1, false, CHANNEL_HOURS, new StringType("14")),
Arguments.of(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(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(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, 1, true, CHANNEL_START, new DateTimeType("2024-06-15T14:00:00.000+0200")),
Arguments.of(24, 1, true, CHANNEL_END, new DateTimeType("2024-06-15T15:00:00.000+0200")),
Arguments.of(24, 1, true, CHANNEL_HOURS, new StringType("14")),
Arguments.of(24, 1, false, CHANNEL_START, new DateTimeType("2024-06-15T14:00:00.000+0200")),
Arguments.of(24, 1, false, CHANNEL_END, new DateTimeType("2024-06-15T15:00:00.000+0200")),
Arguments.of(24, 1, false, CHANNEL_HOURS, new StringType("14")),
Arguments.of(24, 2, true, CHANNEL_START, new DateTimeType("2024-06-15T13:00:00.000+0200")),
Arguments.of(24, 2, true, CHANNEL_END, new DateTimeType("2024-06-15T15:00:00.000+0200")),
Arguments.of(24, 2, true, CHANNEL_HOURS, new StringType("13,14")),
Arguments.of(24, 2, false, CHANNEL_START, new DateTimeType("2024-06-15T13:00:00.000+0200")),
Arguments.of(24, 2, false, CHANNEL_END, new DateTimeType("2024-06-15T15:00:00.000+0200")),
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
@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");
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));

AwattarBestPriceHandler handler = new AwattarBestPriceHandler(bestpriceMock, timeZoneProviderMock) {
@Override
protected TimeRange getRange(int start, int duration, ZoneId zoneId) {
return new TimeRange(1718402400000L, 1718488800000L);
protected ZonedDateTime getStarTime(int start, ZoneId zoneId) {
return ZonedDateTime.of(2024, 6, 15, 12, 0, 0, 0, zoneId);
}

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);
}
};

Expand Down

0 comments on commit 074b441

Please sign in to comment.