Add opt-in Paper integration tests

This commit is contained in:
2026-09-26 23:48:35 -07:00
parent 1721349a71
commit 1eab80ab2b
3 changed files with 148 additions and 1 deletions

View File

@@ -0,0 +1,126 @@
package com.majinnaibu.bukkitplugins.metropolis;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.UUID;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.jupiter.api.Test;
class MetropolisServerIntegrationTest {
@Test
void runningPaperLoadsMetropolisAndDispatchesItsVersionCommand() throws IOException {
try (RconClient rcon = connectToConfiguredServer()) {
String installedPlugins = rcon.execute("plugins");
String versionResponse = rcon.execute("metropolis");
assertTrue(installedPlugins.contains("Metropolis"), installedPlugins);
assertTrue(versionResponse.contains("Metropolis: version"), versionResponse);
}
}
@Test
void plotReservationIsPersistedByTheLoadedWorldGuardPlugin() throws IOException {
String regionId = "metropolis-it-" + UUID.randomUUID().toString().substring(0, 8);
try (RconClient rcon = connectToConfiguredServer()) {
String response = rcon.execute("metropolis-plot-reserve " + regionId + " 100 60 100 105 70 105");
assertTrue(response.contains("Reserved plot region " + regionId), response);
}
Path serverDirectory = Path.of(requiredEnvironment("METROPOLIS_SERVER_DIR"));
String worldName = System.getenv().getOrDefault("METROPOLIS_WORLD", "world");
Path regionsFile = serverDirectory.resolve("plugins/WorldGuard/worlds")
.resolve(worldName)
.resolve("regions.yml");
YamlConfiguration regions = YamlConfiguration.loadConfiguration(regionsFile.toFile());
assertTrue(regions.contains("regions." + regionId), "WorldGuard did not persist region " + regionId);
}
private static RconClient connectToConfiguredServer() throws IOException {
String host = requiredEnvironment("METROPOLIS_RCON_HOST");
int port = Integer.parseInt(requiredEnvironment("METROPOLIS_RCON_PORT"));
String password = requiredEnvironment("METROPOLIS_RCON_PASSWORD");
return new RconClient(host, port, password);
}
private static String requiredEnvironment(String name) {
String value = System.getenv(name);
if (value == null || value.isBlank()) {
throw new IllegalStateException("Set " + name + " to run integrationTest.");
}
return value;
}
private static final class RconClient implements AutoCloseable {
private final Socket socket = new Socket();
private final DataInputStream input;
private final DataOutputStream output;
private int requestId = 1;
private RconClient(String host, int port, String password) throws IOException {
socket.connect(new InetSocketAddress(host, port), 5000);
socket.setSoTimeout(5000);
input = new DataInputStream(socket.getInputStream());
output = new DataOutputStream(socket.getOutputStream());
int authId = requestId++;
Packet response = send(authId, 3, password);
if (response.id != authId) {
throw new IOException("RCON authentication failed.");
}
}
private String execute(String command) throws IOException {
return send(requestId++, 2, command).payload;
}
private Packet send(int id, int type, String payload) throws IOException {
byte[] data = payload.getBytes(StandardCharsets.UTF_8);
writeLittleEndianInt(data.length + 10);
writeLittleEndianInt(id);
writeLittleEndianInt(type);
output.write(data);
output.writeByte(0);
output.writeByte(0);
output.flush();
int length = readLittleEndianInt();
byte[] packet = input.readNBytes(length);
if (packet.length != length || length < 10) {
throw new IOException("Received an invalid RCON packet.");
}
int responseId = littleEndianInt(packet, 0);
String response = new String(packet, 8, length - 10, StandardCharsets.UTF_8);
return new Packet(responseId, response);
}
private void writeLittleEndianInt(int value) throws IOException {
output.writeInt(Integer.reverseBytes(value));
}
private int readLittleEndianInt() throws IOException {
return Integer.reverseBytes(input.readInt());
}
private static int littleEndianInt(byte[] bytes, int offset) {
return (bytes[offset] & 0xff)
| ((bytes[offset + 1] & 0xff) << 8)
| ((bytes[offset + 2] & 0xff) << 16)
| (bytes[offset + 3] << 24);
}
@Override
public void close() throws IOException {
socket.close();
}
}
private record Packet(int id, String payload) {}
}