feat(admin): manage player stealth state
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
package games.dmg.spigotstealth;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class KnownPlayerResolverTest {
|
||||
@Test
|
||||
void resolvesKnownOfflinePlayerByExactNameOrUuid() {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
PlayerStealthState player = PlayerStealthState.empty(playerId).withLastKnownName("Alex");
|
||||
PersistentStealthState state = new PersistentStealthState(Map.of(playerId, player), Map.of());
|
||||
KnownPlayerResolver resolver = new KnownPlayerResolver(() -> state, List::of);
|
||||
assertEquals(playerId, resolver.resolve("alex").player().playerId());
|
||||
assertEquals(playerId, resolver.resolve(playerId.toString()).player().playerId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void reportsAmbiguousAndUnknownNamesWithoutCreatingRecords() {
|
||||
UUID first = UUID.randomUUID();
|
||||
UUID second = UUID.randomUUID();
|
||||
PersistentStealthState state = new PersistentStealthState(Map.of(
|
||||
first, PlayerStealthState.empty(first).withLastKnownName("Alex"),
|
||||
second, PlayerStealthState.empty(second).withLastKnownName("ALEX")), Map.of());
|
||||
KnownPlayerResolver resolver = new KnownPlayerResolver(() -> state, List::of);
|
||||
assertEquals(KnownPlayerResolver.Status.AMBIGUOUS, resolver.resolve("alex").status());
|
||||
assertEquals(KnownPlayerResolver.Status.UNKNOWN, resolver.resolve("nobody").status());
|
||||
assertEquals(2, state.players().size());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package games.dmg.spigotstealth;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.contains;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class StealthAdminCommandTest {
|
||||
@Test
|
||||
void statusInspectsKnownOfflinePlayerAndReportsAllState() {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
try (Fixture fixture = fixture(playerId)) {
|
||||
CommandSender sender = sender(true);
|
||||
fixture.command.onCommand(sender, mock(Command.class), "stealthadmin", new String[] {"status", "Alex"});
|
||||
verify(sender).sendMessage(contains(playerId.toString()));
|
||||
verify(sender).sendMessage(contains("prepared=yes"));
|
||||
verify(sender).sendMessage(contains("concealed=no"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void grantPersistsThenReportsAndAuditsAction() {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
try (Fixture fixture = fixture(playerId)) {
|
||||
CommandSender sender = sender(true);
|
||||
fixture.command.onCommand(sender, mock(Command.class), "stealthadmin", new String[] {"grant", playerId.toString()});
|
||||
verify(sender, org.mockito.Mockito.timeout(1000)).sendMessage(contains("granted"));
|
||||
org.junit.jupiter.api.Assertions.assertTrue(fixture.manager.snapshot().player(playerId).unlocked());
|
||||
org.junit.jupiter.api.Assertions.assertTrue(fixture.audit.stream().anyMatch(message -> message.contains("grant") && message.contains(playerId.toString())));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void resetRequiresConfirmationAndPermission() {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
try (Fixture fixture = fixture(playerId)) {
|
||||
CommandSender denied = sender(false);
|
||||
fixture.command.onCommand(denied, mock(Command.class), "stealthadmin", new String[] {"reset", "Alex", "confirm"});
|
||||
verify(denied).sendMessage(contains("permission"));
|
||||
CommandSender allowed = sender(true);
|
||||
fixture.command.onCommand(allowed, mock(Command.class), "stealthadmin", new String[] {"reset", "Alex"});
|
||||
verify(allowed).sendMessage(contains("confirm"));
|
||||
verify(allowed, never()).sendMessage(contains("reset complete"));
|
||||
}
|
||||
}
|
||||
|
||||
private static CommandSender sender(boolean permission) {
|
||||
CommandSender sender = mock(CommandSender.class);
|
||||
when(sender.hasPermission("spigotstealth.admin")).thenReturn(permission);
|
||||
when(sender.getName()).thenReturn("Operator");
|
||||
return sender;
|
||||
}
|
||||
|
||||
private static Fixture fixture(UUID playerId) {
|
||||
StealthStateRepository repository = new StealthStateRepository() {
|
||||
@Override public PersistentStealthState load() { return new PersistentStealthState(Map.of(), Map.of()); }
|
||||
@Override public void save(PersistentStealthState state) { }
|
||||
};
|
||||
StealthStateManager manager = new StealthStateManager(repository, new PersistentStealthState(Map.of(), Map.of()));
|
||||
PlayerStealthState player = new PlayerStealthState(playerId, "Alex", 5000L, false, true, false, null, Map.of());
|
||||
manager.update(state -> state.withPlayer(player)).join();
|
||||
QualifyingInvisibilityService progression = new QualifyingInvisibilityService(
|
||||
manager, Duration.ofHours(8), System::nanoTime, ignored -> { });
|
||||
StealthAdministrationService administration = new StealthAdministrationService(
|
||||
manager, progression, mock(IdentityPresentation.class), ignored -> { }, List::of);
|
||||
KnownPlayerResolver resolver = new KnownPlayerResolver(manager::snapshot, List::of);
|
||||
ArrayList<String> audit = new ArrayList<>();
|
||||
StealthAdminCommand command = new StealthAdminCommand(
|
||||
administration, resolver, Runnable::run, audit::add, Duration.ofHours(8));
|
||||
return new Fixture(manager, command, audit);
|
||||
}
|
||||
|
||||
private record Fixture(
|
||||
StealthStateManager manager,
|
||||
StealthAdminCommand command,
|
||||
ArrayList<String> audit) implements AutoCloseable {
|
||||
@Override public void close() { manager.close(); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package games.dmg.spigotstealth;
|
||||
|
||||
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;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class StealthAdministrationServiceTest {
|
||||
@Test
|
||||
void grantsOfflineUnlockAndNotifiesExactlyOnce() {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
ArrayList<UUID> notifications = new ArrayList<>();
|
||||
try (StealthStateManager manager = manager()) {
|
||||
StealthAdministrationService administration = service(manager, List::of, notifications::add);
|
||||
assertTrue(administration.grant(playerId, "Alex").join().changed());
|
||||
assertFalse(administration.grant(playerId, "Alex").join().changed());
|
||||
assertTrue(manager.snapshot().player(playerId).unlocked());
|
||||
assertEquals(List.of(playerId), notifications);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void resetClearsProgressUnlockPreparationConcealmentAndActiveTracking() {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
Player player = mock(Player.class);
|
||||
org.mockito.Mockito.when(player.getUniqueId()).thenReturn(playerId);
|
||||
try (StealthStateManager manager = manager()) {
|
||||
PlayerStealthState state = new PlayerStealthState(
|
||||
playerId, "Alex", 1234L, true, true, true, Instant.EPOCH, Map.of("future", "keep"));
|
||||
manager.update(all -> all.withPlayer(state)).join();
|
||||
QualifyingInvisibilityService progression = new QualifyingInvisibilityService(
|
||||
manager, Duration.ofHours(8), System::nanoTime, ignored -> { });
|
||||
progression.begin(playerId, "Alex", Instant.EPOCH).join();
|
||||
IdentityPresentation presentation = mock(IdentityPresentation.class);
|
||||
StealthAdministrationService administration = new StealthAdministrationService(
|
||||
manager, progression, presentation, ignored -> { }, () -> List.of(player));
|
||||
|
||||
administration.reset(playerId, "Alex", player).join();
|
||||
|
||||
PlayerStealthState reset = manager.snapshot().player(playerId);
|
||||
assertEquals(0L, reset.accumulatedMillis());
|
||||
assertFalse(reset.unlocked());
|
||||
assertFalse(reset.preparedLogin());
|
||||
assertFalse(reset.concealed());
|
||||
assertFalse(progression.isQualifying(playerId));
|
||||
assertEquals("keep", reset.unknownFields().get("future"));
|
||||
verify(presentation).reveal(player);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void listsOnlyCurrentlyOnlineConcealedPlayers() {
|
||||
UUID concealedId = UUID.randomUUID();
|
||||
UUID ordinaryId = UUID.randomUUID();
|
||||
Player concealed = player(concealedId, "Hidden");
|
||||
Player ordinary = player(ordinaryId, "Visible");
|
||||
try (StealthStateManager manager = manager()) {
|
||||
manager.update(state -> state
|
||||
.withPlayer(PlayerStealthState.empty(concealedId).withLastKnownName("Hidden").withSession(false, true))
|
||||
.withPlayer(PlayerStealthState.empty(ordinaryId).withLastKnownName("Visible"))).join();
|
||||
StealthAdministrationService administration = service(
|
||||
manager, () -> List.of(concealed, ordinary), ignored -> { });
|
||||
assertEquals(List.of("Hidden"), administration.concealedOnlineNames());
|
||||
}
|
||||
}
|
||||
|
||||
private static StealthAdministrationService service(
|
||||
StealthStateManager manager,
|
||||
java.util.function.Supplier<? extends java.util.Collection<? extends Player>> online,
|
||||
java.util.function.Consumer<UUID> notifier) {
|
||||
QualifyingInvisibilityService progression = new QualifyingInvisibilityService(
|
||||
manager, Duration.ofHours(8), System::nanoTime, ignored -> { });
|
||||
return new StealthAdministrationService(
|
||||
manager, progression, mock(IdentityPresentation.class), notifier, online);
|
||||
}
|
||||
|
||||
private static Player player(UUID playerId, String name) {
|
||||
Player player = mock(Player.class);
|
||||
org.mockito.Mockito.when(player.getUniqueId()).thenReturn(playerId);
|
||||
org.mockito.Mockito.when(player.getName()).thenReturn(name);
|
||||
return player;
|
||||
}
|
||||
|
||||
private static StealthStateManager manager() {
|
||||
StealthStateRepository repository = new StealthStateRepository() {
|
||||
@Override public PersistentStealthState load() { return new PersistentStealthState(Map.of(), Map.of()); }
|
||||
@Override public void save(PersistentStealthState state) { }
|
||||
};
|
||||
return new StealthStateManager(repository, new PersistentStealthState(Map.of(), Map.of()));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user