diff options
author | gjoranv <gjoranv@gmail.com> | 2023-10-24 11:31:21 +0200 |
---|---|---|
committer | gjoranv <gjoranv@gmail.com> | 2023-10-24 11:31:21 +0200 |
commit | 019d35078421a33e579992aca13dbe6f14ca67bf (patch) | |
tree | 552fe07416191476f017b5a55365525d96635622 | |
parent | ce3f04a365d8d655fafd8fe43c5ffa6868db8cdd (diff) |
Add bill status logic.
2 files changed, 115 insertions, 0 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/StatusHistory.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/StatusHistory.java index 6335ada1396..f0c7f806c8c 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/StatusHistory.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/StatusHistory.java @@ -14,6 +14,17 @@ public class StatusHistory { SortedMap<ZonedDateTime, BillStatus> history; public StatusHistory(SortedMap<ZonedDateTime, BillStatus> history) { + // Validate the given history + var iter = history.values().iterator(); + BillStatus next = iter.hasNext() ? iter.next() : null; + while (iter.hasNext()) { + var current = next; + next = iter.next(); + if (! validateStatus(current, next)) { + throw new IllegalArgumentException("Invalid transition from " + current + " to " + next); + } + } + this.history = history; } @@ -32,4 +43,19 @@ public class StatusHistory { return history; } + public void checkValidTransition(BillStatus newStatus) { + if (! validateStatus(current(), newStatus)) { + throw new IllegalArgumentException("Invalid transition from " + current() + " to " + newStatus); + } + } + + private static boolean validateStatus(BillStatus current, BillStatus newStatus) { + return switch(current) { + case OPEN -> true; + case FROZEN -> newStatus != BillStatus.OPEN; // This could be subject to change. + case CLOSED -> newStatus == BillStatus.CLOSED; + case VOID -> newStatus == BillStatus.VOID; + }; + } + } diff --git a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/integration/billing/StatusHistoryTest.java b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/integration/billing/StatusHistoryTest.java new file mode 100644 index 00000000000..46a4c7e199c --- /dev/null +++ b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/integration/billing/StatusHistoryTest.java @@ -0,0 +1,89 @@ +package com.yahoo.vespa.hosted.controller.api.integration.billing; + +import org.junit.jupiter.api.Test; + +import java.time.Clock; +import java.time.ZonedDateTime; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * @author gjoranv + */ +public class StatusHistoryTest { + + private final Clock clock = Clock.systemUTC(); + + @Test + void open_can_change_to_any_status() { + var history = StatusHistory.open(clock); + history.checkValidTransition(BillStatus.FROZEN); + history.checkValidTransition(BillStatus.CLOSED); + history.checkValidTransition(BillStatus.VOID); + } + + @Test + void frozen_cannot_change_to_open() { + var history = new StatusHistory(historyWith(BillStatus.FROZEN)); + + history.checkValidTransition(BillStatus.CLOSED); + history.checkValidTransition(BillStatus.VOID); + + assertThrows(IllegalArgumentException.class, () -> history.checkValidTransition(BillStatus.OPEN)); + } + + @Test + void closed_cannot_change() { + var history = new StatusHistory(historyWith(BillStatus.CLOSED)); + + assertThrows(IllegalArgumentException.class, () -> history.checkValidTransition(BillStatus.OPEN)); + assertThrows(IllegalArgumentException.class, () -> history.checkValidTransition(BillStatus.FROZEN)); + assertThrows(IllegalArgumentException.class, () -> history.checkValidTransition(BillStatus.VOID)); + } + + @Test + void void_cannot_change() { + var history = new StatusHistory(historyWith(BillStatus.VOID)); + + assertThrows(IllegalArgumentException.class, () -> history.checkValidTransition(BillStatus.OPEN)); + assertThrows(IllegalArgumentException.class, () -> history.checkValidTransition(BillStatus.FROZEN)); + assertThrows(IllegalArgumentException.class, () -> history.checkValidTransition(BillStatus.CLOSED)); + } + + @Test + void any_status_can_change_to_itself() { + var history = new StatusHistory(historyWith(BillStatus.OPEN)); + history.checkValidTransition(BillStatus.OPEN); + + history = new StatusHistory(historyWith(BillStatus.FROZEN)); + history.checkValidTransition(BillStatus.FROZEN); + + history = new StatusHistory(historyWith(BillStatus.CLOSED)); + history.checkValidTransition(BillStatus.CLOSED); + + history = new StatusHistory(historyWith(BillStatus.VOID)); + history.checkValidTransition(BillStatus.VOID); + } + + @Test + void it_validates_status_history_in_constructor() { + assertThrows(IllegalArgumentException.class, () -> new StatusHistory(historyWith(BillStatus.FROZEN, BillStatus.OPEN))); + assertThrows(IllegalArgumentException.class, () -> new StatusHistory(historyWith(BillStatus.CLOSED, BillStatus.OPEN))); + assertThrows(IllegalArgumentException.class, () -> new StatusHistory(historyWith(BillStatus.CLOSED, BillStatus.FROZEN))); + assertThrows(IllegalArgumentException.class, () -> new StatusHistory(historyWith(BillStatus.CLOSED, BillStatus.VOID))); + assertThrows(IllegalArgumentException.class, () -> new StatusHistory(historyWith(BillStatus.VOID, BillStatus.OPEN))); + assertThrows(IllegalArgumentException.class, () -> new StatusHistory(historyWith(BillStatus.VOID, BillStatus.FROZEN))); + assertThrows(IllegalArgumentException.class, () -> new StatusHistory(historyWith(BillStatus.VOID, BillStatus.CLOSED))); + } + + private SortedMap<ZonedDateTime, BillStatus> historyWith(BillStatus... statuses) { + var history = new TreeMap<>(Map.of(ZonedDateTime.now(clock), BillStatus.OPEN)); + for (var status : statuses) { + history.put(ZonedDateTime.now(clock), status); + } + return history; + } +} |