124 lines
4.2 KiB
Java
124 lines
4.2 KiB
Java
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 runningPaperLoadsMetropolis() throws IOException {
|
|
String installedPlugins;
|
|
try (RconClient rcon = connectToConfiguredServer()) {
|
|
installedPlugins = rcon.execute("plugins");
|
|
}
|
|
assertTrue(installedPlugins.contains("Metropolis"), installedPlugins);
|
|
}
|
|
|
|
@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) {}
|
|
} |