Files
factorio-clusterio/test/integration/index.js
T
Hornwitser 295cc7543a Reverse argument order for sendTo
It makes more sense to put the address first then have the large
structure to send as the second argument to sendTo.  The opposite order
was originally chosen to be similar to that of sendto in BSD sockets.
2023-07-27 00:24:24 +02:00

287 lines
9.3 KiB
JavaScript

/* eslint-disable no-console */
"use strict";
const path = require("path");
const fs = require("fs-extra");
const child_process = require("child_process");
const jwt = require("jsonwebtoken");
const phin = require("phin");
const util = require("util");
const events = require("events");
const libData = require("@clusterio/lib/data");
const libLink = require("@clusterio/lib/link");
const { LineSplitter } = require("@clusterio/lib/stream");
const { ConsoleTransport, logger } = require("@clusterio/lib/logging");
const libLoggingUtils = require("@clusterio/lib/logging_utils");
// Make sure permissions from plugins are loaded
require("../../plugins/global_chat/info");
require("../../plugins/player_auth/info");
require("../../plugins/research_sync/info");
require("../../plugins/statistics_exporter/info");
require("../../plugins/subspace_storage/info");
class TestControl extends libLink.Link {
constructor(connector, subscribe = true) {
super(connector);
this.hostUpdates = [];
this.instanceUpdates = [];
this.saveListUpdates = [];
this.modUpdates = [];
this.modPackUpdates = [];
this.userUpdates = [];
this.connector.on("connect", () => {
if (!subscribe) {
return;
}
this.send(
new libData.HostSetSubscriptionsRequest(true, [])
).catch(err => logger.error(`Error setting host subscriptions:\n${err.stack}`));
this.send(
new libData.InstanceDetailsSetSubscriptionsRequest(true, [])
).catch(err => logger.error(`Error setting instance subscriptions:\n${err.stack}`));
this.send(
new libData.InstanceSetSaveListSubscriptionsRequest(true, [])
).catch(err => logger.error(`Error setting save list subscriptions:\n${err.stack}`));
this.send(
new libData.ModSetSubscriptionsRequest(true, [])
).catch(err => logger.error(`Error setting mod subscriptions:\n${err.stack}`));
this.send(
new libData.UserSetSubscriptionsRequest(true, [])
).catch(err => logger.error(`Error setting user subscriptions:\n${err.stack}`));
});
this.handle(libData.AccountUpdateEvent);
this.handle(libData.HostUpdateEvent, this.handleHostUpdateEvent.bind(this));
this.handle(libData.InstanceDetailsUpdateEvent, this.handleInstanceDetailsUpdateEvent.bind(this));
this.handle(libData.InstanceSaveListUpdateEvent, this.handleInstanceSaveListUpdateEvent.bind(this));
this.handle(libData.ModUpdateEvent, this.handleModUpdateEvent.bind(this));
this.handle(libData.ModPackUpdateEvent, this.handleModPackUpdateEvent.bind(this));
this.handle(libData.UserUpdateEvent, this.handleUserUpdateEvent.bind(this));
}
async handleHostUpdateEvent(event) {
this.hostUpdates.push(event.update);
}
async handleInstanceDetailsUpdateEvent(event) {
this.instanceUpdates.push(event.details);
}
async handleInstanceSaveListUpdateEvent(event) {
this.saveListUpdates.push(event);
}
async handleModUpdateEvent(event) {
this.modUpdates.push(event.mod);
}
async handleModPackUpdateEvent(event) {
this.modPackUpdates.push(event.modPack);
}
async handleUserUpdateEvent(event) {
this.userUpdates.push(event.user);
}
}
class TestControlConnector extends libLink.WebSocketClientConnector {
register() {
this.sendHandshake(
new libData.MessageRegisterControl(
new libData.RegisterControlData(
this.token,
"clusterioctl",
"test",
)
)
);
}
}
// Mark that this test takes a lot of time, or depeneds on a test
// that takes a lot of time.
function slowTest(test) {
// eslint-disable-next-line node/no-process-env
if (process.env.FAST_TEST) {
test.skip();
}
test.timeout(20000);
}
async function get(urlPath) {
let res = await phin({
method: "GET",
url: `https://localhost:4443${urlPath}`,
core: { rejectUnauthorized: false },
});
if (res.statusCode !== 200) {
throw new Error(`Got response code ${res.statusCode}, content: ${res.body}`);
}
return res;
}
let controllerProcess;
let hostProcess;
let control;
let url = "https://localhost:4443/";
let controlToken = jwt.sign({ aud: "user", user: "test" }, Buffer.from("TestSecretDoNotUse", "base64"));
let instancesDir = path.join("temp", "test", "instances");
let databaseDir = path.join("temp", "test", "database");
let pluginListPath = path.join("temp", "test", "plugin-list.json");
let controllerConfigPath = path.join("temp", "test", "config-controller.json");
let hostConfigPath = path.join("temp", "test", "config-host.json");
let controlConfigPath = path.join("temp", "test", "config-control.json");
async function exec(command, options = {}) {
// Uncomment to show commands run in tests
// console.log(command);
options = { cwd: path.join("temp", "test"), ...options };
return await util.promisify(child_process.exec)(command, options);
}
async function execCtl(...args) {
args[0] = `node ../../packages/ctl ${args[0]}`;
return await exec(...args);
}
async function sendRcon(instanceId, command) {
return await control.sendTo({ instanceId }, new libData.InstanceSendRconRequest(command));
}
function getControl() {
return control;
}
function spawn(name, cmd, waitFor) {
return new Promise((resolve, reject) => {
console.log(cmd);
let parts = cmd.split(" ");
let process = child_process.spawn(parts[0], parts.slice(1), { cwd: path.join("temp", "test") });
let stdout = new LineSplitter({ readableObjectMode: true });
stdout.on("data", line => {
line = line.toString("utf8");
if (waitFor.test(line)) {
resolve(process);
}
console.log(name, line);
});
let stderr = new LineSplitter({ readableObjectMode: true });
stderr.on("data", line => { console.log(name, line.toString("utf8")); });
process.stdout.pipe(stdout);
process.stderr.pipe(stderr);
});
}
before(async function() {
this.timeout(40000);
// Some integration tests may cause log events
logger.add(new ConsoleTransport({
level: "info",
format: new libLoggingUtils.TerminalFormat(),
}));
await fs.remove(databaseDir);
await fs.remove(instancesDir);
await fs.remove(pluginListPath);
await fs.remove(controllerConfigPath);
await fs.remove(hostConfigPath);
await fs.remove(controlConfigPath);
await fs.ensureDir(path.join("temp", "test"));
await exec("node ../../packages/controller config set controller.auth_secret TestSecretDoNotUse");
await exec("node ../../packages/controller config set controller.http_port 8880");
await exec("node ../../packages/controller config set controller.https_port 4443");
await exec("node ../../packages/controller config set controller.heartbeat_interval 0.25");
await exec("node ../../packages/controller config set controller.session_timeout 2");
await exec("node ../../packages/controller config set controller.tls_certificate ../../test/file/tls/cert.pem");
await exec("node ../../packages/controller config set controller.tls_private_key ../../test/file/tls/key.pem");
await exec("node ../../packages/ctl plugin add ../../plugins/global_chat");
await exec("node ../../packages/ctl plugin add ../../plugins/research_sync");
await exec("node ../../packages/ctl plugin add ../../plugins/statistics_exporter");
await exec("node ../../packages/ctl plugin add ../../plugins/subspace_storage");
await exec("node ../../packages/ctl plugin add ../../plugins/player_auth");
await exec("node ../../packages/controller bootstrap create-admin test");
await exec("node ../../packages/controller bootstrap create-ctl-config test");
await exec("node ../../packages/ctl control-config set control.tls_ca ../../test/file/tls/cert.pem");
controllerProcess = await spawn("controller:", "node ../../packages/controller run", /Started controller/);
await execCtl("host create-config --id 4 --name host --generate-token");
await exec(`node ../../packages/host config set host.factorio_directory ${path.join("..", "..", "factorio")}`);
await exec("node ../../packages/host config set host.tls_ca ../../test/file/tls/cert.pem");
hostProcess = await spawn("host:", "node ../../packages/host run", /Started host/);
let tlsCa = await fs.readFile("test/file/tls/cert.pem");
let controlConnector = new TestControlConnector(url, 2, tlsCa);
controlConnector.token = controlToken;
control = new TestControl(controlConnector);
await controlConnector.connect();
const testPack = libData.ModPack.fromJSON({});
testPack.id = 12;
testPack.name = "subspace_storage-pack";
testPack.factorioVersion = "1.1.0";
testPack.mods.set("clusterio_lib", { name: "clusterio_lib", enabled: true, version: "0.1.2" });
testPack.mods.set("subspace_storage", { name: "subspace_storage", enabled: true, version: "1.99.8" });
await control.sendTo("controller", new libData.ModPackCreateRequest(testPack));
await control.sendTo(
"controller",
new libData.ControllerConfigSetFieldRequest("controller.default_mod_pack_id", "12"),
);
});
after(async function() {
this.timeout(20000);
if (hostProcess) {
console.log("Shutting down host");
hostProcess.kill("SIGINT");
await events.once(hostProcess, "exit");
}
if (controllerProcess) {
console.log("Shutting down controller");
controllerProcess.kill("SIGINT");
await events.once(controllerProcess, "exit");
}
if (control) {
await control.connector.close();
}
});
// Ensure the test processes are stopped.
process.on("exit", () => {
if (hostProcess) { hostProcess.kill(); }
if (controllerProcess) { controllerProcess.kill(); }
});
module.exports = {
TestControl,
TestControlConnector,
slowTest,
get,
exec,
execCtl,
sendRcon,
getControl,
spawn,
url,
controlToken,
instancesDir,
databaseDir,
controllerConfigPath,
hostConfigPath,
controlConfigPath,
};