Claim protection from a local index: revision-polled panel endpoint, no per-block network checks
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DjMbLQujBHunCCu5GpsHaT
This commit is contained in:
10 files changed
+442
-127
No files matched your search
@@ -0,0 +1,93 @@
|
||||
package net.scopenet.integration;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import org.junit.jupiter.api.*;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ClaimIndexTest {
|
||||
private static final UUID MEMBER = UUID.fromString("b50ad385-829d-3141-a216-7e7d7539ba7f");
|
||||
private static final UUID OUTSIDER = UUID.randomUUID();
|
||||
private static final String INDEX = """
|
||||
{"revision":"7","unchanged":false,
|
||||
"guilds":[{"id":"g1","name":"Iron Fortress","tag":"IRON"}],
|
||||
"claims":[["minecraft:overworld",3,-4,0],["minecraft:the_nether",-1,-1,0]],
|
||||
"members":{"g1":["b50ad385-829d-3141-a216-7e7d7539ba7f","not-a-uuid"]}}""";
|
||||
|
||||
private static JsonObject json(String s) { return JsonParser.parseString(s).getAsJsonObject(); }
|
||||
|
||||
@Test void answersLocallyAndTreatsWildernessAsOpen() {
|
||||
ClaimIndex index = new ClaimIndex();
|
||||
assertFalse(index.loaded());
|
||||
assertTrue(index.check("minecraft:overworld", 3, -4, OUTSIDER).allowed(), "nothing is enforced before the first load");
|
||||
assertTrue(index.apply(json(INDEX)));
|
||||
assertEquals("7", index.revision());
|
||||
assertEquals(2, index.claimCount());
|
||||
|
||||
ChunkCheckResult mine = index.check("minecraft:overworld", 3, -4, MEMBER);
|
||||
assertTrue(mine.claimed() && mine.allowed());
|
||||
ChunkCheckResult theirs = index.check("minecraft:overworld", 3, -4, OUTSIDER);
|
||||
assertTrue(theirs.claimed() && !theirs.allowed());
|
||||
assertEquals("IRON", theirs.guildTag());
|
||||
assertFalse(index.check("minecraft:overworld", 3, -3, OUTSIDER).claimed());
|
||||
assertFalse(index.check("minecraft:the_end", 3, -4, OUTSIDER).claimed(), "claims are per dimension");
|
||||
assertTrue(index.isClaimed("minecraft:the_nether", -1, -1));
|
||||
assertFalse(index.isClaimed("minecraft:overworld", -1, -1));
|
||||
}
|
||||
|
||||
@Test void negativeCoordinatesDoNotCollide() {
|
||||
ClaimIndex index = new ClaimIndex();
|
||||
index.apply(json("""
|
||||
{"revision":"1","guilds":[{"id":"g","name":"A","tag":"A"}],
|
||||
"claims":[["d",-1,0,0],["d",0,-1,0],["d",-1,-1,0]],"members":{}}"""));
|
||||
assertTrue(index.isClaimed("d", -1, 0));
|
||||
assertTrue(index.isClaimed("d", 0, -1));
|
||||
assertTrue(index.isClaimed("d", -1, -1));
|
||||
assertFalse(index.isClaimed("d", 0, 0));
|
||||
assertFalse(index.isClaimed("d", 1, -1));
|
||||
}
|
||||
|
||||
@Test void unchangedResponsesKeepTheCurrentCopy() {
|
||||
ClaimIndex index = new ClaimIndex();
|
||||
index.apply(json(INDEX));
|
||||
assertFalse(index.apply(json("{\"revision\":\"7\",\"unchanged\":true}")));
|
||||
assertEquals(2, index.claimCount());
|
||||
}
|
||||
|
||||
@Test void pollingSendsTheRevisionAndSurvivesPanelOutages() throws Exception {
|
||||
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
|
||||
AtomicInteger calls = new AtomicInteger();
|
||||
StringBuilder lastBody = new StringBuilder();
|
||||
server.createContext("/api/server/v1/guilds/claim-index", exchange -> {
|
||||
String body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
|
||||
lastBody.setLength(0);
|
||||
lastBody.append(body);
|
||||
int n = calls.incrementAndGet();
|
||||
byte[] out = (n == 1 ? INDEX : n == 2 ? "{\"revision\":\"7\",\"unchanged\":true}" : "boom").getBytes(StandardCharsets.UTF_8);
|
||||
exchange.sendResponseHeaders(n >= 3 ? 503 : 200, out.length);
|
||||
exchange.getResponseBody().write(out);
|
||||
exchange.close();
|
||||
});
|
||||
server.start();
|
||||
try {
|
||||
PanelClient client = new PanelClient(Settings.of("http://127.0.0.1:" + server.getAddress().getPort(), "sn_" + "a".repeat(40)));
|
||||
assertTrue(client.refreshClaims());
|
||||
assertEquals("{}", lastBody.toString(), "first poll has no revision");
|
||||
assertFalse(client.refreshClaims());
|
||||
assertEquals("{\"revision\":\"7\"}", lastBody.toString());
|
||||
assertThrows(java.io.IOException.class, client::refreshClaims);
|
||||
// The last good copy still protects land while the panel is down.
|
||||
assertFalse(client.checkChunk("minecraft:overworld", 3, -4, OUTSIDER).allowed());
|
||||
assertTrue(client.checkChunk("minecraft:overworld", 3, -4, MEMBER).allowed());
|
||||
assertTrue(client.isClaimed("minecraft:overworld", 3, -4));
|
||||
client.close();
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user