feat(admin): control player quest commands
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
package games.dmg.spigotquestboard;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
final class PlayerCommandSettingsTest {
|
||||
@TempDir Path temporaryDirectory;
|
||||
|
||||
@Test
|
||||
void defaultsDisabledAndPersistsEnabledState() throws Exception {
|
||||
Path path = temporaryDirectory.resolve("settings.yml");
|
||||
PlayerCommandSettings settings = new PlayerCommandSettings(
|
||||
new YamlPlayerCommandSettingsRepository(path)
|
||||
);
|
||||
|
||||
assertFalse(settings.enabled());
|
||||
settings.setEnabled(true);
|
||||
|
||||
PlayerCommandSettings reloaded = new PlayerCommandSettings(
|
||||
new YamlPlayerCommandSettingsRepository(path)
|
||||
);
|
||||
assertTrue(reloaded.enabled());
|
||||
|
||||
reloaded.setEnabled(false);
|
||||
|
||||
assertFalse(new PlayerCommandSettings(
|
||||
new YamlPlayerCommandSettingsRepository(path)
|
||||
).enabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistenceFailureLeavesPriorState() throws Exception {
|
||||
PlayerCommandSettings settings = new PlayerCommandSettings(
|
||||
new PlayerCommandSettingsRepository() {
|
||||
@Override public boolean loadEnabled() { return false; }
|
||||
@Override public void saveEnabled(boolean enabled) throws IOException {
|
||||
throw new IOException("disk full");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
assertThrows(IOException.class, () -> settings.setEnabled(true));
|
||||
|
||||
assertFalse(settings.enabled());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package games.dmg.spigotquestboard;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class PluginCommandWiringTest {
|
||||
@Test
|
||||
void adminAndPlayerCommandsShareThePersistedSetting() throws Exception {
|
||||
PlayerCommandSettings settings = new PlayerCommandSettings(
|
||||
new PlayerCommandSettingsRepository() {
|
||||
private boolean enabled;
|
||||
@Override public boolean loadEnabled() { return enabled; }
|
||||
@Override public void saveEnabled(boolean enabled) { this.enabled = enabled; }
|
||||
}
|
||||
);
|
||||
BoardRepository boards = new BoardRepository() {
|
||||
@Override public BoardState load() { return new BoardState(Set.of()); }
|
||||
@Override public void save(BoardState state) { }
|
||||
};
|
||||
QuestBrowser browser = mock(QuestBrowser.class);
|
||||
when(browser.activeQuests(org.mockito.ArgumentMatchers.any())).thenReturn(List.of());
|
||||
SpigotQuestBoardPlugin.CommandHandlers handlers =
|
||||
SpigotQuestBoardPlugin.commandHandlers(
|
||||
new BoardRegistry(boards), settings, mock(QuestCreationGateway.class), browser,
|
||||
null, null, null, Clock.systemUTC()
|
||||
);
|
||||
Player player = mock(Player.class);
|
||||
CommandSender admin = mock(CommandSender.class);
|
||||
when(admin.hasPermission("spigotquestboard.admin")).thenReturn(true);
|
||||
Command command = mock(Command.class);
|
||||
|
||||
assertTrue(handlers.quests().onCommand(
|
||||
player, command, "quests", new String[] {"list"}
|
||||
));
|
||||
verify(player).sendMessage(
|
||||
"Player quest commands are disabled. Use a physical quest board instead."
|
||||
);
|
||||
|
||||
handlers.admin().onCommand(
|
||||
admin, command, "questadmin", new String[] {"commands", "enable"}
|
||||
);
|
||||
handlers.quests().onCommand(player, command, "quests", new String[] {"list"});
|
||||
|
||||
verify(browser).activeQuests(org.mockito.ArgumentMatchers.any());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package games.dmg.spigotquestboard;
|
||||
|
||||
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 static org.mockito.Mockito.mock;
|
||||
@@ -7,6 +8,8 @@ import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.World;
|
||||
@@ -20,7 +23,10 @@ final class QuestAdminCommandTest {
|
||||
@Test
|
||||
void unauthorizedSenderCannotCreateBoard() throws Exception {
|
||||
MemoryBoardRepository repository = new MemoryBoardRepository();
|
||||
QuestAdminCommand executor = new QuestAdminCommand(new BoardRegistry(repository));
|
||||
QuestAdminCommand executor = new QuestAdminCommand(
|
||||
new BoardRegistry(repository),
|
||||
new PlayerCommandSettings(new MemoryPlayerCommandSettingsRepository())
|
||||
);
|
||||
Player player = mock(Player.class);
|
||||
when(player.hasPermission("spigotquestboard.admin")).thenReturn(false);
|
||||
|
||||
@@ -30,10 +36,111 @@ final class QuestAdminCommandTest {
|
||||
assertTrue(repository.state.boards().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void authorizedSenderCanEnableAndDisablePlayerCommands() throws Exception {
|
||||
MemoryPlayerCommandSettingsRepository settingsRepository =
|
||||
new MemoryPlayerCommandSettingsRepository();
|
||||
PlayerCommandSettings settings = new PlayerCommandSettings(settingsRepository);
|
||||
QuestAdminCommand executor = new QuestAdminCommand(
|
||||
new BoardRegistry(new MemoryBoardRepository()), settings
|
||||
);
|
||||
CommandSender sender = mock(CommandSender.class);
|
||||
when(sender.hasPermission("spigotquestboard.admin")).thenReturn(true);
|
||||
|
||||
assertTrue(executor.onCommand(
|
||||
sender, mock(Command.class), "questadmin", new String[] {"commands", "enable"}
|
||||
));
|
||||
assertTrue(settings.enabled());
|
||||
assertTrue(executor.onCommand(
|
||||
sender, mock(Command.class), "questadmin", new String[] {"commands", "disable"}
|
||||
));
|
||||
|
||||
assertFalse(settings.enabled());
|
||||
verify(sender).sendMessage("Player quest commands enabled.");
|
||||
verify(sender).sendMessage("Player quest commands disabled.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void unauthorizedSenderCannotChangePlayerCommands() throws Exception {
|
||||
MemoryPlayerCommandSettingsRepository repository =
|
||||
new MemoryPlayerCommandSettingsRepository();
|
||||
PlayerCommandSettings settings = new PlayerCommandSettings(repository);
|
||||
QuestAdminCommand executor = new QuestAdminCommand(
|
||||
new BoardRegistry(new MemoryBoardRepository()), settings
|
||||
);
|
||||
CommandSender sender = mock(CommandSender.class);
|
||||
when(sender.hasPermission("spigotquestboard.admin")).thenReturn(false);
|
||||
|
||||
executor.onCommand(
|
||||
sender, mock(Command.class), "questadmin", new String[] {"commands", "enable"}
|
||||
);
|
||||
|
||||
assertFalse(settings.enabled());
|
||||
assertEquals(0, repository.saves);
|
||||
}
|
||||
|
||||
@Test
|
||||
void settingsPersistenceFailureIsReportedAndLeavesPriorState() throws Exception {
|
||||
PlayerCommandSettings settings = new PlayerCommandSettings(
|
||||
new PlayerCommandSettingsRepository() {
|
||||
@Override public boolean loadEnabled() { return false; }
|
||||
@Override public void saveEnabled(boolean enabled) throws IOException {
|
||||
throw new IOException("disk full");
|
||||
}
|
||||
}
|
||||
);
|
||||
QuestAdminCommand executor = new QuestAdminCommand(
|
||||
new BoardRegistry(new MemoryBoardRepository()), settings
|
||||
);
|
||||
CommandSender sender = mock(CommandSender.class);
|
||||
when(sender.hasPermission("spigotquestboard.admin")).thenReturn(true);
|
||||
|
||||
executor.onCommand(
|
||||
sender, mock(Command.class), "questadmin", new String[] {"commands", "enable"}
|
||||
);
|
||||
|
||||
assertFalse(settings.enabled());
|
||||
verify(sender).sendMessage(
|
||||
"Player command setting could not be saved. Player quest commands remain disabled."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void autocompleteIsPermissionAwareAndContextual() throws Exception {
|
||||
QuestAdminCommand executor = new QuestAdminCommand(
|
||||
new BoardRegistry(new MemoryBoardRepository()),
|
||||
new PlayerCommandSettings(new MemoryPlayerCommandSettingsRepository())
|
||||
);
|
||||
CommandSender authorized = mock(CommandSender.class);
|
||||
when(authorized.hasPermission("spigotquestboard.admin")).thenReturn(true);
|
||||
CommandSender unauthorized = mock(CommandSender.class);
|
||||
when(unauthorized.hasPermission("spigotquestboard.admin")).thenReturn(false);
|
||||
Command command = mock(Command.class);
|
||||
|
||||
assertEquals(List.of("commands"), executor.onTabComplete(
|
||||
authorized, command, "questadmin", new String[] {"com"}
|
||||
));
|
||||
assertEquals(List.of("enable"), executor.onTabComplete(
|
||||
authorized, command, "questadmin", new String[] {"commands", "en"}
|
||||
));
|
||||
assertEquals(List.of("disable"), executor.onTabComplete(
|
||||
authorized, command, "questadmin", new String[] {"commands", "di"}
|
||||
));
|
||||
assertTrue(executor.onTabComplete(
|
||||
authorized, command, "questadmin", new String[] {"createboard", ""}
|
||||
).isEmpty());
|
||||
assertTrue(executor.onTabComplete(
|
||||
unauthorized, command, "questadmin", new String[] {""}
|
||||
).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void registersTargetedPhysicalBlock() throws Exception {
|
||||
MemoryBoardRepository repository = new MemoryBoardRepository();
|
||||
QuestAdminCommand executor = new QuestAdminCommand(new BoardRegistry(repository));
|
||||
QuestAdminCommand executor = new QuestAdminCommand(
|
||||
new BoardRegistry(repository),
|
||||
new PlayerCommandSettings(new MemoryPlayerCommandSettingsRepository())
|
||||
);
|
||||
Player player = mock(Player.class);
|
||||
Block block = mock(Block.class);
|
||||
World world = mock(World.class);
|
||||
@@ -57,7 +164,10 @@ final class QuestAdminCommandTest {
|
||||
@Test
|
||||
void consoleAndMissingTargetDoNotChangeState() throws Exception {
|
||||
MemoryBoardRepository repository = new MemoryBoardRepository();
|
||||
QuestAdminCommand executor = new QuestAdminCommand(new BoardRegistry(repository));
|
||||
QuestAdminCommand executor = new QuestAdminCommand(
|
||||
new BoardRegistry(repository),
|
||||
new PlayerCommandSettings(new MemoryPlayerCommandSettingsRepository())
|
||||
);
|
||||
CommandSender console = mock(CommandSender.class);
|
||||
when(console.hasPermission("spigotquestboard.admin")).thenReturn(true);
|
||||
assertTrue(executor.onCommand(console, mock(Command.class), "questadmin", new String[] {"createboard"}));
|
||||
@@ -70,6 +180,18 @@ final class QuestAdminCommandTest {
|
||||
assertTrue(repository.state.boards().isEmpty());
|
||||
}
|
||||
|
||||
private static final class MemoryPlayerCommandSettingsRepository
|
||||
implements PlayerCommandSettingsRepository {
|
||||
private boolean enabled;
|
||||
private int saves;
|
||||
|
||||
@Override public boolean loadEnabled() { return enabled; }
|
||||
@Override public void saveEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
saves++;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class MemoryBoardRepository implements BoardRepository {
|
||||
private BoardState state = new BoardState(Set.of());
|
||||
@Override public BoardState load() { return state; }
|
||||
|
||||
@@ -16,6 +16,42 @@ 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);
|
||||
|
||||
@@ -6,6 +6,7 @@ import static org.mockito.ArgumentMatchers.contains;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.time.Clock;
|
||||
@@ -20,6 +21,47 @@ import org.junit.jupiter.api.Test;
|
||||
final class QuestCommandTest {
|
||||
private static final Instant NOW = Instant.parse("2026-09-05T03:00:00Z");
|
||||
|
||||
@Test
|
||||
void disabledSettingGatesEveryPlayerCommandFormAndAutocomplete() throws Exception {
|
||||
QuestCreationGateway creator = mock(QuestCreationGateway.class);
|
||||
QuestBrowser browser = mock(QuestBrowser.class);
|
||||
QuestCompletionGateway completer = mock(QuestCompletionGateway.class);
|
||||
QuestCancellationGateway canceller = mock(QuestCancellationGateway.class);
|
||||
QuestClaimGateway claimant = mock(QuestClaimGateway.class);
|
||||
PlayerCommandSettings settings = new PlayerCommandSettings(
|
||||
new PlayerCommandSettingsRepository() {
|
||||
@Override public boolean loadEnabled() { return false; }
|
||||
@Override public void saveEnabled(boolean enabled) { }
|
||||
}
|
||||
);
|
||||
QuestCommand executor = new QuestCommand(
|
||||
creator, browser, completer, canceller, claimant,
|
||||
Clock.fixed(NOW, ZoneOffset.UTC), settings
|
||||
);
|
||||
Player player = mock(Player.class);
|
||||
Command command = mock(Command.class);
|
||||
List<String[]> forms = List.of(
|
||||
new String[] {},
|
||||
new String[] {"list"},
|
||||
new String[] {"create", "stone", "1"},
|
||||
new String[] {"complete", "quest-id"},
|
||||
new String[] {"cancel", "quest-id"},
|
||||
new String[] {"claim"}
|
||||
);
|
||||
|
||||
for (String[] form : forms) {
|
||||
assertTrue(executor.onCommand(player, command, "quests", form));
|
||||
}
|
||||
|
||||
verify(player, times(forms.size())).sendMessage(
|
||||
"Player quest commands are disabled. Use a physical quest board instead."
|
||||
);
|
||||
assertTrue(executor.onTabComplete(
|
||||
player, command, "quests", new String[] {""}
|
||||
).isEmpty());
|
||||
verifyNoInteractions(creator, browser, completer, canceller, claimant);
|
||||
}
|
||||
|
||||
@Test
|
||||
void routesValidatedCreateArgumentsWithCurrentUtcTime() {
|
||||
RecordingCreator creator = new RecordingCreator();
|
||||
|
||||
Reference in New Issue
Block a user