mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 15:04:52 +09:00
Review changes for #1142: - base becomes template everywhere (is_template, template_id, --template, --is-template, --templates, InstanceDetails.isTemplate). - Object fields inherit per property. inherited_fields lists the overridden properties for object fields and the template object is merged with them. - Linking a template also inherits fields that hold the default value. The server name stays per instance. - Ports are never inherited. - InstanceDetails carries templateId. Web UI: inherited values are locked until unlocked, overridden values can be inherited again, templates are listed separately, the create modal can create templates and instances from templates, and the instance page has "Create template" which can link the source. Part of #200 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
643 lines
22 KiB
JavaScript
643 lines
22 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import * as lib from "@clusterio/lib";
|
|
|
|
import { Controller, InstanceRecord, InstanceManager } from "@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.NotFoundError);
|
|
});
|
|
});
|
|
|
|
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.ConflictError
|
|
);
|
|
});
|
|
|
|
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.UnavailableError
|
|
);
|
|
});
|
|
|
|
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.UnavailableError
|
|
);
|
|
|
|
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);
|
|
});
|
|
|
|
it("should leave instance unassigned if the new host fails the assign", async function () {
|
|
let unassignRequest;
|
|
controller.wsServer.hostConnections.set(5, {
|
|
send: async (request) => {
|
|
unassignRequest = request;
|
|
},
|
|
connector: { closing: false },
|
|
});
|
|
let failAssign = true;
|
|
let assignRequest;
|
|
controller.wsServer.hostConnections.set(6, {
|
|
send: async (request) => {
|
|
if (failAssign) {
|
|
throw new lib.InternalServerError("EACCES: permission denied");
|
|
}
|
|
assignRequest = request;
|
|
},
|
|
connector: { closing: false },
|
|
});
|
|
|
|
instance.config.set("instance.assigned_host", 5);
|
|
instance.status = "stopped";
|
|
await assert.rejects(
|
|
() => instances.assignInstance(1, 6),
|
|
{ message: "EACCES: permission denied" }
|
|
);
|
|
|
|
assert(unassignRequest instanceof lib.InstanceUnassignInternalRequest);
|
|
const stored = instances.records.get(1);
|
|
assert.equal(stored.config.get("instance.assigned_host"), null);
|
|
assert.equal(stored.status, "unassigned");
|
|
|
|
failAssign = false;
|
|
await instances.assignInstance(1, 6);
|
|
assert.equal(instance.config.get("instance.assigned_host"), 6);
|
|
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);
|
|
});
|
|
});
|
|
|
|
describe("inheritance", function () {
|
|
/** @type {InstanceRecord} */
|
|
let template;
|
|
/** @type {InstanceRecord} */
|
|
let child;
|
|
let updated;
|
|
|
|
async function createInheritor(id, templateId, fields = {}) {
|
|
const config = new lib.InstanceConfig("controller", {
|
|
"instance.id": id, "instance.name": `Instance ${id}`, "instance.template_id": templateId, ...fields,
|
|
});
|
|
instances.applyTemplate(
|
|
config, ["instance.id", "instance.name", "instance.template_id", ...Object.keys(fields)]
|
|
);
|
|
return await instances.createInstance(config);
|
|
}
|
|
|
|
beforeEach(async function () {
|
|
template = await instances.createInstance(new lib.InstanceConfig("controller", {
|
|
"instance.id": 10,
|
|
"instance.name": "Template",
|
|
"instance.is_template": true,
|
|
"factorio.version": "2.0",
|
|
"factorio.game_port": 34190,
|
|
"factorio.settings": { name: "Template server", max_players: 5 },
|
|
}));
|
|
child = await createInheritor(11, 10, { "factorio.game_port": 34197 });
|
|
updated = [];
|
|
controller.instanceConfigUpdated = async (inst) => { updated.push(inst.id); };
|
|
});
|
|
|
|
describe(".applyTemplate()", function () {
|
|
it("should copy template values and mark them inherited", function () {
|
|
assert.equal(child.config.get("factorio.version"), "2.0");
|
|
assert.equal(child.config.isInherited("factorio.version"), true);
|
|
assert.equal(child.config.get("factorio.game_port"), 34197);
|
|
assert.equal(child.config.isInherited("factorio.game_port"), false);
|
|
assert.equal(child.config.isInherited("instance.name"), false);
|
|
});
|
|
it("should never inherit ports", function () {
|
|
const config = new lib.InstanceConfig("controller", { "instance.id": 12, "instance.template_id": 10 });
|
|
instances.applyTemplate(config, ["instance.id", "instance.template_id"]);
|
|
assert.equal(config.get("factorio.game_port"), null);
|
|
assert.equal(config.isInherited("factorio.game_port"), false);
|
|
});
|
|
it("should inherit factorio.settings per property with its own server name", function () {
|
|
const settings = child.config.get("factorio.settings");
|
|
assert.equal(settings.max_players, 5);
|
|
assert.equal(settings.name, "TestController - Instance 11");
|
|
assert.equal(child.config.isInherited("factorio.settings", "max_players"), true);
|
|
assert.equal(child.config.isInherited("factorio.settings", "name"), false);
|
|
});
|
|
it("should keep properties given by the creator as overrides", async function () {
|
|
const other = await createInheritor(12, 10, { "factorio.settings": { max_players: 8 } });
|
|
assert.equal(other.config.get("factorio.settings").max_players, 8);
|
|
assert.equal(other.config.isInherited("factorio.settings", "max_players"), false);
|
|
assert.equal(other.config.isInherited("factorio.settings", "auto_pause"), true);
|
|
});
|
|
});
|
|
|
|
describe("template changes", function () {
|
|
it("should propagate to inherited fields and send the config to the host", function () {
|
|
template.config.set("factorio.version", "2.1");
|
|
assert.equal(child.config.get("factorio.version"), "2.1");
|
|
assert.equal(child.config.isInherited("factorio.version"), true);
|
|
assert.deepEqual(updated, [11]);
|
|
});
|
|
it("should mark the inheritor as requiring restart", function () {
|
|
child.config.restartRequired = false;
|
|
template.config.set("factorio.version", "2.1");
|
|
assert.equal(child.config.restartRequired, true);
|
|
});
|
|
it("should not touch fields the inheritor has set", function () {
|
|
child.config.set("factorio.version", "2.1");
|
|
template.config.set("factorio.version", "2.2");
|
|
assert.equal(child.config.get("factorio.version"), "2.1");
|
|
});
|
|
it("should merge object properties and keep overrides", function () {
|
|
template.config.setProp("factorio.settings", "max_players", 10);
|
|
template.config.setProp("factorio.settings", "name", "Renamed");
|
|
template.config.setProp("factorio.settings", "auto_pause", true);
|
|
const settings = child.config.get("factorio.settings");
|
|
assert.equal(settings.max_players, 10);
|
|
assert.equal(settings.auto_pause, true);
|
|
assert.equal(settings.name, "TestController - Instance 11");
|
|
});
|
|
it("should propagate through several levels", async function () {
|
|
child.config.set("instance.is_template", true);
|
|
const grandchild = await createInheritor(12, 11);
|
|
template.config.setProp("factorio.settings", "max_players", 10);
|
|
assert.equal(grandchild.config.get("factorio.settings").max_players, 10);
|
|
assert.deepEqual(updated.sort(), [11, 12]);
|
|
});
|
|
});
|
|
|
|
describe("inheritor changes", function () {
|
|
it("should stop inheriting a field that is changed", function () {
|
|
child.config.set("factorio.version", "2.1");
|
|
assert.equal(child.config.isInherited("factorio.version"), false);
|
|
});
|
|
it("should only override the properties changed in an object", function () {
|
|
child.config.setProp("factorio.settings", "max_players", 3);
|
|
assert.equal(child.config.isInherited("factorio.settings", "max_players"), false);
|
|
assert.equal(child.config.isInherited("factorio.settings", "auto_pause"), true);
|
|
});
|
|
it("should restore the template value of a removed property", function () {
|
|
child.config.setProp("factorio.settings", "max_players", 3);
|
|
child.config.setProp("factorio.settings", "max_players");
|
|
assert.equal(child.config.get("factorio.settings").max_players, 5);
|
|
assert.equal(child.config.isInherited("factorio.settings", "max_players"), true);
|
|
});
|
|
it("should stop inheriting with overrideField() even if the value is unchanged", function () {
|
|
instances.overrideField(child, "factorio.version");
|
|
instances.overrideField(child, "factorio.settings", ["auto_pause"]);
|
|
assert.equal(child.config.isInherited("factorio.version"), false);
|
|
assert.equal(child.config.isInherited("factorio.settings", "auto_pause"), false);
|
|
});
|
|
});
|
|
|
|
describe(".inheritField()", function () {
|
|
it("should restore the template value", function () {
|
|
child.config.set("factorio.version", "2.1");
|
|
instances.inheritField(11, "factorio.version");
|
|
assert.equal(child.config.get("factorio.version"), "2.0");
|
|
assert.equal(child.config.isInherited("factorio.version"), true);
|
|
});
|
|
it("should restore a single property", function () {
|
|
child.config.setProp("factorio.settings", "max_players", 3);
|
|
instances.inheritField(11, "factorio.settings", "max_players");
|
|
assert.equal(child.config.get("factorio.settings").max_players, 5);
|
|
assert.equal(child.config.get("factorio.settings").name, "TestController - Instance 11");
|
|
});
|
|
it("should restore all properties", function () {
|
|
instances.inheritField(11, "factorio.settings");
|
|
assert.equal(child.config.get("factorio.settings").name, "Template server");
|
|
});
|
|
it("should throw if the instance has no template", function () {
|
|
assert.throws(
|
|
() => instances.inheritField(1, "factorio.version"),
|
|
new lib.ValidationError("Instance does not have a template"),
|
|
);
|
|
});
|
|
it("should throw for fields that cannot be inherited", function () {
|
|
assert.throws(
|
|
() => instances.inheritField(11, "instance.name"),
|
|
new lib.ValidationError("Field instance.name cannot be inherited"),
|
|
);
|
|
assert.throws(
|
|
() => instances.inheritField(11, "factorio.rcon_port"),
|
|
new lib.ValidationError("Field factorio.rcon_port cannot be inherited"),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("template_id changes", function () {
|
|
it("should inherit fields equal to the template or the default", function () {
|
|
instance.config.set("factorio.version", "2.0");
|
|
instance.config.set("factorio.verbose_logging", true);
|
|
template.config.set("instance.auto_start", true);
|
|
instance.config.set("instance.template_id", 10);
|
|
assert.equal(instance.config.isInherited("factorio.version"), true);
|
|
assert.equal(instance.config.isInherited("instance.auto_start"), true);
|
|
assert.equal(instance.config.get("instance.auto_start"), true);
|
|
assert.equal(instance.config.isInherited("factorio.verbose_logging"), false);
|
|
});
|
|
it("should inherit object properties equal to the template or the default", function () {
|
|
instance.config.setProp("factorio.settings", "max_players", 0);
|
|
instance.config.setProp("factorio.settings", "game_password", "secret");
|
|
instance.config.set("instance.template_id", 10);
|
|
const settings = instance.config.get("factorio.settings");
|
|
assert.equal(settings.max_players, 5);
|
|
assert.equal(settings.game_password, "secret");
|
|
assert.equal(instance.config.isInherited("factorio.settings", "game_password"), false);
|
|
assert.equal(instance.config.isInherited("factorio.settings", "max_players"), true);
|
|
});
|
|
it("should keep the server name of the instance", function () {
|
|
instance.config.setProp("factorio.settings", "name", "Template server");
|
|
instance.config.set("instance.template_id", 10);
|
|
assert.equal(instance.config.isInherited("factorio.settings", "name"), false);
|
|
});
|
|
it("should clear inherited fields when the template is removed", function () {
|
|
child.config.set("instance.template_id", null);
|
|
assert.deepEqual(child.config.get("instance.inherited_fields"), {});
|
|
assert.equal(child.config.get("factorio.version"), "2.0");
|
|
});
|
|
});
|
|
|
|
describe(".checkInheritance()", function () {
|
|
it("should accept a valid template", function () {
|
|
instances.checkInheritance(instance.config, false, 10);
|
|
});
|
|
it("should reject a missing template", function () {
|
|
assert.throws(
|
|
() => instances.checkInheritance(instance.config, false, 99),
|
|
new lib.NotFoundError("Instance with ID 99 does not exist"),
|
|
);
|
|
});
|
|
it("should reject an instance that is not a template", function () {
|
|
assert.throws(
|
|
() => instances.checkInheritance(instance.config, false, 11),
|
|
new lib.ValidationError("Instance Instance 11 is not a template"),
|
|
);
|
|
});
|
|
it("should reject cycles", function () {
|
|
child.config.set("instance.is_template", true);
|
|
assert.throws(
|
|
() => instances.checkInheritance(template.config, true, 11),
|
|
new lib.ValidationError("Templates cannot inherit from themselves"),
|
|
);
|
|
});
|
|
it("should reject making an assigned instance a template", function () {
|
|
instance.config.set("instance.assigned_host", 5);
|
|
assert.throws(
|
|
() => instances.checkInheritance(instance.config, true, null),
|
|
new lib.ConflictError("Instance must be unassigned before it can be made a template"),
|
|
);
|
|
});
|
|
it("should reject unmarking a template that has inheritors", function () {
|
|
assert.throws(
|
|
() => instances.checkInheritance(template.config, false, null),
|
|
new lib.ConflictError("Instance is the template of 1 other instance(s)"),
|
|
);
|
|
});
|
|
});
|
|
|
|
it("should not assign a template to a host", async function () {
|
|
controller.wsServer.hostConnections.set(5, { send: async () => {}, connector: { closing: false } });
|
|
await assert.rejects(
|
|
instances.assignInstance(10, 5),
|
|
new lib.ValidationError("Templates cannot be assigned to a host"),
|
|
);
|
|
});
|
|
|
|
it("should not delete a template that has inheritors", async function () {
|
|
await assert.rejects(
|
|
instances.deleteInstance(10),
|
|
new lib.ConflictError("Instance is the template of 1 other instance(s)"),
|
|
);
|
|
});
|
|
|
|
it("should report templates in instance details", function () {
|
|
assert.equal(template.toInstanceDetails().isTemplate, true);
|
|
assert.equal(child.toInstanceDetails().isTemplate, false);
|
|
});
|
|
});
|
|
});
|