package dev.loki.loparkour.ghost; import dev.loki.loparkour.ghost.core.GhostManager; import dev.loki.loparkour.ghost.model.GhostData; import dev.loki.loparkour.ghost.model.GhostFrame; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.UUID; import static org.junit.jupiter.api.Assertions.*; /** * Unit tests for the per-player personal replay storage of {@link GhostManager}. */ class GhostManagerTest { private static final int EVICTION_CAP = 2; /** * A manager whose run cap is fixed, overriding the config-driven {@link #maxRuns()}. * Chosen over mutating {@code Config} statics, which the plugin initializes at * enable time and are not settable from unit tests. */ private static final class FixedCapManager extends GhostManager { private FixedCapManager(File ghostFolder) { super(ghostFolder); } @Override public int maxRuns() { return EVICTION_CAP; } } private static GhostData run(UUID uuid, String name, int score, long timestamp) { final List frames = new ArrayList<>(); frames.add(new GhostFrame(0, 0, 100, 0, 0, 0)); return new GhostData(uuid, name, score, timestamp, "world", frames); } @Test void saveRunAndGetRunsReturnNewestFirst(@TempDir Path tempDir) throws IOException { final UUID uuid = UUID.randomUUID(); final GhostManager manager = new GhostManager(tempDir.toFile()); final long base = 1_700_000_000_000L; manager.saveRun(uuid, run(uuid, "A", 10, base)); manager.saveRun(uuid, run(uuid, "B", 20, base + 1_000)); manager.saveRun(uuid, run(uuid, "C", 30, base + 2_000)); final List runs = manager.getRuns(uuid); assertEquals(3, runs.size()); assertEquals(base + 2_000, runs.get(0).getTimestamp()); assertEquals(30, runs.get(0).getScore()); assertEquals(base, runs.get(2).getTimestamp()); assertEquals(10, runs.get(2).getScore()); } @Test void saveRunEvictsOldestBeyondCap(@TempDir Path tempDir) throws IOException { final UUID uuid = UUID.randomUUID(); final GhostManager manager = new FixedCapManager(tempDir.toFile()); final long base = 1_700_000_000_000L; for (int i = 0; i < 5; i++) { manager.saveRun(uuid, run(uuid, "P" + i, i * 10, base + i * 1_000L)); } final List runs = manager.getRuns(uuid); assertEquals(EVICTION_CAP, runs.size(), "Old runs beyond the cap must be evicted"); assertEquals(base + 4_000, runs.get(0).getTimestamp()); assertEquals(base + 3_000, runs.get(1).getTimestamp()); } @Test void getRunsWithEmptyFolderReturnsEmptyList(@TempDir Path tempDir) { final GhostManager manager = new GhostManager(tempDir.toFile()); final List runs = manager.getRuns(UUID.randomUUID()); assertTrue(runs.isEmpty(), "Player with no saved runs should return an empty list"); } @Test void getRunsIgnoresStaleFormatFiles(@TempDir Path tempDir) throws IOException { final UUID uuid = UUID.randomUUID(); final GhostManager manager = new GhostManager(tempDir.toFile()); manager.saveRun(uuid, run(uuid, "New", 10, 1_700_000_000_000L)); // Corrupt one file so it reads as an unsupported version final File playerFolder = new File(tempDir.toFile(), uuid.toString()); final File[] files = playerFolder.listFiles((dir, name) -> name.endsWith(".ghost")); assertNotNull(files); try (DataOutputStream out = new DataOutputStream( new FileOutputStream(files[0], false))) { out.writeInt(2); } final List runs = manager.getRuns(uuid); assertTrue(runs.isEmpty(), "Stale-format files must be skipped and deleted"); } }