mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 23:04:52 +09:00
* Fix tests failing due to missmatched Factorio detection When the configured factorio directory is full of junk then the tests use the first directory it finds for one of its tests, while the server code uses a more elaborate logic to figure this out. Turns out this is complicating things more than needed. Simply trying each entry in the directory and seeing if it works when treated as a directory always work, regardsless of symlinks, junctions, reparse-points or whatever else filesystem magic there is. * Skip tests requiring Factorio if it's not present Prevent confusing test failures by skipping all the tests that require a working Factorio installation to sucteed, and print a warning at the end of the test suite if tests may have been skipped due to this. * Handle ENOTDIR when determining Factorio version If the path to the factorio install contains a component that is not a file then ENOTDIR may be returned on POSIX like systems. Treat this the same as ENOENT so that detection logic works when used on a file. --------- Co-authored-by: Hornwitser <code@hornwitser.no>
339 lines
15 KiB
JavaScript
339 lines
15 KiB
JavaScript
"use strict";
|
|
const assert = require("assert").strict;
|
|
const lib = require("@clusterio/lib");
|
|
|
|
const { testMatrix } = require("../common");
|
|
const {
|
|
slowTest, exec, execCtl, execCtlProcess, sendRcon, getControl,
|
|
requiresFactorio, hasFactorio,
|
|
} = require("./index");
|
|
|
|
const instId = 48;
|
|
const instName = "Integration";
|
|
const instSetConfig = `instance config set ${instName}`;
|
|
const instAltId = 49;
|
|
const instAltName = "IntegrationAlt";
|
|
|
|
const requireApi = [
|
|
"local api =",
|
|
"package.loaded['modules/clusterio/api']", // 1.1.110
|
|
"or package.loaded['__level__/modules/clusterio/api.lua']", // 2.0.0
|
|
].join(" ");
|
|
|
|
function getUser(name) {
|
|
return getControl().send(new lib.UserGetRequest(name));
|
|
}
|
|
|
|
describe("Clusterio Instance", function() {
|
|
requiresFactorio(this);
|
|
before(async function() {
|
|
this.timeout(20000);
|
|
const visibility = JSON.stringify({ lan: true, public: false }).replace(
|
|
/"/g, process.platform === "win32" ? '""' : '\\"'
|
|
);
|
|
|
|
// Create a new instance
|
|
await execCtl(`instance create ${instName} --id ${instId}`);
|
|
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`);
|
|
await execCtl(`instance assign ${instName} 4`);
|
|
// Exclude main test instance from start-all to prevent interference
|
|
await execCtl(`instance config set ${instName} instance.exclude_from_start_all true`);
|
|
|
|
for (const plugin of [
|
|
"global_chat", "research_sync", "statistics_exporter", "subspace_storage", "player_auth", "inventory_sync",
|
|
]) {
|
|
await execCtl(`instance config set ${instName} ${plugin}.load_plugin false`);
|
|
}
|
|
|
|
// Create a new alt instance
|
|
await execCtl(`instance create ${instAltName} --id ${instAltId}`);
|
|
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`);
|
|
await execCtl(`instance assign ${instAltName} 4`);
|
|
// Exclude alt test instance from start-all to prevent interference
|
|
await execCtl(`instance config set ${instAltName} instance.exclude_from_start_all true`);
|
|
await execCtl(`instance start ${instAltName}`);
|
|
});
|
|
after(async function() {
|
|
if (!hasFactorio()) {
|
|
return;
|
|
}
|
|
this.timeout(20000);
|
|
// Delete the instances
|
|
await execCtl(`instance delete ${instName}`);
|
|
await execCtl(`instance stop ${instAltName}`);
|
|
await execCtl(`instance delete ${instAltName}`);
|
|
});
|
|
for (const [savePatchingEnabled, scriptCommandsEnabled] of testMatrix([true, false], [true, false])) {
|
|
const strSavePatching = `save patching: ${savePatchingEnabled ? "Enabled" : "Disabled"}`;
|
|
const strScriptCommands = `script commands: ${scriptCommandsEnabled ? "Enabled" : "Disabled"}`;
|
|
async function allowSendingScriptCommands() {
|
|
if (!scriptCommandsEnabled) {
|
|
// Enable command to prevent "Attempted to use script command when disabled"
|
|
// This works because we only disable achievements on startup, this will break if we make it live update
|
|
await execCtl(`${instSetConfig} factorio.enable_script_commands true`);
|
|
}
|
|
}
|
|
describe(`Integration - ${strSavePatching}, ${strScriptCommands}`, function() {
|
|
before(async function() {
|
|
this.timeout(20000);
|
|
// Create and start a patched save
|
|
await execCtl(`${instSetConfig} factorio.enable_save_patching ${savePatchingEnabled}`);
|
|
await execCtl(`${instSetConfig} factorio.enable_script_commands ${scriptCommandsEnabled}`);
|
|
await execCtl(`instance save create ${instName}`);
|
|
await execCtl(`instance start ${instName}`);
|
|
});
|
|
after(async function() {
|
|
// Stop the instance
|
|
await execCtl(`instance stop ${instName}`);
|
|
});
|
|
describe("Disable achievements", function() {
|
|
it("should only disable achievements when script commands are enabled", async function() {
|
|
await allowSendingScriptCommands();
|
|
const res = await sendRcon(instId, "/sc rcon.print('Disabled')");
|
|
assert.equal(res === "Disabled\n", scriptCommandsEnabled);
|
|
});
|
|
});
|
|
describe("Player events", function() {
|
|
if (savePatchingEnabled) {
|
|
// IPC expects save patching to be enabled
|
|
it("should do nothing for an unknown type", async function() {
|
|
await assert.rejects(getUser("DoesNotExist"), "User should not exist");
|
|
await allowSendingScriptCommands();
|
|
await sendRcon(instId,
|
|
`/sc ${requireApi} api.send_json("player_event",` +
|
|
"{ type='invalid type', name='DoesNotExist' })"
|
|
);
|
|
await assert.rejects(getUser("DoesNotExist"), "User was created");
|
|
});
|
|
it("should respond to a player join and leave event", async function() {
|
|
await allowSendingScriptCommands();
|
|
await sendRcon(instId,
|
|
`/sc ${requireApi} api.send_json("player_event",` +
|
|
"{ type='join', name='JoiningPlayer' })"
|
|
);
|
|
const userJoin = await getUser("JoiningPlayer");
|
|
assert(userJoin.instances.has(instId), "Player is not shown as online");
|
|
await sendRcon(instId,
|
|
`/sc ${requireApi} api.send_json("player_event",` +
|
|
"{ type='leave', name='JoiningPlayer', reason='quit' })"
|
|
);
|
|
const userLeave = await getUser("JoiningPlayer");
|
|
assert(!userLeave.instances.has(instId), "Player is not shown as offline");
|
|
});
|
|
}
|
|
it("should respond to a player ban and unban event", async function() {
|
|
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 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");
|
|
});
|
|
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
|
|
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");
|
|
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",
|
|
"User is not promoted on alt"
|
|
);
|
|
|
|
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");
|
|
const isAltDemoted = await sendRcon(instAltId, "/demote AdminPlayer");
|
|
assert.equal(isAltDemoted,
|
|
"AdminPlayer is not in the admin list.\n",
|
|
"User is not demoted on alt"
|
|
);
|
|
});
|
|
it("should respond to a player whitelist add and remove event", async function() {
|
|
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 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"
|
|
);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
});
|
|
describe("start-all and stop-all commands", function() {
|
|
requiresFactorio(this);
|
|
// Test instance IDs for start-all/stop-all tests
|
|
const testInstId1 = 50;
|
|
const testInstId2 = 51;
|
|
const testInstId3 = 52;
|
|
const testInstName1 = "StartAllTest1";
|
|
const testInstName2 = "StartAllTest2";
|
|
const testInstName3 = "StartAllTest3";
|
|
|
|
before(async function() {
|
|
this.timeout(30000);
|
|
|
|
// Create test instances for start-all/stop-all testing
|
|
await execCtl(`instance create ${testInstName1} --id ${testInstId1}`);
|
|
await execCtl(`instance create ${testInstName2} --id ${testInstId2}`);
|
|
await execCtl(`instance create ${testInstName3} --id ${testInstId3}`);
|
|
|
|
// Configure instances
|
|
await execCtl(`instance assign ${testInstName1} 4`);
|
|
await execCtl(`instance assign ${testInstName2} 4`);
|
|
await execCtl(`instance assign ${testInstName3} 4`);
|
|
|
|
// Exclude testInstName3 from start-all by default
|
|
await execCtl(`instance config set ${testInstName3} instance.exclude_from_start_all true`);
|
|
|
|
// Disable plugins to speed up testing
|
|
for (const instanceName of [testInstName1, testInstName2, testInstName3]) {
|
|
for (const plugin of [
|
|
"global_chat", "research_sync", "statistics_exporter",
|
|
"subspace_storage", "player_auth", "inventory_sync",
|
|
]) {
|
|
await execCtl(`instance config set ${instanceName} ${plugin}.load_plugin false`);
|
|
}
|
|
}
|
|
});
|
|
|
|
after(async function() {
|
|
this.timeout(30000);
|
|
// Clean up test instances - ensure they are stopped first
|
|
await execCtl(`instance stop ${testInstName1}`).catch(() => {});
|
|
await execCtl(`instance stop ${testInstName2}`).catch(() => {});
|
|
await execCtl(`instance stop ${testInstName3}`).catch(() => {});
|
|
|
|
await execCtl(`instance delete ${testInstName1}`).catch(() => {});
|
|
await execCtl(`instance delete ${testInstName2}`).catch(() => {});
|
|
await execCtl(`instance delete ${testInstName3}`).catch(() => {});
|
|
});
|
|
|
|
async function getInstanceStatus(id) {
|
|
const instances = await getControl().send(new lib.InstanceDetailsListRequest());
|
|
const instanceMap = new Map(instances.map(instance => [instance.id, instance]));
|
|
return instanceMap.get(id)?.status;
|
|
}
|
|
|
|
describe("start-all command", function() {
|
|
beforeEach(async function() {
|
|
this.timeout(15000);
|
|
// Ensure all test instances are stopped before each test
|
|
await execCtl(`instance stop ${testInstName1}`).catch(() => {});
|
|
await execCtl(`instance stop ${testInstName2}`).catch(() => {});
|
|
await execCtl(`instance stop ${testInstName3}`).catch(() => {});
|
|
});
|
|
|
|
afterEach(async function() {
|
|
this.timeout(15000);
|
|
// Clean up: stop any instances that were started during tests
|
|
await execCtl(`instance stop ${testInstName1}`).catch(() => {});
|
|
await execCtl(`instance stop ${testInstName2}`).catch(() => {});
|
|
await execCtl(`instance stop ${testInstName3}`).catch(() => {});
|
|
});
|
|
|
|
it("starts only stopped instances that are not excluded by default", async function() {
|
|
this.timeout(20000);
|
|
// Start one instance to verify it's not started again
|
|
await execCtl(`instance start ${testInstName2}`);
|
|
|
|
// Verify initial state
|
|
assert.equal(await getInstanceStatus(testInstId1), "stopped");
|
|
assert.equal(await getInstanceStatus(testInstId2), "running");
|
|
assert.equal(await getInstanceStatus(testInstId3), "stopped");
|
|
|
|
// Run start-all without force
|
|
await execCtl("instance start-all");
|
|
|
|
// Verify results - only testInstName1 should be started
|
|
// testInstName2 was already running, testInstName3 is excluded
|
|
assert.equal(await getInstanceStatus(testInstId1), "running");
|
|
assert.equal(await getInstanceStatus(testInstId2), "running");
|
|
assert.equal(await getInstanceStatus(testInstId3), "stopped");
|
|
});
|
|
|
|
it("starts excluded instances when --force flag is provided", async function() {
|
|
this.timeout(20000);
|
|
// Verify initial state - all stopped
|
|
assert.equal(await getInstanceStatus(testInstId1), "stopped");
|
|
assert.equal(await getInstanceStatus(testInstId2), "stopped");
|
|
assert.equal(await getInstanceStatus(testInstId3), "stopped");
|
|
|
|
// Run start-all with force
|
|
await execCtl("instance start-all --force");
|
|
|
|
// Verify results - all instances should be started including excluded one
|
|
assert.equal(await getInstanceStatus(testInstId1), "running");
|
|
assert.equal(await getInstanceStatus(testInstId2), "running");
|
|
assert.equal(await getInstanceStatus(testInstId3), "running");
|
|
});
|
|
});
|
|
|
|
describe("stop-all command", function() {
|
|
beforeEach(async function() {
|
|
this.timeout(15000);
|
|
// Ensure a known state before each test
|
|
await execCtl(`instance stop ${testInstName1}`).catch(() => {});
|
|
await execCtl(`instance stop ${testInstName2}`).catch(() => {});
|
|
await execCtl(`instance stop ${testInstName3}`).catch(() => {});
|
|
});
|
|
|
|
afterEach(async function() {
|
|
this.timeout(15000);
|
|
// Clean up: stop any instances that were started during tests
|
|
await execCtl(`instance stop ${testInstName1}`).catch(() => {});
|
|
await execCtl(`instance stop ${testInstName2}`).catch(() => {});
|
|
await execCtl(`instance stop ${testInstName3}`).catch(() => {});
|
|
});
|
|
|
|
it("stops only running or starting instances", async function() {
|
|
this.timeout(20000);
|
|
// Start some instances to test stop-all
|
|
await execCtl(`instance start ${testInstName1}`);
|
|
await execCtl(`instance start ${testInstName3}`);
|
|
|
|
// Verify initial state
|
|
assert.equal(await getInstanceStatus(testInstId1), "running");
|
|
assert.equal(await getInstanceStatus(testInstId2), "stopped");
|
|
assert.equal(await getInstanceStatus(testInstId3), "running");
|
|
|
|
await execCtl("instance stop-all");
|
|
|
|
// Verify results - running instances should be stopped, stopped ones unchanged
|
|
assert.equal(await getInstanceStatus(testInstId1), "stopped");
|
|
assert.equal(await getInstanceStatus(testInstId2), "stopped");
|
|
assert.equal(await getInstanceStatus(testInstId3), "stopped");
|
|
});
|
|
});
|
|
});
|