import assert from "node:assert/strict"; import events from "node:events"; import * as lib from "@clusterio/lib"; import { ControlConnection, ControllerHooks } from "@clusterio/controller"; import { MockConnector, MockController, MockLogger } from "../mock.js"; const addr = lib.Address.fromShorthand; describe("controller/src/ControlConnection", function() { describe(".handleControllerRestartRequest()", function() { let mockController; beforeEach(function() { mockController = { canRestart: true, shouldRestart: false, stopped: false, async checkRestartDowngrade() { return null; }, stop() { this.stopped = true; }, }; }); async function restart() { await ControlConnection.prototype.handleControllerRestartRequest.call({ _controller: mockController, }); } it("restarts when the installed version is not older", async function() { await restart(); assert.equal(mockController.shouldRestart, true); assert.equal(mockController.stopped, true); }); it("rejects a downgrade without stopping the controller", async function() { mockController.checkRestartDowngrade = async () => ({ installedVersion: "1.0.0", runningVersion: "2.0.0", }); await assert.rejects(restart, /Stop the controller before starting the older version manually/); assert.equal(mockController.shouldRestart, false); assert.equal(mockController.stopped, false); }); }); describe("close cleanup", function() { let connector; let mockController; let connection; beforeEach(function() { connector = new MockConnector(addr("controller"), addr({ controlId: 1 })); connector._socket = {}; const transports = new Set(); mockController = { hooks: new ControllerHooks(new MockLogger()), router: null, _registeredRequests: new Map(), _fallbackedRequests: new Map(), _registeredEvents: new Map(), _snoopedEvents: new Map(), subscriptions: { unsubscribeLink() {} }, clusterLogger: { transports, add(transport) { transports.add(transport); }, remove(transport) { transports.delete(transport); }, }, debugEvents: new events.EventEmitter(), sendRequestToHostByInstanceId() {}, }; connection = new ControlConnection({ version: "test" }, connector, mockController, {}, 1); }); it("removes the log transport from clusterLogger", async function() { await connection.handleLogSetSubscriptionsRequest( new lib.LogSetSubscriptionsRequest(true, false, [], []) ); assert.equal(mockController.clusterLogger.transports.size, 1); connector.emit("close"); assert.equal(mockController.clusterLogger.transports.size, 0); assert.equal(connection.logTransport, null); }); it("removes the debug dump listener after repeated requests", async function() { await connection.handleDebugDumpWsRequest(new lib.DebugDumpWsRequest()); await connection.handleDebugDumpWsRequest(new lib.DebugDumpWsRequest()); assert.equal(mockController.debugEvents.listenerCount("message"), 1); assert.equal(connector._socket.clusterio_ignore_dump, true); connector.emit("close"); assert.equal(mockController.debugEvents.listenerCount("message"), 0); }); }); describe(".handleUserDeleteRequest()", function() { let mockController; let nextId; beforeEach(function() { mockController = new MockController(); mockController.sendTo = () => {}; mockController.wsServer.controlConnections = new Map(); nextId = 1; }); function connect(name) { const connection = { _controller: mockController, user: mockController.users.getByName(name), connector: { terminated: false, terminate() { this.terminated = true; }, }, }; mockController.wsServer.controlConnections.set(nextId, connection); nextId += 1; return connection; } async function deleteUser(connection, name) { await ControlConnection.prototype.handleUserDeleteRequest.call( connection, new lib.UserDeleteRequest(name) ); } it("terminates connections of the deleted user", async function() { const admin = connect("test"); const player1 = connect("player"); const player2 = connect("player"); await deleteUser(admin, "player"); assert.equal(mockController.users.getByName("player"), undefined); assert.equal(player1.connector.terminated, true); assert.equal(player2.connector.terminated, true); assert.equal(admin.connector.terminated, false); }); it("terminates its own connection when deleting itself", async function() { const player = connect("player"); await deleteUser(player, "player"); assert.equal(player.connector.terminated, true); }); }); });