diff --git a/.eslintignore b/.eslintignore index d3cfadef..849ddff3 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1 @@ -packages/master/static -plugins/*/static +dist/ diff --git a/.gitignore b/.gitignore index 08f2a543..d93623fb 100644 --- a/.gitignore +++ b/.gitignore @@ -46,10 +46,6 @@ static/export saves /temp/ -# Web build -/packages/master/static -/plugins/*/static - # IDE .idea/ .vs/ diff --git a/docs/developing-for-clusterio.md b/docs/developing-for-clusterio.md index cfbcfe4c..c6c4271e 100644 --- a/docs/developing-for-clusterio.md +++ b/docs/developing-for-clusterio.md @@ -161,11 +161,13 @@ which accepts a table and calls `add_lib` for each value in the table. Communicating with Clusterio ---------------------------- -There's a poorly to virtuarly undocumented HTTP interface that can be -used to communicate with Clusterio to do things like get the status of -instances, grab items from the store, run commands on instances, etc, -etc. For now you can read the source code, and maybe ask on the Discord -for some pointers if you get stuck. +Clusterio uses a homebrew protocol based on sending JSON payloads over +a WebSocket connection. See [socket.md](socket.md) for the +implementation details of it. + +It's also possible to write a plugin for Clusterio that exposes a custom +interface over HTTP, WebSocket or any other technology supported by +Node.js. Clusterio Modules @@ -177,7 +179,9 @@ modules that are loaded into the game if you don't need the capabilites of the plugin system. The save patcher puts modules into the `modules` folder of the Factorio save and adds code to `control.lua` to load the module according to the `load` and `require` options to the module.json -file. +file. Like with scenarios for Clusterio the [event_handler +interface](#event_handler-interface) has to be used for any event +subscriptions in modules. Stand alone modules are placed into the modules folder of Clusterio, plugin modules are located in the module folder of the plugin. In diff --git a/docs/writing-plugins.md b/docs/writing-plugins.md index ae5ab831..62b8c854 100644 --- a/docs/writing-plugins.md +++ b/docs/writing-plugins.md @@ -621,7 +621,7 @@ until the connection is re-established. This means that if your plugin sends a lot of events or requests, they can end up being queued up in a buffer for a long time and sent out all at once. To avoid this you should be throtteling and/or stopping your requests/events after `drop` -has been raised, and continue back as normal when `connect` is raised. +has been raised, and continue back as normal when `resume` is raised. Collecting Statistics @@ -660,7 +660,7 @@ set it accordingly, for example: ); // Somewhere in the instance plugin code - barMetric.labels(String(this.instance.config.get("instance.id"))).set(someValue); + barMetric.labels(String(this.instance.id)).set(someValue); Metrics are automatically registered to the default registry, and this default registry is automatically polled by the master server on slaves. diff --git a/package.json b/package.json index 8d33925a..541a855a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "root", "private": true, "scripts": { - "test": "mocha --exit --check-leaks -R spec --recursive test plugins/*/test", + "test": "mocha --exit --check-leaks -R spec --recursive test plugins/global_chat/test plugins/research_sync/test plugins/statistics_exporter/test plugins/subspace_storage/test", "fast-test": "FAST_TEST=y npm test", "cover": "nyc npm test", "fast-cover": "FAST_TEST=y nyc npm test", diff --git a/packages/ctl/ctl.js b/packages/ctl/ctl.js index 0c6dd755..beb4159a 100755 --- a/packages/ctl/ctl.js +++ b/packages/ctl/ctl.js @@ -368,7 +368,11 @@ roleCommands.add(new libCommand.Command({ yargs.options({ "name": { describe: "New name for role", nargs: 1, type: "string" }, "description": { describe: "New description for role", nargs: 1, type: "string" }, - "permissions": { describe: "New permissions for role", array: true, type: "string" }, + "set-perms": { describe: "Set permissions for role", array: true, type: "string" }, + "add-perms": { describe: "Add permissions to role", array: true, type: "string", conflicts: "set-perms" }, + "remove-perms": { + describe: "Remove permissions from role", array: true, type: "string", conflicts: "set-perms", + }, "grant-default": { describe: "Add default permissions to role", nargs: 0, type: "boolean" }, }); }], @@ -381,8 +385,18 @@ roleCommands.add(new libCommand.Command({ if (args.description !== undefined) { role.description = args.description; } - if (args.permissions !== undefined) { - role.permissions = args.permissions; + if (args.addPerms) { + role.permissions = role.permissions.concat(args.addPerms); + } + if (args.removePerms) { + let perms = new Set(role.permissions); + for (let perm of args.removePerms) { + perms.delete(perm); + } + role.permissions = [...perms]; + } + if (args.setPerms !== undefined) { + role.permissions = args.setPerms; } await libLink.messages.updateRole.send(control, role); @@ -621,7 +635,7 @@ class Control extends libLink.Link { */ this.plugins = controlPlugins; for (let controlPlugin of controlPlugins.values()) { - libPlugin.attachPluginMessages(this, controlPlugin.info, controlPlugin); + libPlugin.attachPluginMessages(this, controlPlugin); } /** diff --git a/packages/lib/factorio/server.js b/packages/lib/factorio/server.js index d2c764a6..896860b1 100644 --- a/packages/lib/factorio/server.js +++ b/packages/lib/factorio/server.js @@ -11,24 +11,9 @@ const ini = require("ini"); const rconClient = require("rcon-client"); const libErrors = require("@clusterio/lib/errors"); const { logger } = require("@clusterio/lib/logging"); +const { escapeRegExp } = require("@clusterio/lib/helpers"); -/** - * Escapes text for inclusion in a RegExp - * - * Adds \ character in front of special meta characters in the passsed in - * text so that it can be embedded into a RegExp and only match the text. - * - * See https://stackoverflow.com/a/9310752 - * - * @param {string} text - Text to escape RegExp meta chars in. - * @returns {string} escaped text. - * @inner - */ -function escapeRegExp(text) { - return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); -} - /** * Determines the version of Factorio the datadir is pointing to by * reading the changelog.txt in it. diff --git a/packages/lib/helpers.js b/packages/lib/helpers.js index cbbed42e..17464ec5 100644 --- a/packages/lib/helpers.js +++ b/packages/lib/helpers.js @@ -75,9 +75,26 @@ async function readStream(stream) { } +/** + * Escapes text for inclusion in a RegExp + * + * Adds \ character in front of special meta characters in the passsed in + * text so that it can be embedded into a RegExp and only match the text. + * + * See https://stackoverflow.com/a/9310752 + * + * @param {string} text - Text to escape RegExp meta chars in. + * @returns {string} escaped text. + */ +function escapeRegExp(text) { + return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); +} + + module.exports = { basicType, wait, timeout, readStream, + escapeRegExp, }; diff --git a/packages/lib/link/connectors.js b/packages/lib/link/connectors.js index ccf24904..f3881d26 100644 --- a/packages/lib/link/connectors.js +++ b/packages/lib/link/connectors.js @@ -404,7 +404,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector { } this._startedReconnect = null; this._connected = true; - this.emit("connect"); + this.emit("resume"); } else if (type === "invalidate") { this._heartbeatInterval = null; diff --git a/packages/lib/plugin.js b/packages/lib/plugin.js index 08795c84..4dce8257 100644 --- a/packages/lib/plugin.js +++ b/packages/lib/plugin.js @@ -148,13 +148,12 @@ class BaseInstancePlugin { /** * Called when an event on the master connection happens * - * The event param may be one of connect, drop and close and has the - * following meaning: + * The event param may be one of connect, drop, resume and close and has + * the following meaning: * * ##### connect * - * Invoked when a new connection to the master has been established, or - * the existing one that have previously been dropped is re-established. + * Invoked when a new connection to the master has been established. * * ##### drop * @@ -166,6 +165,11 @@ class BaseInstancePlugin { * memory on the slave and sent all in one go when the connection is * re-established again. * + * ##### resume + * + * Invoked when the connection that had previously dropped is + * re-established. + * * ##### close * * Invoked when the connection to the master has been closed. This @@ -173,7 +177,7 @@ class BaseInstancePlugin { * send any messages that goes to or via the master server after the * connection has been closed and before a new one is established. * - * @param {string} event - one of connect, drop, and close + * @param {string} event - one of connect, drop, resume and close */ onMasterConnectionEvent(event) { } @@ -293,13 +297,12 @@ class BaseMasterPlugin { /** * Called when an event on a slave connection happens * - * The event param may be one of connect, drop and close and has the - * following meaning: + * The event param may be one of connect, drop, resume and close and has + * the following meaning: * * ##### connect * - * Invoked when a new connection to the slave has been established, or - * an existing one that had previously been dropped is re-established. + * Invoked when a new connection to the slave has been established. * * ##### drop * @@ -311,13 +314,18 @@ class BaseMasterPlugin { * memory on the master and sent all in one go when the connection is * re-established again. * + * ##### resume + * + * Invoked when a connection that had previously been dropped is + * re-established + * * ##### close * * Invoked when the connection to the slave has been closed. * * @param {module:master/master~SlaveConnection} connection - * The connection the event occured on. - * @param {string} event - one of connect, drop, and close + * @param {string} event - one of connect, drop, resume and close */ onSlaveConnectionEvent(connection, event) { } @@ -418,19 +426,18 @@ class BaseControlPlugin { /** * Attach plugin messages * - * Attaches all messages defined in the `.message` property of the - * `pluginInfo` passed with handlers taken from `pluin`. + * Attaches all messages defined in the `.info.message` property of the + * `plugin` passed with handlers taken from the `pluin`. * * @param {module:lib/link.Link} link - Link to attach handlers to. - * @param {object} pluginInfo - Plugin info object. * @param {module:lib/plugin.BasePlugin} plugin - - * The instance of the plugin to use handlers from. + * The instance of the plugin attach messages and handlers from. */ -function attachPluginMessages(link, pluginInfo, plugin) { - let messageDefinitions = pluginInfo.messages || []; +function attachPluginMessages(link, plugin) { + let messageDefinitions = plugin.info.messages || []; for (let [name, messageFormat] of Object.entries(messageDefinitions)) { - if (messageFormat.plugin !== pluginInfo.name) { - throw new Error(`Type of ${name} message must start with "${pluginInfo.name}:"`); + if (messageFormat.plugin !== plugin.info.name) { + throw new Error(`Type of ${name} message must start with "${plugin.info.name}:"`); } let handler = name + messageFormat.handlerSuffix; @@ -444,7 +451,7 @@ function attachPluginMessages(link, pluginInfo, plugin) { } catch (err) { if (err.code === "MISSING_LINK_HANDLER") { - err.plugin = pluginInfo.name; + err.plugin = plugin.info.name; err.handler = handler; } diff --git a/packages/master/master.js b/packages/master/master.js index dbc1ed1a..e5a2a1aa 100755 --- a/packages/master/master.js +++ b/packages/master/master.js @@ -115,7 +115,7 @@ function serveWeb(route) { } // Set folder to serve static content from (the website) -app.use(express.static(path.join(__dirname, "static"))); +app.use(express.static(path.join(__dirname, "dist", "web"))); app.use(express.static("static")); // Used for data export files const slaveMappingGauge = new libPrometheus.Gauge( @@ -562,7 +562,7 @@ class BaseConnection extends libLink.Link { super("master", target, connector); libLink.attachAllMessages(this); for (let masterPlugin of masterPlugins.values()) { - libPlugin.attachPluginMessages(this, masterPlugin.info, masterPlugin); + libPlugin.attachPluginMessages(this, masterPlugin); } } @@ -1168,7 +1168,7 @@ class SlaveConnection extends BaseConnection { } }); - for (let event of ["connect", "drop", "close"]) { + for (let event of ["connect", "drop", "resume", "close"]) { // eslint-disable-next-line no-loop-func this.connector.on(event, () => { for (let masterPlugin of masterPlugins.values()) { @@ -1397,7 +1397,7 @@ class WebSocketServerConnector extends libLink.WebSocketBaseConnector { for (let message of this._sendBuffer) { this._socket.send(JSON.stringify(message)); } - this.emit("connect"); + this.emit("resume"); } setTimeout(timeout) { @@ -2025,8 +2025,8 @@ async function startServer(args) { } let pluginPackagePath = require.resolve(path.posix.join(pluginInfo.requirePath, "package.json")); - let staticPath = path.join(path.dirname(pluginPackagePath), "static"); - app.use(`/plugin/${pluginInfo.name}`, express.static(staticPath)); + let webPath = path.join(path.dirname(pluginPackagePath), "dist", "web"); + app.use(`/plugin/${pluginInfo.name}`, express.static(webPath)); } // Load plugins diff --git a/packages/master/webpack.config.js b/packages/master/webpack.config.js index 756d809d..8fca09c6 100644 --- a/packages/master/webpack.config.js +++ b/packages/master/webpack.config.js @@ -9,11 +9,11 @@ module.exports = (env = {}) => merge(common(env), { context: __dirname, entry: "./web/index.jsx", devServer: { - contentBase: "./static", + contentBase: "./dist/web", }, output: { filename: "bundle.js", - path: path.resolve(__dirname, "static"), + path: path.resolve(__dirname, "dist", "web"), }, plugins: [ new webpack.container.ModuleFederationPlugin({ diff --git a/packages/slave/slave.js b/packages/slave/slave.js index 7e474879..4e2ad729 100755 --- a/packages/slave/slave.js +++ b/packages/slave/slave.js @@ -128,9 +128,15 @@ class Instance extends libLink.Link { this.plugins = new Map(); this.config = instanceConfig; + /** + * ID of this instance, equivalenet to `instance.config.get("instance.id")`. + * @constant {number} + */ + this.id = this.config.get("instance.id"); + this.logger = logger.child({ - instance_id: this.config.get("instance.id"), - instance_name: this.config.get("instance.name"), + instance_id: this.id, + instance_name: this.name, }); this._configFieldChanged = (group, field, prev) => { @@ -168,7 +174,7 @@ class Instance extends libLink.Link { ); this.server.on("output", (parsed, line) => { - this.logger.log("server", { message: line, instance_id: this.config.get("instance.id"), parsed }); + this.logger.log("server", { message: line, instance_id: this.id, parsed }); libPlugin.invokeHook(this.plugins, "onOutput", parsed, line); }); @@ -185,7 +191,7 @@ class Instance extends libLink.Link { this.server.on("ipc-player_event", event => { libLink.messages.playerEvent.send(this, { - instance_id: this.config.get("instance.id"), + instance_id: this.id, ...event, }); libPlugin.invokeHook(this.plugins, "onPlayerEvent", event); @@ -193,7 +199,7 @@ class Instance extends libLink.Link { } async sendRcon(message, expectEmpty, plugin = "") { - let instanceId = String(this.config.get("instance.id")); + let instanceId = String(this.id); let observeDuration = instanceRconCommandDuration.labels(instanceId).startTimer(); try { return await this.server.sendRcon(message, expectEmpty); @@ -205,13 +211,13 @@ class Instance extends libLink.Link { async _autosave(name) { let stat = await fs.stat(this.path("saves", `${name}.zip`)); - instanceFactorioAutosaveSize.labels(String(this.config.get("instance.id"))).set(stat.size); + instanceFactorioAutosaveSize.labels(String(this.id)).set(stat.size); } notifyStatus(status) { this._status = status; libLink.messages.instanceStatusChanged.send(this, { - instance_id: this.config.get("instance.id"), status, + instance_id: this.id, status, }); } @@ -238,7 +244,7 @@ class Instance extends libLink.Link { collector instanceof libPrometheus.ValueCollector && collector.metric.labels.includes("instance_id") ) { - collector.removeAll({ instance_id: String(this.config.get("instance.id")) }); + collector.removeAll({ instance_id: String(this.id) }); } } @@ -254,7 +260,7 @@ class Instance extends libLink.Link { let instancePlugin = new InstancePluginClass(pluginInfo, this, slave); this.plugins.set(pluginInfo.name, instancePlugin); await instancePlugin.init(); - libPlugin.attachPluginMessages(this, pluginInfo, instancePlugin); + libPlugin.attachPluginMessages(this, instancePlugin); this.logger.info(`Loaded plugin ${pluginInfo.name} in ${Date.now() - pluginLoadStarted}ms`); } @@ -285,7 +291,7 @@ class Instance extends libLink.Link { for (let [name, plugin] of this.plugins) { plugins[name] = plugin.info.version; } - libLink.messages.instanceInitialized.send(this, { instance_id: this.config.get("instance.id"), plugins }); + libLink.messages.instanceInitialized.send(this, { instance_id: this.id, plugins }); } /** @@ -543,9 +549,8 @@ class Instance extends libLink.Link { * Update instance information on the Factorio side */ async updateInstanceData() { - let name = libLuaTools.escapeString(this.config.get("instance.name")); - let id = this.config.get("instance.id"); - await this.sendRcon(`/sc clusterio_private.update_instance(${id}, "${name}")`, true); + let name = libLuaTools.escapeString(this.name); + await this.sendRcon(`/sc clusterio_private.update_instance(${this.id}, "${name}")`, true); } async updateFactorioSettings(current, previous) { @@ -648,8 +653,8 @@ class Instance extends libLink.Link { let pid = this.server.pid; if (pid) { let stats = await pidusage(pid); - instanceFactorioCpuTime.labels(String(this.config.get("instance.id"))).set(stats.ctime / 1000); - instanceFactorioMemoryUsage.labels(String(this.config.get("instance.id"))).set(stats.memory); + instanceFactorioCpuTime.labels(String(this.id)).set(stats.ctime / 1000); + instanceFactorioMemoryUsage.labels(String(this.id)).set(stats.memory); } return { results }; @@ -830,7 +835,7 @@ class InstanceConnection extends libLink.Link { libLink.attachAllMessages(this); for (let pluginInfo of slave.pluginInfos) { - libPlugin.attachPluginMessages(this, pluginInfo, null); + libPlugin.attachPluginMessages(this, { info: pluginInfo }); } } @@ -977,7 +982,7 @@ class Slave extends libLink.Link { this.pluginInfos = pluginInfos; for (let pluginInfo of pluginInfos) { - libPlugin.attachPluginMessages(this, pluginInfo, null); + libPlugin.attachPluginMessages(this, { info: pluginInfo }); } this.config = slaveConfig; @@ -1028,7 +1033,7 @@ class Slave extends libLink.Link { } }); - for (let event of ["connect", "drop", "close"]) { + for (let event of ["connect", "drop", "resume", "close"]) { this.connector.on(event, () => { for (let instanceConnection of this.instanceConnections.values()) { libLink.messages.masterConnectionEvent.send(instanceConnection, { event }); diff --git a/plugins/global_chat/webpack.config.js b/plugins/global_chat/webpack.config.js index 042295a4..60b98d7b 100644 --- a/plugins/global_chat/webpack.config.js +++ b/plugins/global_chat/webpack.config.js @@ -10,7 +10,7 @@ module.exports = (env = {}) => merge(common(env), { entry: "./web/index.jsx", output: { filename: "bundle.js", - path: path.resolve(__dirname, "static"), + path: path.resolve(__dirname, "dist", "web"), }, plugins: [ new webpack.container.ModuleFederationPlugin({ diff --git a/plugins/research_sync/webpack.config.js b/plugins/research_sync/webpack.config.js index 4a519c63..484dfafe 100644 --- a/plugins/research_sync/webpack.config.js +++ b/plugins/research_sync/webpack.config.js @@ -10,7 +10,7 @@ module.exports = (env = {}) => merge(common(env), { entry: "./web/index.jsx", output: { filename: "bundle.js", - path: path.resolve(__dirname, "static"), + path: path.resolve(__dirname, "dist", "web"), }, plugins: [ new webpack.container.ModuleFederationPlugin({ diff --git a/plugins/statistics_exporter/instance.js b/plugins/statistics_exporter/instance.js index e7204d7a..db40dcbd 100644 --- a/plugins/statistics_exporter/instance.js +++ b/plugins/statistics_exporter/instance.js @@ -53,7 +53,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin { throw new Error(`Error parsing statistics JSON: ${err.message}, content "${string}"`); } - let instanceId = this.instance.config.get("instance.id"); + let instanceId = this.instance.id; instanceGameTicksTotal.labels(String(instanceId)).set(stats.game_tick); instancePlayerCount.labels(String(instanceId)).set(stats.player_count); diff --git a/plugins/statistics_exporter/webpack.config.js b/plugins/statistics_exporter/webpack.config.js index 3198c71e..0da4c121 100644 --- a/plugins/statistics_exporter/webpack.config.js +++ b/plugins/statistics_exporter/webpack.config.js @@ -10,7 +10,7 @@ module.exports = (env = {}) => merge(common(env), { entry: "./web/index.jsx", output: { filename: "bundle.js", - path: path.resolve(__dirname, "static"), + path: path.resolve(__dirname, "dist", "web"), }, plugins: [ new webpack.container.ModuleFederationPlugin({ diff --git a/plugins/subspace_storage/instance.js b/plugins/subspace_storage/instance.js index 668f0b69..1f2529ea 100644 --- a/plugins/subspace_storage/instance.js +++ b/plugins/subspace_storage/instance.js @@ -39,7 +39,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin { async provideItems(items) { this.info.messages.place.send(this.instance, { items, - instance_id: this.instance.config.get("instance.id"), + instance_id: this.instance.id, }); if (this.instance.config.get("subspace_storage.log_item_transfers")) { @@ -52,7 +52,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin { async requestItems(items) { // Request the items all at once let response = await this.info.messages.remove.send(this.instance, { - instance_id: this.instance.config.get("instance.id"), + instance_id: this.instance.id, items, }); diff --git a/plugins/subspace_storage/webpack.config.js b/plugins/subspace_storage/webpack.config.js index 4a7b4901..cfd034a8 100644 --- a/plugins/subspace_storage/webpack.config.js +++ b/plugins/subspace_storage/webpack.config.js @@ -10,7 +10,7 @@ module.exports = (env = {}) => merge(common(env), { entry: "./web/index.jsx", output: { filename: "bundle.js", - path: path.resolve(__dirname, "static"), + path: path.resolve(__dirname, "dist", "web"), }, plugins: [ new webpack.container.ModuleFederationPlugin({ diff --git a/test/integration/clusterio.js b/test/integration/clusterio.js index 550c8fd7..3ab0ffb4 100644 --- a/test/integration/clusterio.js +++ b/test/integration/clusterio.js @@ -4,6 +4,7 @@ const fs = require("fs-extra"); const path = require("path"); const libLink = require("@clusterio/lib/link"); +const libUsers = require("@clusterio/lib/users"); const { slowTest, get, exec, execCtl, sendRcon, getControl, instancesDir } = require("./index"); @@ -282,16 +283,40 @@ describe("Integration of Clusterio", function() { describe("role edit", function() { it("should modify the given role", async function() { - let args = "--name new --description \"A new role\" --permissions"; + let args = "--name new --description \"A new role\" --set-perms"; await execCtl(`role edit temp ${args}`); let result = await libLink.messages.listRoles.send(getControl()); let newRole = result.list.find(role => role.name === "new"); assert.deepEqual(newRole, { id: 5, name: "new", description: "A new role", permissions: [] }); }); + it("should add permissions with --add-perms", async function() { + let args = "--name new --add-perms core.slave.list core.instance.list"; + await execCtl(`role edit new ${args}`); + let result = await libLink.messages.listRoles.send(getControl()); + let newRole = result.list.find(role => role.name === "new"); + assert.deepEqual(newRole.permissions, ["core.slave.list", "core.instance.list"]); + }); + it("should remove permissions with --remove-perms", async function() { + let args = "--name new --remove-perms core.slave.list"; + await execCtl(`role edit new ${args}`); + let result = await libLink.messages.listRoles.send(getControl()); + let newRole = result.list.find(role => role.name === "new"); + assert.deepEqual(newRole.permissions, ["core.instance.list"]); + }); + it("should grant default permissions with --grant-default", async function() { + await execCtl("role edit new --grant-default"); + let result = await libLink.messages.listRoles.send(getControl()); + let newRole = result.list.find(role => role.name === "new"); + let defaultPermissions = [...libUsers.permissions.values()] + .filter(p => p.grantByDefault) + .map(p => p.name) + ; + assert.deepEqual(new Set(newRole.permissions), new Set(defaultPermissions)); + }); }); describe("role delete", function() { - it("should modify the given role", async function() { + it("should delete the given role", async function() { await execCtl("role delete new"); let result = await libLink.messages.listRoles.send(getControl()); let newRole = result.list.find(role => role.name === "new"); diff --git a/test/integration/link.js b/test/integration/link.js index 731fef97..39848e16 100644 --- a/test/integration/link.js +++ b/test/integration/link.js @@ -34,17 +34,17 @@ describe("Integration of lib/link", function() { it("should reconnect on connection lost", async function() { controlConnector._socket.close(1008, "Test"); - await events.once(controlConnector, "connect"); + await events.once(controlConnector, "resume"); }); it("should reconnect on connection terminated", async function() { controlConnector._socket.terminate(); - await events.once(controlConnector, "connect"); + await events.once(controlConnector, "resume"); }); it("should reconnect on connection heartbeat timeout", async function() { controlConnector.stopHeartbeat(); - await events.once(controlConnector, "connect"); + await events.once(controlConnector, "resume"); }); it("should invalidate on reconnection with bad session token", async function() { diff --git a/test/lib/helpers.js b/test/lib/helpers.js index b9671a93..5b5159f7 100644 --- a/test/lib/helpers.js +++ b/test/lib/helpers.js @@ -23,7 +23,8 @@ describe("lib/helpers", function() { await libHelpers.wait(100); let duration = Number(hrtime() - startNs) / 1e6; - assert(duration > 90 && duration < 110, `duration waited (${duration}ms) 10% of expected (100ms)`); + // On windows the time waited is normally up to 15 ms late with outliers in the 20ms range. + assert(duration > 90 && duration < 130, `duration (${duration}ms) not within (90-130)`); }); }); diff --git a/test/lib/plugin.js b/test/lib/plugin.js index 6d5fc169..6ea1d9ac 100644 --- a/test/lib/plugin.js +++ b/test/lib/plugin.js @@ -43,24 +43,24 @@ describe("lib/plugin", function() { let mockLink = new libLink.Link("source", "target", new mock.MockConnector()); let mockEvent = new libLink.Event({ type: "test:test", links: ["target-source"] }); it("should accept pluginInfo without messages", function() { - libPlugin.attachPluginMessages(mockLink, {}, null); + libPlugin.attachPluginMessages(mockLink, { info: {} }); }); it("should attach handler for the given message", function() { function mockEventEventHandler() { }; libPlugin.attachPluginMessages( - mockLink, { name: "test", messages: { mockEvent }}, { mockEventEventHandler } + mockLink, { mockEventEventHandler, info: { name: "test", messages: { mockEvent }}} ); assert(mockLink._handlers.get("test:test_event"), "handler was not registered"); }); it("should throw if missing handler for the given message", function() { assert.throws( - () => libPlugin.attachPluginMessages(mockLink, { name: "test", messages: { mockEvent }}, {}), + () => libPlugin.attachPluginMessages(mockLink, { info: { name: "test", messages: { mockEvent }}}), new Error("Missing mockEventEventHandler on plugin test for test:test_event on source-target link") ); }); it("should throw if message starts with the wrong prefix", function() { assert.throws( - () => libPlugin.attachPluginMessages(mockLink, { name: "foo", messages: { mockEvent }}, {}), + () => libPlugin.attachPluginMessages(mockLink, { info: { name: "foo", messages: { mockEvent }}}), new Error('Type of mockEvent message must start with "foo:"') ); }); diff --git a/test/lib/plugin_loader.js b/test/lib/plugin_loader.js index c5ce7332..b5098955 100644 --- a/test/lib/plugin_loader.js +++ b/test/lib/plugin_loader.js @@ -4,6 +4,7 @@ const fs = require("fs-extra"); const path = require("path"); const libPluginLoader = require("@clusterio/lib/plugin_loader"); +const { escapeRegExp } = require("@clusterio/lib/helpers"); describe("lib/plugin_loader", function() { @@ -34,7 +35,7 @@ describe("lib/plugin_loader", function() { it("should throw on missing plugin", async function() { await assert.rejects( libPluginLoader.loadPluginInfos(new Map([["missing", missingPlugin]]), []), - new RegExp(`^Error: PluginError: Cannot find module '${missingPlugin}/info'`) + new RegExp(`^Error: PluginError: Cannot find module '${escapeRegExp(missingPlugin)}/info'`) ); }); it("should load test plugin", async function() { diff --git a/test/mock.js b/test/mock.js index 89ada241..6ca83e96 100644 --- a/test/mock.js +++ b/test/mock.js @@ -103,6 +103,7 @@ class MockInstance extends libLink.Link { this.logger = new MockLogger(); this.server = new MockServer(); this.name = "test"; + this.id = 7357; this.mockConfigEntries = new Map([ ["instance.id", 7357], ["factorio.enable_save_patching", true],