mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-14 23:24:52 +09:00
Stabilize player-list integration tests and RCON fixtures (#1117)
* Wait for player list replication in integration tests * Select available RCON ports before starting Factorio * Document zero as automatic RCON port allocation * Address review wording and test organization * Restrict port collision hardening to integration fixtures
This commit is contained in:
1 parent
cbf2b10782
commit
0fb9e793d7
5 files changed
+215
-33
No files matched your search
@@ -11,6 +11,8 @@ import {
|
||||
requiresFactorio, hasFactorio, getFactorioVersion, instancesDir,
|
||||
} from "./index.js";
|
||||
|
||||
import { getTestRconPort } from "./rcon_port.js";
|
||||
|
||||
const instId = 48;
|
||||
const instName = "Integration";
|
||||
const instSetConfig = `instance config set ${instName}`;
|
||||
@@ -40,6 +42,46 @@ function getUser(name) {
|
||||
return getControl().send(new lib.UserGetRequest(name));
|
||||
}
|
||||
|
||||
// RCON completion does not acknowledge list replication. In particular, whitelist
|
||||
// updates pass through an asynchronous file watcher before being broadcast.
|
||||
async function waitForAssertion(check, timeoutMs = 5000) {
|
||||
const deadline = performance.now() + timeoutMs;
|
||||
for (;;) {
|
||||
try {
|
||||
await check();
|
||||
return;
|
||||
} catch (err) {
|
||||
if (!(err instanceof assert.AssertionError) || performance.now() >= deadline) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
await lib.wait(25);
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForUserFlag(name, flag, expected, message) {
|
||||
await waitForAssertion(async () => {
|
||||
// The initial event also creates the user, so absence is a pending state.
|
||||
const users = await getControl().send(new lib.UserListRequest());
|
||||
const user = users.find(entry => entry.name.toLowerCase() === name.toLowerCase());
|
||||
assert(user, `User ${name} has not been created`);
|
||||
assert.equal(user[flag], expected, message);
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForAltAdmin(expected, message) {
|
||||
await waitForAssertion(async () => {
|
||||
// Read Factorio's persisted list before checking /promote or /demote
|
||||
// responses, so the checks cannot apply a change that has not replicated yet.
|
||||
const json = await fs.readFile(path.join(instancesDir, instAltName, "server-adminlist.json"), "utf8");
|
||||
let admins;
|
||||
// A concurrent write can leave a partial document; retry that observation.
|
||||
assert.doesNotThrow(() => { admins = JSON.parse(json); }, "Invalid admin list JSON");
|
||||
assert(Array.isArray(admins), "Admin list is not an array");
|
||||
assert.equal(admins.some(name => name.toLowerCase() === "adminplayer"), expected, message);
|
||||
});
|
||||
}
|
||||
|
||||
describe("Clusterio Instance", function() {
|
||||
requiresFactorio(this);
|
||||
before(async function() {
|
||||
@@ -50,6 +92,7 @@ describe("Clusterio Instance", function() {
|
||||
|
||||
// Create a new instance
|
||||
await execCtl(`instance create ${instName} --id ${instId}`);
|
||||
await execCtl(`instance config set ${instName} factorio.rcon_port ${await getTestRconPort()}`);
|
||||
await execCtl(`instance config set ${instName} factorio.enable_whitelist true`);
|
||||
await execCtlProcess(`instance config set-prop ${instName} factorio.settings visibility "${visibility}"`);
|
||||
await execCtl(`instance config set-prop ${instName} factorio.settings require_user_verification false`);
|
||||
@@ -65,6 +108,7 @@ describe("Clusterio Instance", function() {
|
||||
|
||||
// Create a new alt instance
|
||||
await execCtl(`instance create ${instAltName} --id ${instAltId}`);
|
||||
await execCtl(`instance config set ${instAltName} factorio.rcon_port ${await getTestRconPort()}`);
|
||||
await execCtl(`instance config set ${instAltName} factorio.enable_whitelist true`);
|
||||
await execCtlProcess(`instance config set-prop ${instAltName} factorio.settings visibility "${visibility}"`);
|
||||
await execCtl(`instance config set-prop ${instAltName} factorio.settings require_user_verification false`);
|
||||
@@ -213,28 +257,28 @@ describe("Clusterio Instance", function() {
|
||||
});
|
||||
}
|
||||
it("should respond to a player ban and unban event", async function() {
|
||||
this.timeout(25000);
|
||||
await execCtl(`${instSetConfig} factorio.sync_banlist bidirectional`);
|
||||
await sendRcon(instId, "/ban BannedPlayer");
|
||||
await lib.wait(15); // Propagation time, 10ms worked for me, so i made it 15ms
|
||||
const userBanned = await getUser("BannedPlayer");
|
||||
assert(userBanned.isBanned, "Player is not banned");
|
||||
const isAltBanned = await sendRcon(instAltId, "/banlist get BannedPlayer");
|
||||
assert.equal(isAltBanned, "BannedPlayer is banned.\n", "User is not banned on alt");
|
||||
await waitForUserFlag("BannedPlayer", "isBanned", true, "Player is not banned");
|
||||
await waitForAssertion(async () => {
|
||||
const isAltBanned = await sendRcon(instAltId, "/banlist get BannedPlayer");
|
||||
assert.equal(isAltBanned, "BannedPlayer is banned.\n", "User is not banned on alt");
|
||||
});
|
||||
|
||||
await sendRcon(instId, "/unban BannedPlayer");
|
||||
await lib.wait(15); // Propagation time, 10ms worked for me, so i made it 15ms
|
||||
const userUnbanned = await getUser("BannedPlayer");
|
||||
assert(!userUnbanned.isBanned, "Player is not unbanned");
|
||||
const isAltUnbanned = await sendRcon(instAltId, "/banlist get BannedPlayer");
|
||||
assert.equal(isAltUnbanned, "BannedPlayer is not banned.\n", "User is not unbanned on alt");
|
||||
await waitForUserFlag("BannedPlayer", "isBanned", false, "Player is not unbanned");
|
||||
await waitForAssertion(async () => {
|
||||
const isAltUnbanned = await sendRcon(instAltId, "/banlist get BannedPlayer");
|
||||
assert.equal(isAltUnbanned, "BannedPlayer is not banned.\n", "User is not unbanned on alt");
|
||||
});
|
||||
});
|
||||
it("should respond to a player promote and demote event", async function() {
|
||||
// Because there is no admin list command this test will be different to the other two
|
||||
this.timeout(25000);
|
||||
await execCtl(`${instSetConfig} factorio.sync_adminlist bidirectional`);
|
||||
await sendRcon(instId, "/promote AdminPlayer");
|
||||
await lib.wait(15); // Propagation time, 10ms worked for me, so i made it 15ms
|
||||
const userPromote = await getUser("AdminPlayer");
|
||||
assert(userPromote.isAdmin, "Player is not promoted");
|
||||
await waitForUserFlag("AdminPlayer", "isAdmin", true, "Player is not promoted");
|
||||
await waitForAltAdmin(true, "User is not promoted on alt");
|
||||
const isAltPromoted = await sendRcon(instAltId, "/promote AdminPlayer");
|
||||
assert.equal(isAltPromoted,
|
||||
"AdminPlayer is already in the admin list and will be promoted upon joining the game.\n",
|
||||
@@ -242,9 +286,8 @@ describe("Clusterio Instance", function() {
|
||||
);
|
||||
|
||||
await sendRcon(instId, "/demote AdminPlayer");
|
||||
await lib.wait(15); // Propagation time, 10ms worked for me, so i made it 15ms
|
||||
const userDemote = await getUser("AdminPlayer");
|
||||
assert(!userDemote.isAdmin, "Player is not demoted");
|
||||
await waitForUserFlag("AdminPlayer", "isAdmin", false, "Player is not demoted");
|
||||
await waitForAltAdmin(false, "User is not demoted on alt");
|
||||
const isAltDemoted = await sendRcon(instAltId, "/demote AdminPlayer");
|
||||
assert.equal(isAltDemoted,
|
||||
"AdminPlayer is not in the admin list.\n",
|
||||
@@ -252,26 +295,27 @@ describe("Clusterio Instance", function() {
|
||||
);
|
||||
});
|
||||
it("should respond to a player whitelist add and remove event", async function() {
|
||||
this.timeout(25000);
|
||||
await execCtl(`${instSetConfig} factorio.sync_whitelist bidirectional`);
|
||||
await sendRcon(instId, "/whitelist add WhitelistPlayer");
|
||||
await lib.wait(15); // Propagation time, 10ms worked for me, so i made it 15ms
|
||||
const userAdded = await getUser("WhitelistPlayer");
|
||||
assert(userAdded.isWhitelisted, "Player is not whitelisted");
|
||||
const isAltAdded = await sendRcon(instAltId, "/whitelist get WhitelistPlayer");
|
||||
assert.equal(isAltAdded,
|
||||
"WhitelistPlayer is whitelisted.\n",
|
||||
"User is not whitelisted on alt"
|
||||
);
|
||||
await waitForUserFlag("WhitelistPlayer", "isWhitelisted", true, "Player is not whitelisted");
|
||||
await waitForAssertion(async () => {
|
||||
const isAltAdded = await sendRcon(instAltId, "/whitelist get WhitelistPlayer");
|
||||
assert.equal(isAltAdded,
|
||||
"WhitelistPlayer is whitelisted.\n",
|
||||
"User is not whitelisted on alt"
|
||||
);
|
||||
});
|
||||
|
||||
await sendRcon(instId, "/whitelist remove WhitelistPlayer");
|
||||
await lib.wait(15); // Propagation time, 10ms worked for me, so i made it 15ms
|
||||
const userRemoved = await getUser("WhitelistPlayer");
|
||||
assert(!userRemoved.isWhitelisted, "Player is not unwhitelisted");
|
||||
const isAltRemoved = await sendRcon(instAltId, "/whitelist get WhitelistPlayer");
|
||||
assert.equal(isAltRemoved,
|
||||
"WhitelistPlayer is not whitelisted.\n",
|
||||
"User is not unwhitelisted on alt"
|
||||
);
|
||||
await waitForUserFlag("WhitelistPlayer", "isWhitelisted", false, "Player is not unwhitelisted");
|
||||
await waitForAssertion(async () => {
|
||||
const isAltRemoved = await sendRcon(instAltId, "/whitelist get WhitelistPlayer");
|
||||
assert.equal(isAltRemoved,
|
||||
"WhitelistPlayer is not whitelisted.\n",
|
||||
"User is not unwhitelisted on alt"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -303,6 +347,7 @@ describe("start-all and stop-all commands", function() {
|
||||
/"/g, process.platform === "win32" ? '""' : '\\"'
|
||||
);
|
||||
for (const name of [testInstName1, testInstName2, testInstName3]) {
|
||||
await execCtl(`instance config set ${name} factorio.rcon_port ${await getTestRconPort()}`);
|
||||
await execCtlProcess(`instance config set-prop ${name} factorio.settings visibility "${visibility}"`);
|
||||
await execCtl(`instance config set-prop ${name} factorio.settings require_user_verification false`);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user