Files
purpur-sentinel/src/test/java/games/dmg/purpursentinel/BukkitEnforcementTest.java
T
dmg 2a2d307f6d
CI / build (push) Successful in 1m11s
CI / build (pull_request) Successful in 1m17s
feat(enforcement): add graduated movement responses
2026-10-29 18:58:14 -04:00

92 lines
4.2 KiB
Java

package games.dmg.purpursentinel;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerMoveEvent;
import org.junit.jupiter.api.Test;
class BukkitEnforcementTest {
private static final DetectionSignal SIGNAL = DetectionSignal.ABSOLUTE_GROUND_SPEED;
@Test
void emittedEpisodesWarnSetbackHoldAndKickThroughFixedServerApis() {
var fixture = fixture(EnforcementMode.SETBACK_KICK);
fixture.listener.captureSafeAnchor(fixture.player, fixture.anchor);
for (int episode = 1; episode <= 7; episode++) {
when(fixture.server.getCurrentTick()).thenReturn(episode);
fixture.listener.observe(alert(fixture.id));
}
verify(fixture.player).sendMessage(argThat((net.kyori.adventure.text.Component component) -> component.toString().contains("repeated movement")));
verify(fixture.player).teleport(fixture.anchor);
verify(fixture.player).kick(argThat(component -> component.toString().contains("repeated movement")));
assertTrue(fixture.listener.holdRemainingSeconds(fixture.id, fixture.clock.get()) > 0);
var move = new PlayerMoveEvent(fixture.player, fixture.anchor,
new Location(fixture.world, 2, 64, 0, 90, 15));
fixture.listener.onMove(move);
assertEquals(fixture.anchor.getX(), move.getTo().getX());
assertEquals(90, move.getTo().getYaw());
assertEquals(15, move.getTo().getPitch());
}
@Test
void recentDamageSkipsSetbackAndDamageEndsAnActiveHoldWithoutAnotherTeleport() {
var fixture = fixture(EnforcementMode.SETBACK);
fixture.listener.captureSafeAnchor(fixture.player, fixture.anchor);
fixture.listener.recordDamage(fixture.player);
for (int episode = 1; episode <= 5; episode++) {
when(fixture.server.getCurrentTick()).thenReturn(episode);
fixture.listener.observe(alert(fixture.id));
}
verify(fixture.player, never()).teleport(any(Location.class));
fixture.clock.addAndGet(11_000_000_000L);
fixture.listener.reset(fixture.id);
fixture.listener.captureSafeAnchor(fixture.player, fixture.anchor);
for (int episode = 1; episode <= 5; episode++) {
when(fixture.server.getCurrentTick()).thenReturn(episode + 10);
fixture.listener.observe(alert(fixture.id));
}
verify(fixture.player).teleport(fixture.anchor);
fixture.listener.recordDamage(fixture.player);
assertEquals(0, fixture.listener.holdRemainingSeconds(fixture.id, fixture.clock.get()));
verify(fixture.player, times(1)).teleport(any(Location.class));
}
private static Fixture fixture(EnforcementMode mode) {
var server = mock(Server.class);
var player = mock(Player.class);
var world = mock(World.class);
var id = UUID.randomUUID();
var anchor = new Location(world, 0, 64, 0, 10, 5);
when(player.getUniqueId()).thenReturn(id);
when(player.isOnline()).thenReturn(true);
when(player.getWorld()).thenReturn(world);
when(player.getLocation()).thenReturn(anchor);
when(player.teleport(any(Location.class))).thenReturn(true);
when(server.getPlayer(id)).thenReturn(player);
var clock = new AtomicLong(1_000_000_000L);
var settings = new AtomicReference<>(new EnforcementSettings(mode, 600, 3, 5, 7, 10, 10,
EnumSet.of(SIGNAL)));
var listener = new BukkitEnforcement(server, settings::get, clock::get,
location -> true, EnforcementAuditSink.NONE);
return new Fixture(server, player, world, id, anchor, clock, listener);
}
private static AlertDecision alert(UUID id) { return new AlertDecision(id, 6, 1.03, 0.80); }
private record Fixture(Server server, Player player, World world, UUID id, Location anchor,
AtomicLong clock, BukkitEnforcement listener) { }
}