From 600d14406f01fb2c6ec2c06c8d2dee59ed4bde2f Mon Sep 17 00:00:00 2001
From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com>
Date: Thu, 24 Sep 2026 23:11:09 +0000
Subject: [PATCH 1/3] fix: stop node deactivation when refunds cannot progress
---
.../net/tfminecraft/dowsing/objects/Node.java | 3 +
.../dowsing/objects/NodeDeactivationTest.java | 111 ++++++++++++++++++
2 files changed, 114 insertions(+)
create mode 100644 src/test/java/net/tfminecraft/dowsing/objects/NodeDeactivationTest.java
diff --git a/src/main/java/net/tfminecraft/dowsing/objects/Node.java b/src/main/java/net/tfminecraft/dowsing/objects/Node.java
index 9da7d0a..eab9fb2 100644
--- a/src/main/java/net/tfminecraft/dowsing/objects/Node.java
+++ b/src/main/java/net/tfminecraft/dowsing/objects/Node.java
@@ -461,7 +461,10 @@ public void deActivate() {
if(isClaimable()) return;
if(this.cycleTime > 0 && this.cycleTime < Cache.cycleLength) {
while(this.getInputCounter() > 0) {
+ int pendingInputs = this.getInputCounter();
refund();
+ // A missing barrel or hopper prevents refunds. Keep the pending inputs for retry.
+ if(this.getInputCounter() >= pendingInputs) break;
}
}
NodeManager.requestNodeBenefitSync();
diff --git a/src/test/java/net/tfminecraft/dowsing/objects/NodeDeactivationTest.java b/src/test/java/net/tfminecraft/dowsing/objects/NodeDeactivationTest.java
new file mode 100644
index 0000000..f499140
--- /dev/null
+++ b/src/test/java/net/tfminecraft/dowsing/objects/NodeDeactivationTest.java
@@ -0,0 +1,111 @@
+package net.tfminecraft.dowsing.objects;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.UUID;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import net.tfminecraft.dowsing.Cache;
+
+class NodeDeactivationTest {
+
+ private Integer previousCycleLength;
+
+ @BeforeEach
+ void setCycleLength() {
+ previousCycleLength = Cache.cycleLength;
+ Cache.cycleLength = 60;
+ }
+
+ @AfterEach
+ void restoreCycleLength() {
+ Cache.cycleLength = previousCycleLength;
+ }
+
+ @Test
+ void failedRefundStopsAndKeepsPendingInputsForRetry() {
+ RefundNode node = new RefundNode(3, 0);
+
+ node.deActivate();
+
+ assertFalse(node.getIsActive());
+ assertEquals(1, node.attempts);
+ assertEquals(3, node.getInputCounter());
+
+ node.successfulRefundsRemaining = 3;
+ node.deActivate();
+
+ assertEquals(4, node.attempts);
+ assertEquals(0, node.getInputCounter());
+ }
+
+ @Test
+ void partialRefundStopsAtFirstFailure() {
+ RefundNode node = new RefundNode(3, 1);
+
+ node.deActivate();
+
+ assertFalse(node.getIsActive());
+ assertEquals(2, node.attempts);
+ assertEquals(2, node.getInputCounter());
+ }
+
+ @Test
+ void successfulRefundsDrainPendingInputs() {
+ RefundNode node = new RefundNode(3, 3);
+
+ node.deActivate();
+
+ assertFalse(node.getIsActive());
+ assertEquals(3, node.attempts);
+ assertEquals(0, node.getInputCounter());
+ }
+
+ @Test
+ void noPendingInputsSkipsRefunds() {
+ RefundNode node = new RefundNode(0, 0);
+
+ node.deActivate();
+
+ assertFalse(node.getIsActive());
+ assertEquals(0, node.attempts);
+ }
+
+ private static final class RefundNode extends Node {
+ private int attempts;
+ private int successfulRefundsRemaining;
+ private final int maximumAttempts;
+
+ RefundNode(int pendingInputs, int successfulRefunds) {
+ super(UUID.randomUUID(), null, null, null, true, 1, 1, null, 10, pendingInputs, 50);
+ successfulRefundsRemaining = successfulRefunds;
+ maximumAttempts = pendingInputs + 1;
+ }
+
+ @Override
+ public boolean isClaimable() {
+ return false;
+ }
+
+ @Override
+ public void update() {
+ // No world or production configuration is needed for the refund loop.
+ }
+
+ @Override
+ public void refund() {
+ // Fail deterministically on the old loop instead of hanging the test JVM.
+ assertTrue(++attempts <= maximumAttempts, "Deactivation kept retrying a failed refund");
+ if (successfulRefundsRemaining > 0) {
+ successfulRefundsRemaining--;
+ setInputCounter(getInputCounter() - 1);
+ }
+ // Missing barrel/hopper returns without consuming a pending input.
+ }
+ }
+}
From 863fbbc42394a469b165e984f7b7228e0429d3f1 Mon Sep 17 00:00:00 2001
From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com>
Date: Thu, 24 Sep 2026 23:23:01 +0000
Subject: [PATCH 2/3] fix: make pending node refunds retryable before edits
---
pom.xml | 6 +
.../dowsing/managers/InventoryManager.java | 8 +-
.../dowsing/managers/NodeManager.java | 14 ++
.../net/tfminecraft/dowsing/objects/Node.java | 9 ++
.../dowsing/managers/NodeRefundMenuTest.java | 133 ++++++++++++++++++
.../dowsing/objects/NodeDeactivationTest.java | 27 ++++
6 files changed, 196 insertions(+), 1 deletion(-)
create mode 100644 src/test/java/net/tfminecraft/dowsing/managers/NodeRefundMenuTest.java
diff --git a/pom.xml b/pom.xml
index e8a3e6d..a8cd1c4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -91,6 +91,12 @@
5.14.0
test
+
+ org.mockito
+ mockito-core
+ 5.20.0
+ test
+
net.tfminecraft
magic
diff --git a/src/main/java/net/tfminecraft/dowsing/managers/InventoryManager.java b/src/main/java/net/tfminecraft/dowsing/managers/InventoryManager.java
index 21a5027..4b45d2b 100644
--- a/src/main/java/net/tfminecraft/dowsing/managers/InventoryManager.java
+++ b/src/main/java/net/tfminecraft/dowsing/managers/InventoryManager.java
@@ -334,7 +334,13 @@ ItemStack createStatus(Node n) {
i.setType(Material.RED_STAINED_GLASS_PANE);
ItemMeta m = i.getItemMeta();
m.setDisplayName("§cINACTIVE");
- if(n.getErrors().size() > 0) {
+ if(n.hasPendingRefund()) {
+ m.setDisplayName("§eRetry Refund");
+ List lore = new ArrayList<>(n.getErrors());
+ lore.add("§7Restore the barrel and hopper, then click to retry.");
+ lore.add("§7Refunds must finish before changing or activating this node.");
+ m.setLore(lore);
+ } else if(n.getErrors().size() > 0) {
m.setLore(n.getErrors());
}
i.setItemMeta(m);
diff --git a/src/main/java/net/tfminecraft/dowsing/managers/NodeManager.java b/src/main/java/net/tfminecraft/dowsing/managers/NodeManager.java
index e916ca5..269f210 100644
--- a/src/main/java/net/tfminecraft/dowsing/managers/NodeManager.java
+++ b/src/main/java/net/tfminecraft/dowsing/managers/NodeManager.java
@@ -231,6 +231,7 @@ public void validate() {
}
}
public void confirmClick(Player p, Node n, ConfirmType t) {
+ if(t != ConfirmType.DEACTIVATE && blockPendingRefund(p, n)) return;
if(t.equals(ConfirmType.DEACTIVATE)) {
n.deActivate();
InventoryManager inv = new InventoryManager();
@@ -254,6 +255,11 @@ public void confirmClick(Player p, Node n, ConfirmType t) {
currentNode.put(p, n);
}
}
+ private boolean blockPendingRefund(Player p, Node n) {
+ if(!n.hasPendingRefund()) return false;
+ p.sendMessage("§cRestore the barrel and hopper, then click Retry Refund before changing this node.");
+ return true;
+ }
@EventHandler(ignoreCancelled = true)
public void placeVanillaNode(BlockPlaceEvent e) {
NodeBlock b = BlockLoader.getByBlock(e.getBlock().getType());
@@ -399,6 +405,7 @@ public void invenClick(InventoryClickEvent e) {
p.playSound(p.getLocation(), Sound.ENTITY_VILLAGER_NO, 1f, 1f);
return;
}
+ if(e.getSlot() != 17 && blockPendingRefund(p, n)) return;
if(e.getSlot() == 8) {
if(n.getIsActive()) {
p.sendMessage("§cCannot upgrade while node is active");
@@ -436,6 +443,11 @@ public void invenClick(InventoryClickEvent e) {
if(n.getIsActive()) {
confirm.put(p, ConfirmType.DEACTIVATE);
inv.confirmView(p);
+ } else if(n.hasPendingRefund()) {
+ n.deActivate();
+ p.sendMessage(n.hasPendingRefund()
+ ? "§cRefund still pending. Restore the barrel and hopper, then retry."
+ : "§aRefund complete. The node remains inactive.");
} else {
n.activate();
if(n.getIsActive()) {
@@ -482,6 +494,7 @@ public void invenClick(InventoryClickEvent e) {
}
} else if(currentSlot.get(p) != null && e.getView().getTitle().equalsIgnoreCase("§7"+n.getBlock().getResource()+" Node: "+WordUtils.capitalize(currentSlot.get(p).getId().replace("_", " ")))) {
e.setCancelled(true);
+ if(e.getSlot() != 26 && blockPendingRefund(p, n)) return;
if(!n.hasGuild()) {
n.breakNode();
nodes.remove(n);
@@ -517,6 +530,7 @@ public void invenClick(InventoryClickEvent e) {
currentNode.put(p, n);
} else if(e.getView().getTitle().equalsIgnoreCase("§7"+n.getBlock().getResource()+" Node: Type")) {
e.setCancelled(true);
+ if(e.getSlot() != 26 && blockPendingRefund(p, n)) return;
if(!n.hasGuild()) {
n.breakNode();
nodes.remove(n);
diff --git a/src/main/java/net/tfminecraft/dowsing/objects/Node.java b/src/main/java/net/tfminecraft/dowsing/objects/Node.java
index eab9fb2..fa601cf 100644
--- a/src/main/java/net/tfminecraft/dowsing/objects/Node.java
+++ b/src/main/java/net/tfminecraft/dowsing/objects/Node.java
@@ -404,6 +404,10 @@ public void refund() {
ng.refund(this);;
}
public void activate() {
+ if(hasPendingRefund()) {
+ deActivate();
+ return;
+ }
if(isClaimable()) return;
NodeEngine ng = new NodeEngine();
Boolean failed = false;
@@ -456,6 +460,11 @@ public void activate() {
ng.takeInputs(this);
NodeManager.requestNodeBenefitSync();
}
+ /** Whether an inactive node still owes refunds from an interrupted cycle. */
+ public boolean hasPendingRefund() {
+ return !this.isActive && this.inputCounter > 0
+ && this.cycleTime > 0 && this.cycleTime < Cache.cycleLength;
+ }
public void deActivate() {
this.isActive = false;
if(isClaimable()) return;
diff --git a/src/test/java/net/tfminecraft/dowsing/managers/NodeRefundMenuTest.java b/src/test/java/net/tfminecraft/dowsing/managers/NodeRefundMenuTest.java
new file mode 100644
index 0000000..ddf52e8
--- /dev/null
+++ b/src/test/java/net/tfminecraft/dowsing/managers/NodeRefundMenuTest.java
@@ -0,0 +1,133 @@
+package net.tfminecraft.dowsing.managers;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.InventoryView;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import net.tfminecraft.dowsing.enums.ConfirmType;
+import net.tfminecraft.dowsing.objects.Node;
+import net.tfminecraft.dowsing.objects.NodeBlock;
+import net.tfminecraft.dowsing.objects.NodeSlot;
+
+class NodeRefundMenuTest {
+ private NodeManager manager;
+ private Node node;
+ private Player player;
+ private InventoryClickEvent click;
+ private InventoryView view;
+
+ @BeforeEach
+ void setUp() {
+ manager = new NodeManager();
+ node = mock(Node.class);
+ player = mock(Player.class);
+ click = mock(InventoryClickEvent.class);
+ view = mock(InventoryView.class);
+ NodeBlock block = mock(NodeBlock.class);
+ when(node.getBlock()).thenReturn(block);
+ when(block.getResource()).thenReturn("Iron");
+ when(node.hasGuild()).thenReturn(true);
+ when(node.hasPendingRefund()).thenReturn(true);
+ when(node.getIsActive()).thenReturn(false);
+ when(player.hasPermission("dowsing.admin")).thenReturn(true);
+ when(click.getWhoClicked()).thenReturn(player);
+ when(click.getView()).thenReturn(view);
+ when(view.getTitle()).thenReturn("§7Iron Node");
+ when(click.getClickedInventory()).thenReturn(mock(Inventory.class));
+ manager.currentNode.put(player, node);
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {6, 8, 9, 18, 24, 26, 10})
+ void pendingRefundBlocksMainMenuChanges(int slot) {
+ when(click.getSlot()).thenReturn(slot);
+
+ manager.invenClick(click);
+
+ verify(click).setCancelled(true);
+ verify(player).sendMessage(contains("Retry Refund"));
+ verify(node, never()).setGuild(any());
+ verify(node, never()).breakNode();
+ verify(node, never()).activate();
+ }
+
+ @ParameterizedTest
+ @EnumSource(value = ConfirmType.class, names = {"DELETE_NODE", "CHANGE_TYPE"})
+ void pendingRefundBlocksStaleConfirmation(ConfirmType action) {
+ manager.confirmClick(player, node, action);
+
+ verify(player).sendMessage(contains("Retry Refund"));
+ verify(node, never()).breakNode();
+ verify(node, never()).setCurrentType(any());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"§7Iron Node: Type", "§7Iron Node: Fuel"})
+ void pendingRefundBlocksAlreadyOpenSubmenus(String title) {
+ NodeSlot slot = mock(NodeSlot.class);
+ when(slot.getId()).thenReturn("fuel");
+ manager.currentSlot.put(player, slot);
+ when(view.getTitle()).thenReturn(title);
+
+ manager.invenClick(click);
+
+ verify(click).setCancelled(true);
+ verify(player).sendMessage(contains("Retry Refund"));
+ verify(slot, never()).setActivePm(any());
+ }
+
+ @Test
+ void statusButtonRetriesRefundWithoutActivating() {
+ when(click.getSlot()).thenReturn(17);
+
+ manager.invenClick(click);
+
+ verify(node).deActivate();
+ verify(node, never()).activate();
+ verify(player).sendMessage(contains("Refund still pending"));
+ }
+
+ @Test
+ void successfulRetryLeavesNodeInactive() {
+ when(click.getSlot()).thenReturn(17);
+ when(node.hasPendingRefund()).thenReturn(true, false);
+
+ manager.invenClick(click);
+
+ verify(node).deActivate();
+ verify(node, never()).activate();
+ verify(player).sendMessage(contains("Refund complete"));
+ }
+
+ @Test
+ void statusButtonStillActivatesWhenNoRefundIsPending() {
+ when(click.getSlot()).thenReturn(17);
+ when(node.hasPendingRefund()).thenReturn(false);
+ // Stop at the production boundary before Bukkit's server-backed sound registry is needed.
+ RuntimeException activationReached = new RuntimeException("Activation reached");
+ doThrow(activationReached).when(node).activate();
+
+ assertSame(activationReached, assertThrows(RuntimeException.class, () -> manager.invenClick(click)));
+
+ verify(node).activate();
+ verify(node, never()).deActivate();
+ verify(player, never()).sendMessage(anyString());
+ }
+}
diff --git a/src/test/java/net/tfminecraft/dowsing/objects/NodeDeactivationTest.java b/src/test/java/net/tfminecraft/dowsing/objects/NodeDeactivationTest.java
index f499140..48f94e7 100644
--- a/src/test/java/net/tfminecraft/dowsing/objects/NodeDeactivationTest.java
+++ b/src/test/java/net/tfminecraft/dowsing/objects/NodeDeactivationTest.java
@@ -36,12 +36,39 @@ void failedRefundStopsAndKeepsPendingInputsForRetry() {
assertFalse(node.getIsActive());
assertEquals(1, node.attempts);
assertEquals(3, node.getInputCounter());
+ assertTrue(node.hasPendingRefund());
node.successfulRefundsRemaining = 3;
node.deActivate();
assertEquals(4, node.attempts);
assertEquals(0, node.getInputCounter());
+ assertFalse(node.hasPendingRefund());
+ }
+
+ @Test
+ void activationRetriesPendingRefundWithoutStartingANewCycle() {
+ RefundNode node = new RefundNode(3, 0);
+ node.deActivate();
+ node.successfulRefundsRemaining = 3;
+
+ node.activate();
+
+ assertEquals(0, node.getInputCounter());
+ assertFalse(node.getIsActive());
+ assertFalse(node.hasPendingRefund());
+ }
+
+ @Test
+ void onlyInterruptedInactiveCyclesHavePendingRefunds() {
+ RefundNode node = new RefundNode(3, 0);
+ assertFalse(node.hasPendingRefund());
+ node.isActive = false;
+ assertTrue(node.hasPendingRefund());
+ node.setCycleTime(0);
+ assertFalse(node.hasPendingRefund());
+ node.setCycleTime(Cache.cycleLength);
+ assertFalse(node.hasPendingRefund());
}
@Test
From bf11534d0dcdbbfaba8ad7c4fdc6db4672aa30f4 Mon Sep 17 00:00:00 2001
From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com>
Date: Thu, 24 Sep 2026 23:33:12 +0000
Subject: [PATCH 3/3] fix: guard node menu clicks and refund initial cycle
inputs
---
.../dowsing/managers/NodeManager.java | 4 ++++
.../net/tfminecraft/dowsing/objects/Node.java | 4 ++--
.../dowsing/managers/NodeRefundMenuTest.java | 24 ++++++++++++++++++-
.../dowsing/objects/NodeDeactivationTest.java | 16 +++++++++----
4 files changed, 41 insertions(+), 7 deletions(-)
diff --git a/src/main/java/net/tfminecraft/dowsing/managers/NodeManager.java b/src/main/java/net/tfminecraft/dowsing/managers/NodeManager.java
index 269f210..0b9e595 100644
--- a/src/main/java/net/tfminecraft/dowsing/managers/NodeManager.java
+++ b/src/main/java/net/tfminecraft/dowsing/managers/NodeManager.java
@@ -393,6 +393,7 @@ public void invenClick(InventoryClickEvent e) {
InventoryManager inv = new InventoryManager();
if(e.getView().getTitle().equalsIgnoreCase("§7"+n.getBlock().getResource()+ " Node")) {
e.setCancelled(true);
+ if(e.getClickedInventory() != e.getView().getTopInventory()) return;
if(!n.hasGuild()) {
n.breakNode();
nodes.remove(n);
@@ -494,6 +495,7 @@ public void invenClick(InventoryClickEvent e) {
}
} else if(currentSlot.get(p) != null && e.getView().getTitle().equalsIgnoreCase("§7"+n.getBlock().getResource()+" Node: "+WordUtils.capitalize(currentSlot.get(p).getId().replace("_", " ")))) {
e.setCancelled(true);
+ if(e.getClickedInventory() != e.getView().getTopInventory()) return;
if(e.getSlot() != 26 && blockPendingRefund(p, n)) return;
if(!n.hasGuild()) {
n.breakNode();
@@ -530,6 +532,7 @@ public void invenClick(InventoryClickEvent e) {
currentNode.put(p, n);
} else if(e.getView().getTitle().equalsIgnoreCase("§7"+n.getBlock().getResource()+" Node: Type")) {
e.setCancelled(true);
+ if(e.getClickedInventory() != e.getView().getTopInventory()) return;
if(e.getSlot() != 26 && blockPendingRefund(p, n)) return;
if(!n.hasGuild()) {
n.breakNode();
@@ -564,6 +567,7 @@ public void invenClick(InventoryClickEvent e) {
inv.confirmView(p);
} else if(e.getView().getTitle().equalsIgnoreCase("§7Confirm Action")) {
e.setCancelled(true);
+ if(e.getClickedInventory() != e.getView().getTopInventory()) return;
if(!n.hasGuild()) {
n.breakNode();
nodes.remove(n);
diff --git a/src/main/java/net/tfminecraft/dowsing/objects/Node.java b/src/main/java/net/tfminecraft/dowsing/objects/Node.java
index fa601cf..d1a86ee 100644
--- a/src/main/java/net/tfminecraft/dowsing/objects/Node.java
+++ b/src/main/java/net/tfminecraft/dowsing/objects/Node.java
@@ -463,12 +463,12 @@ public void activate() {
/** Whether an inactive node still owes refunds from an interrupted cycle. */
public boolean hasPendingRefund() {
return !this.isActive && this.inputCounter > 0
- && this.cycleTime > 0 && this.cycleTime < Cache.cycleLength;
+ && this.cycleTime >= 0 && this.cycleTime < Cache.cycleLength;
}
public void deActivate() {
this.isActive = false;
if(isClaimable()) return;
- if(this.cycleTime > 0 && this.cycleTime < Cache.cycleLength) {
+ if(this.cycleTime >= 0 && this.cycleTime < Cache.cycleLength) {
while(this.getInputCounter() > 0) {
int pendingInputs = this.getInputCounter();
refund();
diff --git a/src/test/java/net/tfminecraft/dowsing/managers/NodeRefundMenuTest.java b/src/test/java/net/tfminecraft/dowsing/managers/NodeRefundMenuTest.java
index ddf52e8..5a963b0 100644
--- a/src/test/java/net/tfminecraft/dowsing/managers/NodeRefundMenuTest.java
+++ b/src/test/java/net/tfminecraft/dowsing/managers/NodeRefundMenuTest.java
@@ -50,10 +50,32 @@ void setUp() {
when(click.getWhoClicked()).thenReturn(player);
when(click.getView()).thenReturn(view);
when(view.getTitle()).thenReturn("§7Iron Node");
- when(click.getClickedInventory()).thenReturn(mock(Inventory.class));
+ Inventory top = mock(Inventory.class);
+ when(click.getClickedInventory()).thenReturn(top);
+ when(view.getTopInventory()).thenReturn(top);
manager.currentNode.put(player, node);
}
+ @ParameterizedTest
+ @ValueSource(strings = {"§7Iron Node", "§7Iron Node: Type", "§7Iron Node: Fuel", "§7Confirm Action"})
+ void lowerInventoryClicksCannotTriggerNodeActions(String title) {
+ NodeSlot slot = mock(NodeSlot.class);
+ when(slot.getId()).thenReturn("fuel");
+ manager.currentSlot.put(player, slot);
+ when(view.getTitle()).thenReturn(title);
+ when(click.getSlot()).thenReturn(17);
+ when(click.getClickedInventory()).thenReturn(mock(Inventory.class));
+
+ manager.invenClick(click);
+
+ verify(click).setCancelled(true);
+ verify(node, never()).deActivate();
+ verify(node, never()).activate();
+ verify(node, never()).update();
+ verify(node, never()).breakNode();
+ verify(player, never()).sendMessage(anyString());
+ }
+
@ParameterizedTest
@ValueSource(ints = {6, 8, 9, 18, 24, 26, 10})
void pendingRefundBlocksMainMenuChanges(int slot) {
diff --git a/src/test/java/net/tfminecraft/dowsing/objects/NodeDeactivationTest.java b/src/test/java/net/tfminecraft/dowsing/objects/NodeDeactivationTest.java
index 48f94e7..d1ec256 100644
--- a/src/test/java/net/tfminecraft/dowsing/objects/NodeDeactivationTest.java
+++ b/src/test/java/net/tfminecraft/dowsing/objects/NodeDeactivationTest.java
@@ -9,6 +9,8 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import net.tfminecraft.dowsing.Cache;
@@ -27,9 +29,11 @@ void restoreCycleLength() {
Cache.cycleLength = previousCycleLength;
}
- @Test
- void failedRefundStopsAndKeepsPendingInputsForRetry() {
+ @ParameterizedTest
+ @ValueSource(ints = {0, 1})
+ void failedRefundStopsAndKeepsPendingInputsForRetry(int cycleTime) {
RefundNode node = new RefundNode(3, 0);
+ node.setCycleTime(cycleTime);
node.deActivate();
@@ -66,6 +70,8 @@ void onlyInterruptedInactiveCyclesHavePendingRefunds() {
node.isActive = false;
assertTrue(node.hasPendingRefund());
node.setCycleTime(0);
+ assertTrue(node.hasPendingRefund());
+ node.setCycleTime(-1);
assertFalse(node.hasPendingRefund());
node.setCycleTime(Cache.cycleLength);
assertFalse(node.hasPendingRefund());
@@ -82,9 +88,11 @@ void partialRefundStopsAtFirstFailure() {
assertEquals(2, node.getInputCounter());
}
- @Test
- void successfulRefundsDrainPendingInputs() {
+ @ParameterizedTest
+ @ValueSource(ints = {0, 1})
+ void successfulRefundsDrainPendingInputs(int cycleTime) {
RefundNode node = new RefundNode(3, 3);
+ node.setCycleTime(cycleTime);
node.deActivate();