Files
factorio-clusterio/test/controller/InstanceManager.test.js
Cooldude2606andHornwitser 2235294c59 Refactor user and instance manager (#837)
* Refactor UserManager to use Datastore

* Rename lib.User to UserDetails also adds to/fromUserDetails

* Fix existing tests

* Add user class tests

* Add tests for finaliseUsers

* Move HostDetails to own file with tests

* Move InstanceDetails to its own file with tests

* Rename HostInfo and add tests

* Rename InstanceInfo and add tests

- Also changes the names of the host properties related to instances
- Also removes the need to specify the location of InstanceInfo

* Make json round trip test validate schema

* Add InstanceManager

* Fix lint errors

* Extra tests

* Add final test coverage

* Add one last test to InstanceManager

* Response to comments

* Use generator rather than return anonumous generator

Co-authored-by: Hornwitser <github@hornwitser.no>

* Fix lint issue with generator

---------

Co-authored-by: Hornwitser <github@hornwitser.no>
2026-03-01 23:04:37 +00:00

359 lines
10 KiB
JavaScript

"use strict";
const assert = require("assert").strict;
const lib = require("@clusterio/lib");
const { Controller, InstanceRecord, InstanceManager } = require("@clusterio/controller");
describe("controller/InstanceManager", function () {
/** @type {Controller} */
let controller;
/** @type {InstanceManager} */
let instances;
/** @type {InstanceRecord} */
let instance;
beforeEach(function () {
const controllerConfig = new lib.ControllerConfig("controller", {
"controller.name": "TestController",
});
const instanceConfig = new lib.InstanceConfig("controller", {
"instance.id": 1,
"instance.name": "Test",
});
const datastoreInstances = new lib.SubscribableDatastore();
datastoreInstances.set(new InstanceRecord(instanceConfig, "unassigned"));
controller = new Controller(lib.logger, [], controllerConfig,
undefined, undefined, undefined, undefined, datastoreInstances);
instances = controller.instances;
instance = instances.getMutable(1);
});
describe("constructor()", function () {
it("should call _notifyConfigFieldChanged when config changes", async function () {
let called = false;
instances._notifyConfigFieldChanged = async (
inst, field, curr, prev
) => {
called = true;
assert.equal(inst.id, 1);
assert.equal(field, "instance.name");
assert.equal(prev, "Test");
assert.equal(curr, "Updated");
};
instance.config.set("instance.name", "Updated");
assert.equal(called, true);
});
it("should update datastore when config changes", function () {
let setCalled = false;
const originalSet = instances.records.set.bind(instances.records);
instances.records.set = function (value) {
setCalled = true;
return originalSet(value);
};
instance.config.set("instance.name", "Changed");
assert.equal(setCalled, true);
});
});
describe(".has() / .get() / .getMutable()", function () {
it("should return instance when exists", async function () {
assert.equal(instances.has(instance.id), true);
assert.equal(instances.get(instance.id), instance);
assert.equal(instances.getMutable(instance.id), instance);
});
it("should return undefined when missing", function () {
assert.equal(instances.has(999), false);
assert.equal(instances.get(999), undefined);
});
});
describe(".values() / .valuesMutable()", function () {
it("should iterate all instances", async function () {
const config1 = new lib.InstanceConfig("controller", {
"instance.id": 2,
"instance.name": "A",
});
const config2 = new lib.InstanceConfig("controller", {
"instance.id": 3,
"instance.name": "B",
});
const i1 = await instances.createInstance(config1);
const i2 = await instances.createInstance(config2);
assert.deepEqual(Array.from(instances.values()), [instance, i1, i2]);
assert.deepEqual(Array.from(instances.valuesMutable()), [instance, i1, i2]);
});
});
describe(".getForRequest()", function () {
it("should return instance when exists", async function () {
assert.equal(instances.getForRequest(1), instance);
});
it("should throw when missing", function () {
assert.throws(() => instances.getForRequest(999), lib.RequestError);
});
});
describe(".createInstance()", function () {
it("should create new instance with id 2", async function () {
let notified = false;
instances._notifyStatusChanged = async () => { notified = true; };
const config = new lib.InstanceConfig("controller", {
"instance.id": 2,
"instance.name": "Second",
});
const created = await instances.createInstance(config);
assert.equal(created.id, 2);
assert(instances.get(2), created);
assert.equal(notified, true);
});
it("should call notify on config change", async function () {
let notified = false;
instances._notifyConfigFieldChanged = async () => { notified = true; };
const config = new lib.InstanceConfig("controller", {
"instance.id": 2,
"instance.name": "Second",
});
const created = await instances.createInstance(config);
created.config.set("instance.name", "Updated");
assert.equal(created.id, 2);
assert.equal(instances.get(2), created);
assert.equal(notified, true);
});
it("should throw if id already exists", async function () {
const config = new lib.InstanceConfig("controller", {
"instance.id": 1,
"instance.name": "Duplicate",
});
await assert.rejects(
() => instances.createInstance(config),
lib.RequestError
);
});
it("should suppress changes when flag set", async function () {
let notified = false;
instances._notifyStatusChanged = async () => { notified = true; };
const config = new lib.InstanceConfig("controller", {
"instance.id": 2,
"instance.name": "Suppressed",
});
const created = await instances.createInstance(config, true);
assert.equal(instances.get(2), undefined);
assert.equal(notified, false);
});
});
describe(".assignInstance()", function () {
it("should throw if host not connected", async function () {
await assert.rejects(
() => instances.assignInstance(1, 5),
lib.RequestError
);
});
it("should do nothing if the host would not change", async function () {
let assignRequest;
controller.wsServer.hostConnections.set(5, {
send: async (request) => {
assignRequest = request;
},
connector: { closing: false },
});
instance.config.set("instance.assigned_host", 5);
await instances.assignInstance(1, 5);
assert.equal(assignRequest, undefined);
});
it("should not unassign if new host is not connected", async function () {
let unassignRequest;
controller.wsServer.hostConnections.set(5, {
send: async (request) => {
unassignRequest = request;
},
connector: { closing: false },
});
instance.config.set("instance.assigned_host", 5);
await assert.rejects(
() => instances.assignInstance(1, 6),
lib.RequestError
);
assert.equal(unassignRequest, undefined);
});
it("should assign to connected host", async function () {
let assignRequest;
controller.wsServer.hostConnections.set(5, {
send: async (request) => {
assignRequest = request;
},
connector: { closing: false },
});
await instances.assignInstance(1, 5);
assert.equal(instance.config.get("instance.assigned_host"), 5);
assert(assignRequest instanceof lib.InstanceAssignInternalRequest);
assert.equal(assignRequest.instanceId, 1);
});
it("should not fail when host has id of 0", async function () {
let lastEvent;
controller.wsServer.hostConnections.set(0, {
send: async (request) => {
lastEvent = request;
},
connector: { closing: false },
});
await instances.assignInstance(1, 0);
assert.equal(instance.config.get("instance.assigned_host"), 0);
assert(lastEvent instanceof lib.InstanceAssignInternalRequest);
assert.equal(lastEvent.instanceId, 1);
await instances.assignInstance(1, null);
assert.equal(instance.config.get("instance.assigned_host"), null);
assert(lastEvent instanceof lib.InstanceUnassignInternalRequest);
assert.equal(lastEvent.instanceId, 1);
});
it("should unassign old host if connected", async function () {
let unassignRequest;
controller.wsServer.hostConnections.set(5, {
send: async (request) => {
unassignRequest = request;
},
connector: { closing: false },
});
let assignRequest;
controller.wsServer.hostConnections.set(6, {
send: async (request) => {
assignRequest = request;
},
connector: { closing: false },
});
instance.config.set("instance.assigned_host", 5);
await instances.assignInstance(1, 6);
assert(unassignRequest instanceof lib.InstanceUnassignInternalRequest);
assert.equal(unassignRequest.instanceId, 1);
assert(assignRequest instanceof lib.InstanceAssignInternalRequest);
assert.equal(assignRequest.instanceId, 1);
});
it("should not unassign if old host closing", async function () {
let unassignRequest;
controller.wsServer.hostConnections.set(5, {
send: async (request) => {
unassignRequest = request;
},
connector: { closing: true },
});
let assignRequest;
controller.wsServer.hostConnections.set(6, {
send: async (request) => {
assignRequest = request;
},
connector: { closing: false },
});
instance.config.set("instance.assigned_host", 5);
await instances.assignInstance(1, 6);
assert.equal(unassignRequest, undefined);
assert(assignRequest instanceof lib.InstanceAssignInternalRequest);
assert.equal(assignRequest.instanceId, 1);
});
});
describe(".unassignInstance()", function () {
it("should unassign an instance", async function () {
controller.wsServer.hostConnections.set(5, {
send: async () => {},
connector: { closing: true },
});
await instances.assignInstance(instance.id, 5);
await instances.unassignInstance(instance.id);
assert.equal(instance.config.get("instance.assigned_host"), null);
assert.equal(instance.status, "unassigned");
});
it("should do nothing if the host would not change", async function () {
instance.config.set("instance.assigned_host", null);
let fieldChanged;
instance.config.on("fieldChanged", name => {
fieldChanged = name;
});
await instances.unassignInstance(1);
assert.equal(fieldChanged, undefined);
});
});
describe(".deleteInstance()", function () {
it("should delete and notify", async function () {
let notified = false;
instances._notifyStatusChanged = async () => { notified = true; };
await instances.deleteInstance(1);
assert.equal(instances.get(1), undefined);
assert.equal(notified, true);
});
it("should send delete to host if assigned", async function () {
let sentRequest;
controller.sendTo = async (_target, request) => {
sentRequest = request;
};
controller.wsServer.hostConnections.set(5, {
send: async () => {},
connector: { closing: false },
});
await instances.assignInstance(1, 5);
await instances.deleteInstance(1);
assert(sentRequest instanceof lib.InstanceDeleteInternalRequest);
assert.equal(sentRequest.instanceId, 1);
});
});
});