Files
purpur-quest-board/src/test/java/games/dmg/spigotquestboard/QuestBoardDialogUiTest.java
T
dmg be13b9b87c
Release / release (push) Successful in 2m8s
CI / build (push) Successful in 1m15s
feat(admin): control player quest commands
2026-09-05 08:36:45 -04:00

279 lines
10 KiB
Java

package games.dmg.spigotquestboard;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import java.io.IOException;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.List;
import java.util.UUID;
import org.bukkit.entity.Player;
import org.junit.jupiter.api.Test;
final class QuestBoardDialogUiTest {
private static final Instant NOW = Instant.parse("2026-09-05T03:00:00Z");
@Test
void disabledPlayerCommandsDoNotGateBoardUiGateways() throws Exception {
PlayerCommandSettings settings = new PlayerCommandSettings(
new PlayerCommandSettingsRepository() {
@Override public boolean loadEnabled() { return false; }
@Override public void saveEnabled(boolean enabled) { }
}
);
RecordingCreator creator = new RecordingCreator(false);
RecordingCompleter completer = new RecordingCompleter();
RecordingCanceller canceller = new RecordingCanceller();
java.util.concurrent.atomic.AtomicReference<Player> claimantPlayer =
new java.util.concurrent.atomic.AtomicReference<>();
QuestClaimGateway claimant = player -> {
claimantPlayer.set(player);
return new ClaimCollectionResult(0, 0);
};
QuestBoardDialogUi ui = new QuestBoardDialogUi(
creator, now -> List.of(creator.quest(NOW)), completer, canceller, claimant,
Clock.fixed(NOW, ZoneOffset.UTC)
);
Player player = mock(Player.class);
ui.listingText();
ui.submit(player, "stone", "1");
ui.submitCompletion(player, "complete-id");
ui.submitCancellation(player, "cancel-id");
ui.submitClaim(player);
org.junit.jupiter.api.Assertions.assertFalse(settings.enabled());
assertEquals("stone", creator.material);
assertEquals(player, completer.player);
assertEquals(player, canceller.player);
assertEquals(player, claimantPlayer.get());
}
@Test
void dialogSubmissionUsesEquivalentCreationFlow() {
RecordingCreator creator = new RecordingCreator(false);
QuestBoardDialogUi ui = new QuestBoardDialogUi(
creator, now -> List.of(), Clock.fixed(NOW, ZoneOffset.UTC)
);
Player player = mock(Player.class);
ui.submit(player, "stone", "64");
assertEquals("stone", creator.material);
assertEquals(64, creator.quantity);
assertEquals(NOW, creator.createdAt);
verify(player).sendMessage(
"Quest 00000000-0000-0000-0000-000000000010 created. "
+ "Your exact held stack is now escrowed."
);
}
@Test
void completionActionUsesEquivalentCompletionFlow() {
RecordingCreator creator = new RecordingCreator(false);
RecordingCompleter completer = new RecordingCompleter();
QuestBoardDialogUi ui = new QuestBoardDialogUi(
creator, now -> List.of(), completer, Clock.fixed(NOW, ZoneOffset.UTC)
);
Player player = mock(Player.class);
String id = UUID.randomUUID().toString();
ui.submitCompletion(player, id);
assertEquals(player, completer.player);
assertEquals(id, completer.questId);
assertEquals(NOW, completer.completedAt);
}
@Test
void claimActionUsesEquivalentCollectionFlowAtABoard() {
RecordingCreator creator = new RecordingCreator(false);
java.util.concurrent.atomic.AtomicReference<Player> claimantPlayer =
new java.util.concurrent.atomic.AtomicReference<>();
QuestClaimGateway claimant = player -> {
claimantPlayer.set(player);
return new ClaimCollectionResult(1, 0);
};
QuestBoardDialogUi ui = new QuestBoardDialogUi(
creator, now -> List.of(), null, null, claimant,
Clock.fixed(NOW, ZoneOffset.UTC)
);
Player player = mock(Player.class);
ui.submitClaim(player);
assertEquals(player, claimantPlayer.get());
}
@Test
void cancellationActionUsesEquivalentCancellationFlow() {
RecordingCreator creator = new RecordingCreator(false);
RecordingCanceller canceller = new RecordingCanceller();
QuestBoardDialogUi ui = new QuestBoardDialogUi(
creator, now -> List.of(), null, canceller, Clock.fixed(NOW, ZoneOffset.UTC)
);
Player player = mock(Player.class);
String id = UUID.randomUUID().toString();
ui.submitCancellation(player, id);
assertEquals(player, canceller.player);
assertEquals(id, canceller.questId);
assertEquals(NOW, canceller.cancelledAt);
}
@Test
void boardListingDistinguishesDeliveredBlocksFromReturnedRewards() {
RecordingCreator creator = new RecordingCreator(false);
Player player = mock(Player.class);
UUID owner = UUID.randomUUID();
org.mockito.Mockito.when(player.getUniqueId()).thenReturn(owner);
QuestClaimGateway claimant = new QuestClaimGateway() {
@Override
public ClaimCollectionResult collect(Player ignored) {
return new ClaimCollectionResult(0, 0);
}
@Override
public List<QuestClaim> pendingClaims(Player ignored) {
return List.of(
new QuestClaim(
UUID.randomUUID(), UUID.randomUUID(), owner,
List.of(new EscrowItem("STONE", 2, null)), NOW,
QuestClaimSource.COMPLETION
),
new QuestClaim(
UUID.randomUUID(), UUID.randomUUID(), owner,
List.of(new EscrowItem("DIAMOND", 1, null)), NOW,
QuestClaimSource.EXPIRATION
)
);
}
};
QuestBoardDialogUi ui = new QuestBoardDialogUi(
creator, now -> List.of(), null, null, claimant,
Clock.fixed(NOW, ZoneOffset.UTC)
);
String listing = ui.claimListingText(player);
org.junit.jupiter.api.Assertions.assertTrue(listing.contains("DELIVERED BLOCKS — 2 STONE"));
org.junit.jupiter.api.Assertions.assertTrue(
listing.contains("RETURNED REWARD (EXPIRATION) — 1 DIAMOND")
);
}
@Test
void boardOnlyOffersCancellationActionsForPlayersOwnedActiveQuests() {
RecordingCreator creator = new RecordingCreator(false);
Quest owned = creator.quest(NOW);
Quest other = new Quest(
UUID.randomUUID(), UUID.randomUUID(), "Other", "DIRT", 1,
List.of(new EscrowItem("COAL", 1, null)), NOW, NOW.plusSeconds(604800)
);
QuestBoardDialogUi ui = new QuestBoardDialogUi(
creator, now -> List.of(owned, other), null, new RecordingCanceller(),
Clock.fixed(NOW, ZoneOffset.UTC)
);
Player issuer = mock(Player.class);
org.mockito.Mockito.when(issuer.getUniqueId()).thenReturn(owned.issuerId());
assertEquals(List.of(owned.id().toString()), ui.cancellableQuestIds(issuer));
}
@Test
void everyOpeningBuildsAListingFromTheCurrentActiveQuests() {
RecordingCreator creator = new RecordingCreator(false);
Quest active = creator.quest(NOW);
QuestBoardDialogUi ui = new QuestBoardDialogUi(
creator, now -> List.of(active), Clock.fixed(NOW, ZoneOffset.UTC)
);
assertEquals(QuestListingFormatter.format(active, NOW), ui.listingText());
}
@Test
void persistenceFailureExplainsThatRewardWasRestored() {
QuestBoardDialogUi ui = new QuestBoardDialogUi(
new RecordingCreator(true), now -> List.of(), Clock.fixed(NOW, ZoneOffset.UTC)
);
Player player = mock(Player.class);
ui.submit(player, "stone", "64");
verify(player).sendMessage(
"The quest could not be saved. Your held reward was restored."
);
}
private static final class RecordingCanceller implements QuestCancellationGateway {
private Player player;
private String questId;
private Instant cancelledAt;
@Override
public QuestClaim cancel(Player cancellingPlayer, String id, Instant instant) {
player = cancellingPlayer;
questId = id;
cancelledAt = instant;
return null;
}
}
private static final class RecordingCompleter implements QuestCompletionGateway {
private Player player;
private String questId;
private Instant completedAt;
@Override
public QuestCompletion complete(Player completingPlayer, String id, Instant instant) {
player = completingPlayer;
questId = id;
completedAt = instant;
return null;
}
}
private static final class RecordingCreator implements QuestCreationGateway {
private final boolean fail;
private String material;
private int quantity;
private Instant createdAt;
private RecordingCreator(boolean fail) {
this.fail = fail;
}
@Override
public Quest create(
Player player, String requestedMaterial, int requestedAmount, Instant instant
) throws IOException {
if (fail) {
throw new IOException("disk full");
}
material = requestedMaterial;
quantity = requestedAmount;
createdAt = instant;
return quest(instant);
}
private Quest quest(Instant instant) {
return new Quest(
UUID.fromString("00000000-0000-0000-0000-000000000010"),
UUID.fromString("00000000-0000-0000-0000-000000000001"),
"Issuer", "STONE", 64,
List.of(new EscrowItem("DIAMOND", 1, null)),
instant, instant.plusSeconds(604800)
);
}
@Override
public List<String> suggestBlockMaterials(String prefix) {
return List.of();
}
}
}