mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 23:04:52 +09:00
Migrate the usage of CommonJS specific code and imports to ESM. Roughly summarised this: - Changes all require() calls to import statement or expressions. - Changes usage of module.exports to export statements. - Replaces require.resolve with import.meta.resolve. - Replaces conditional imports with conditional subpath imports. - Replaces module === require.main with import.meta.main. - Adds the full filename with extention to all relative imports. - Changes all usage of __dirname and __filename to their import.meta equivalents. - Sets "type": "module" in package.json files. - Removes usage of "use strict". - Adds .ts an an alias of .js for the webpack build. Due to differences in how require and import resolves files the plugin entrypoints will have to include the filename and extension after this migration in order to work.
468 lines
17 KiB
JavaScript
468 lines
17 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import * as lib from "@clusterio/lib";
|
|
|
|
import { Controller, ControlConnection } from "@clusterio/controller";
|
|
import { testMatrix, testRoundTripJsonSerialisable } from "../common.js";
|
|
|
|
describe("messages/controller", function() {
|
|
/** @type {Controller} */
|
|
let controller;
|
|
/** @type {ControlConnection} */
|
|
let controlConnection;
|
|
/** @type {lib.InstanceConfig} */
|
|
let instanceConfig;
|
|
|
|
beforeEach(function() {
|
|
const controllerConfig = new lib.ControllerConfig("controller");
|
|
const connection = new lib.VirtualConnector(
|
|
lib.Address.fromShorthand("controller"),
|
|
lib.Address.fromShorthand({ controlId: 1 }),
|
|
);
|
|
controller = new Controller(lib.logger, [], controllerConfig);
|
|
instanceConfig = new lib.InstanceConfig("controller");
|
|
controller.instances.createInstance(instanceConfig);
|
|
const user = controller.users.getOrCreateUser("test");
|
|
controlConnection = new ControlConnection({ version: "2.0.0" }, connection, controller, user, 1);
|
|
});
|
|
|
|
describe("ControllerUpdateRequest", function() {
|
|
it("runs", async function() {
|
|
controller.config.set("controller.allow_remote_updates", true);
|
|
await controlConnection.handleControllerUpdateRequest(new lib.ControllerUpdateRequest());
|
|
});
|
|
it("rejects if updates are disabled", async function() {
|
|
controller.config.set("controller.allow_remote_updates", false);
|
|
await assert.rejects(
|
|
controlConnection.handleControllerUpdateRequest(new lib.ControllerUpdateRequest()),
|
|
/Remote updates are disabled on this machine/
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("ControllerConfigGetRequest", function() {
|
|
it("runs", async function() {
|
|
const config = await controlConnection.handleControllerConfigGetRequest(
|
|
new lib.ControllerConfigGetRequest()
|
|
);
|
|
assert.deepEqual(config, controller.config.toRemote("control"));
|
|
});
|
|
});
|
|
|
|
describe("ControllerConfigSetFieldRequest", function() {
|
|
it("is round trip json serialisable", function() {
|
|
testRoundTripJsonSerialisable(lib.ControllerConfigSetFieldRequest, testMatrix(
|
|
["controller.name", "invalid"], // field
|
|
["Foo Bar", "true", "5", "{}"], // value
|
|
));
|
|
});
|
|
it("runs", async function() {
|
|
await controlConnection.handleControllerConfigSetFieldRequest(
|
|
new lib.ControllerConfigSetFieldRequest("controller.name", "Foo Bar")
|
|
);
|
|
assert.equal(controller.config.get("controller.name"), "Foo Bar");
|
|
});
|
|
it("rejects if the field does not exist", async function() {
|
|
await assert.rejects(
|
|
controlConnection.handleControllerConfigSetFieldRequest(
|
|
new lib.ControllerConfigSetFieldRequest("invalid", "Foo Bar")
|
|
),
|
|
/No field named 'invalid'/
|
|
);
|
|
});
|
|
it("rejects if the field is inaccessible", async function() {
|
|
await assert.rejects(
|
|
controlConnection.handleControllerConfigSetFieldRequest(
|
|
new lib.ControllerConfigSetFieldRequest("controller.version", "2.0.0")
|
|
),
|
|
/Field 'controller.version' is not accessible from control/
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("ControllerConfigSetPropRequest", function() {
|
|
it("is round trip json serialisable", function() {
|
|
testRoundTripJsonSerialisable(lib.ControllerConfigSetPropRequest, testMatrix(
|
|
["controller.name", "invalid"], // field
|
|
["foo", "bar"], // prop
|
|
["Foo Bar", true, 5, {}], // value
|
|
));
|
|
});
|
|
it("runs", async function() {
|
|
this.skip(); // No object fields on controller
|
|
});
|
|
it("rejects if the field does not exist", async function() {
|
|
await assert.rejects(
|
|
controlConnection.handleControllerConfigSetPropRequest(
|
|
new lib.ControllerConfigSetPropRequest("invalid", "foo", "bar")
|
|
),
|
|
/No field named 'invalid'/
|
|
);
|
|
});
|
|
it("rejects if the field is not an object", async function() {
|
|
await assert.rejects(
|
|
controlConnection.handleControllerConfigSetPropRequest(
|
|
new lib.ControllerConfigSetPropRequest("controller.name", "foo", "bar")
|
|
),
|
|
/Cannot set property on non-object field 'controller\.name'/
|
|
);
|
|
});
|
|
it("rejects if the field is inaccessible", async function() {
|
|
await assert.rejects(
|
|
controlConnection.handleControllerConfigSetPropRequest(
|
|
new lib.ControllerConfigSetPropRequest("controller.version", "foo", "bar")
|
|
),
|
|
/Field 'controller\.version' is not accessible from control/
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("ControllerConfigSetRequest", function() {
|
|
it("is round trip json serialisable", function() {
|
|
testRoundTripJsonSerialisable(lib.ControllerConfigSetRequest, testMatrix(
|
|
[ // fields
|
|
{ "foo": "bar", "baz": { "cat": "dog" } },
|
|
{ "foo": "5", "baz": { "cat": "5" } },
|
|
{ "foo": "true", "baz": { "cat": "true" } },
|
|
{ "foo": "{}", "baz": { "cat": "{}" } },
|
|
],
|
|
));
|
|
});
|
|
it("runs", async function() {
|
|
await controlConnection.handleControllerConfigSetRequest(new lib.ControllerConfigSetRequest({
|
|
"controller.name": "Foo Bar",
|
|
"controller.mods_directory": "Bar Baz",
|
|
}));
|
|
assert.equal(controller.config.get("controller.name"), "Foo Bar");
|
|
assert.equal(controller.config.get("controller.mods_directory"), "Bar Baz");
|
|
});
|
|
it("rejects if the field does not exist", async function() {
|
|
await assert.rejects(
|
|
controlConnection.handleControllerConfigSetRequest(new lib.ControllerConfigSetRequest({
|
|
"controller.name": "Foo Bar",
|
|
"invalid": "Bar Baz",
|
|
})),
|
|
/No field named 'invalid'/
|
|
);
|
|
assert.equal(controller.config.get("controller.name"), "Your Cluster");
|
|
});
|
|
it("rejects if the field is not an object", async function() {
|
|
const prevDir = controller.config.get("controller.mods_directory");
|
|
await assert.rejects(
|
|
controlConnection.handleControllerConfigSetRequest(new lib.ControllerConfigSetRequest({
|
|
"controller.name": "Foo Bar",
|
|
"controller.mods_directory": {
|
|
"foo": "bar",
|
|
},
|
|
})),
|
|
/Cannot set property on non-object field 'controller\.mods_directory'/
|
|
);
|
|
assert.equal(controller.config.get("controller.name"), "Your Cluster");
|
|
assert.equal(controller.config.get("controller.mods_directory"), prevDir);
|
|
});
|
|
it("rejects if the field is inaccessible", async function() {
|
|
const prevVersion = controller.config.get("controller.version");
|
|
await assert.rejects(
|
|
controlConnection.handleControllerConfigSetRequest(new lib.ControllerConfigSetRequest({
|
|
"controller.name": "Foo Bar",
|
|
"controller.version": "2.0.0",
|
|
})),
|
|
/Field 'controller\.version' is not accessible from control/
|
|
);
|
|
assert.equal(controller.config.get("controller.name"), "Your Cluster");
|
|
assert.equal(controller.config.get("controller.version"), prevVersion);
|
|
});
|
|
});
|
|
|
|
describe("InstanceConfigGetRequest", function() {
|
|
it("runs", async function() {
|
|
const config = await controlConnection.handleInstanceConfigGetRequest(
|
|
new lib.InstanceConfigGetRequest(instanceConfig.get("instance.id"))
|
|
);
|
|
assert.deepEqual(config, instanceConfig.toRemote("control"));
|
|
});
|
|
});
|
|
|
|
describe("InstanceConfigSetFieldRequest", function() {
|
|
it("is round trip json serialisable", function() {
|
|
testRoundTripJsonSerialisable(lib.InstanceConfigSetFieldRequest, testMatrix(
|
|
[1], // instance id
|
|
["controller.name", "invalid"], // field
|
|
["Foo Bar", "true", "5", "{}"], // value
|
|
));
|
|
});
|
|
it("runs", async function() {
|
|
await controlConnection.handleInstanceConfigSetFieldRequest(
|
|
new lib.InstanceConfigSetFieldRequest(instanceConfig.get("instance.id"), "instance.name", "Foo Bar")
|
|
);
|
|
assert.equal(instanceConfig.get("instance.name"), "Foo Bar");
|
|
});
|
|
it("rejects if the field does not exist", async function() {
|
|
await assert.rejects(
|
|
controlConnection.handleInstanceConfigSetFieldRequest(
|
|
new lib.InstanceConfigSetFieldRequest(instanceConfig.get("instance.id"), "invalid", "Foo Bar")
|
|
),
|
|
/No field named 'invalid'/
|
|
);
|
|
});
|
|
it("rejects if the field is inaccessible", async function() {
|
|
await assert.rejects(
|
|
controlConnection.handleInstanceConfigSetFieldRequest(
|
|
new lib.InstanceConfigSetFieldRequest(
|
|
instanceConfig.get("instance.id"), "factorio.host_assigned_game_port", "3000"
|
|
)
|
|
),
|
|
/Field 'factorio\.host_assigned_game_port' is not accessible from control/
|
|
);
|
|
});
|
|
it("rejects setting 'instance.assigned_host'", async function() {
|
|
await assert.rejects(
|
|
controlConnection.handleInstanceConfigSetFieldRequest(
|
|
new lib.InstanceConfigSetFieldRequest(
|
|
instanceConfig.get("instance.id"), "instance.assigned_host", 5
|
|
)
|
|
),
|
|
/instance\.assigned_host must be set through the assign-host interface/
|
|
);
|
|
});
|
|
it("rejects setting 'instance.id'", async function() {
|
|
await assert.rejects(
|
|
controlConnection.handleInstanceConfigSetFieldRequest(
|
|
new lib.InstanceConfigSetFieldRequest(
|
|
instanceConfig.get("instance.id"), "instance.id", 5
|
|
)
|
|
),
|
|
/Setting instance\.id is not supported/
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("InstanceConfigSetPropRequest", function() {
|
|
it("is round trip json serialisable", function() {
|
|
testRoundTripJsonSerialisable(lib.InstanceConfigSetPropRequest, testMatrix(
|
|
[1], // instance id
|
|
["controller.name", "invalid"], // field
|
|
["foo", "bar"], // prop
|
|
["Foo Bar", true, 5, {}], // value
|
|
));
|
|
});
|
|
it("runs", async function() {
|
|
controlConnection.handleInstanceConfigSetPropRequest(
|
|
new lib.InstanceConfigSetPropRequest(
|
|
instanceConfig.get("instance.id"), "factorio.settings", "name", "Foo Bar"
|
|
)
|
|
);
|
|
const { name, ...rest } = instanceConfig.get("factorio.settings");
|
|
assert.equal(name, "Foo Bar");
|
|
});
|
|
it("rejects if the field does not exist", async function() {
|
|
await assert.rejects(
|
|
controlConnection.handleInstanceConfigSetPropRequest(
|
|
new lib.InstanceConfigSetPropRequest(instanceConfig.get("instance.id"), "invalid", "foo", "bar")
|
|
),
|
|
/No field named 'invalid'/
|
|
);
|
|
});
|
|
it("rejects if the field is not an object", async function() {
|
|
await assert.rejects(
|
|
controlConnection.handleInstanceConfigSetPropRequest(
|
|
new lib.InstanceConfigSetPropRequest(
|
|
instanceConfig.get("instance.id"), "instance.name", "foo", "bar"
|
|
)
|
|
),
|
|
/Cannot set property on non-object field 'instance\.name'/
|
|
);
|
|
});
|
|
it("rejects if the field is inaccessible", async function() {
|
|
await assert.rejects(
|
|
controlConnection.handleInstanceConfigSetPropRequest(
|
|
new lib.InstanceConfigSetPropRequest(
|
|
instanceConfig.get("instance.id"), "factorio.host_assigned_game_port", "foo", "bar"
|
|
)
|
|
),
|
|
/Field 'factorio\.host_assigned_game_port' is not accessible from control/
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("InstanceConfigSetRequest", function() {
|
|
it("is round trip json serialisable", function() {
|
|
testRoundTripJsonSerialisable(lib.InstanceConfigSetRequest, testMatrix(
|
|
[1], // instance id
|
|
[ // fields
|
|
{ "foo": "bar", "baz": { "cat": "dog" } },
|
|
{ "foo": "5", "baz": { "cat": "5" } },
|
|
{ "foo": "true", "baz": { "cat": "true" } },
|
|
{ "foo": "{}", "baz": { "cat": "{}" } },
|
|
],
|
|
));
|
|
});
|
|
it("runs", async function() {
|
|
await controlConnection.handleInstanceConfigSetRequest(new lib.InstanceConfigSetRequest(
|
|
instanceConfig.get("instance.id"), {
|
|
"instance.name": "Foo Bar",
|
|
"factorio.executable_path": "Bar Baz",
|
|
})
|
|
);
|
|
assert.equal(instanceConfig.get("instance.name"), "Foo Bar");
|
|
assert.equal(instanceConfig.get("factorio.executable_path"), "Bar Baz");
|
|
});
|
|
it("rejects if the field does not exist", async function() {
|
|
await assert.rejects(
|
|
controlConnection.handleInstanceConfigSetRequest(new lib.InstanceConfigSetRequest(
|
|
instanceConfig.get("instance.id"), {
|
|
"instance.name": "Foo Bar",
|
|
"invalid": "Bar Baz",
|
|
})
|
|
),
|
|
/No field named 'invalid'/
|
|
);
|
|
assert.equal(instanceConfig.get("instance.name"), "New Instance");
|
|
});
|
|
it("rejects if the field is not an object", async function() {
|
|
const prevPath = instanceConfig.get("factorio.executable_path");
|
|
await assert.rejects(
|
|
controlConnection.handleInstanceConfigSetRequest(new lib.InstanceConfigSetRequest(
|
|
instanceConfig.get("instance.id"), {
|
|
"instance.name": "Foo Bar",
|
|
"factorio.executable_path": {
|
|
"foo": "bar",
|
|
},
|
|
})
|
|
),
|
|
/Cannot set property on non-object field 'factorio\.executable_path'/
|
|
);
|
|
assert.equal(instanceConfig.get("instance.name"), "New Instance");
|
|
assert.equal(instanceConfig.get("factorio.executable_path"), prevPath);
|
|
});
|
|
it("rejects if the field is inaccessible", async function() {
|
|
await assert.rejects(
|
|
controlConnection.handleInstanceConfigSetRequest(new lib.InstanceConfigSetRequest(
|
|
instanceConfig.get("instance.id"), {
|
|
"instance.name": "Foo Bar",
|
|
"factorio.host_assigned_game_port": "Bar Baz",
|
|
})
|
|
),
|
|
/Field 'factorio\.host_assigned_game_port' is not accessible from control/
|
|
);
|
|
assert.equal(instanceConfig.get("instance.name"), "New Instance");
|
|
// Can't check value of host_assigned_game_port as it is not accessible on controller
|
|
});
|
|
it("rejects setting 'instance.assigned_host'", async function() {
|
|
const prevHost = instanceConfig.get("instance.assigned_host");
|
|
await assert.rejects(
|
|
controlConnection.handleInstanceConfigSetRequest(new lib.InstanceConfigSetRequest(
|
|
instanceConfig.get("instance.id"), {
|
|
"instance.name": "Foo Bar",
|
|
"instance.assigned_host": 5,
|
|
})
|
|
),
|
|
/instance\.assigned_host must be set through the assign-host interface/
|
|
);
|
|
assert.equal(instanceConfig.get("instance.name"), "New Instance");
|
|
assert.equal(instanceConfig.get("instance.assigned_host"), prevHost);
|
|
});
|
|
it("rejects setting 'instance.id'", async function() {
|
|
const prevId = instanceConfig.get("instance.id");
|
|
await assert.rejects(
|
|
controlConnection.handleInstanceConfigSetRequest(new lib.InstanceConfigSetRequest(
|
|
instanceConfig.get("instance.id"), {
|
|
"instance.name": "Foo Bar",
|
|
"instance.id": 5,
|
|
})
|
|
),
|
|
/Setting instance\.id is not supported/
|
|
);
|
|
assert.equal(instanceConfig.get("instance.name"), "New Instance");
|
|
assert.equal(instanceConfig.get("instance.id"), prevId);
|
|
});
|
|
});
|
|
|
|
describe("External", function() {
|
|
it("runs", async function() {
|
|
let callCount = 0;
|
|
const versions = [
|
|
{ version: "1.2.3", stable: true, headlessUrl: "path/to/download" },
|
|
{ version: "1.2.4", stable: false, headlessUrl: "path/to/download" },
|
|
];
|
|
|
|
// Mock the cache to avoid needless fetches
|
|
controller.factorioVersions = new lib.ValueCache(async () => {
|
|
callCount += 1;
|
|
return versions;
|
|
});
|
|
|
|
// First call should result in a fetch
|
|
const versions1 = await controlConnection.handleFactorioVersionsRequest(
|
|
new lib.FactorioVersionsRequest()
|
|
);
|
|
|
|
assert.deepEqual(versions1, versions);
|
|
assert.equal(callCount, 1);
|
|
|
|
// Second call should not fetch due to default maxAgeMs being greater than 0
|
|
const versions2 = await controlConnection.handleFactorioVersionsRequest(
|
|
new lib.FactorioVersionsRequest()
|
|
);
|
|
|
|
assert.deepEqual(versions2, versions);
|
|
assert.equal(callCount, 1);
|
|
|
|
// Setting maxAgeMs to 0 should result in a new fetch
|
|
const versions3 = await controlConnection.handleFactorioVersionsRequest(
|
|
new lib.FactorioVersionsRequest(0)
|
|
);
|
|
|
|
assert.deepEqual(versions3, versions);
|
|
assert.equal(callCount, 2);
|
|
});
|
|
it("round trips LatestReleasesRequest", function() {
|
|
testRoundTripJsonSerialisable(lib.LatestReleasesRequest, [[], [0], [60000]]);
|
|
});
|
|
it("caches the latest releases and persists them for offline use", async function() {
|
|
const releases = {
|
|
stable: { headless: "2.0.77" },
|
|
experimental: { headless: "2.1.8" },
|
|
};
|
|
const databaseDirectory = await fs.mkdtemp(path.join(os.tmpdir(), "clusterio-test-"));
|
|
controller.config.set("controller.database_directory", databaseDirectory);
|
|
const cachePath = path.join(databaseDirectory, "latest-releases.json");
|
|
const originalFetch = global.fetch;
|
|
try {
|
|
let callCount = 0;
|
|
global.fetch = async () => {
|
|
callCount += 1;
|
|
return { ok: true, status: 200, statusText: "OK", json: async () => releases };
|
|
};
|
|
|
|
// First call fetches from the api and persists the result to disk
|
|
const fetched = await controlConnection.handleLatestReleasesRequest(
|
|
new lib.LatestReleasesRequest(0)
|
|
);
|
|
assert.deepEqual(fetched, releases);
|
|
assert.equal(callCount, 1);
|
|
assert.deepEqual(JSON.parse(await fs.readFile(cachePath, "utf8")), releases);
|
|
|
|
// When the api is unavailable the persisted copy is served instead
|
|
global.fetch = async () => { throw new Error("Network Error"); };
|
|
const cached = await controlConnection.handleLatestReleasesRequest(
|
|
new lib.LatestReleasesRequest(0)
|
|
);
|
|
assert.deepEqual(cached, releases);
|
|
|
|
// With no persisted copy and an unavailable api an empty object is returned
|
|
await fs.rm(cachePath);
|
|
const empty = await controlConnection.handleLatestReleasesRequest(
|
|
new lib.LatestReleasesRequest(0)
|
|
);
|
|
assert.deepEqual(empty, {});
|
|
} finally {
|
|
global.fetch = originalFetch;
|
|
await fs.rm(databaseDirectory, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
});
|