mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-14 07:24:52 +09:00
Merge pull request #1129 from bbassie/refactor/async-factory
Replace bootstrap methods with async factories
This commit is contained in:
18 files changed
+540
-281
No files matched your search
@@ -14,6 +14,7 @@ import {
|
||||
addPluginConfigFields, renamePermissionPrefix,
|
||||
} from "@clusterio/lib";
|
||||
|
||||
import * as lib from "@clusterio/lib";
|
||||
import "../setup_logging.js";
|
||||
|
||||
class MockEvent {}
|
||||
@@ -57,6 +58,77 @@ describe("controller/src/Controller", function() {
|
||||
assert(list.check(ip), `${ip} missing from list`);
|
||||
}
|
||||
}
|
||||
describe(".fromConfig()", function() {
|
||||
let dir;
|
||||
beforeEach(async function() {
|
||||
dir = await fs.mkdtemp(path.join(os.tmpdir(), "clusterio-controller-"));
|
||||
});
|
||||
afterEach(async function() {
|
||||
await fs.rm(dir, { recursive: true, force: true });
|
||||
});
|
||||
it("should load datastores and mods from disk", async function() {
|
||||
const databaseDirectory = path.join(dir, "database");
|
||||
const modsDirectory = path.join(dir, "mods");
|
||||
await fs.mkdir(databaseDirectory);
|
||||
await fs.writeFile(path.join(databaseDirectory, "notes.json"), JSON.stringify([
|
||||
{ resourceType: "instance", resourceId: "100", content: "hello" },
|
||||
]));
|
||||
const config = new ControllerConfig("controller", {
|
||||
"controller.version": controllerVersion,
|
||||
"controller.database_directory": databaseDirectory,
|
||||
"controller.mods_directory": modsDirectory,
|
||||
});
|
||||
|
||||
const loaded = await Controller.fromConfig(config, [], {}, true, false);
|
||||
assert(loaded instanceof Controller);
|
||||
assert.equal(loaded.canRestart, true);
|
||||
assert.equal(loaded.recoveryMode, false);
|
||||
assert.equal(loaded.notes.get("instance/100").content, "hello");
|
||||
assert.equal(loaded.modStore.modsDirectory, modsDirectory);
|
||||
assert(loaded.roles.size > 0, "default roles were not added");
|
||||
assert(loaded.modPacks.size > 0, "default mod packs were not added");
|
||||
await fs.access(modsDirectory);
|
||||
});
|
||||
describe("with a plugin version change", function() {
|
||||
before(function() {
|
||||
addPluginConfigFields([{ name: "from_config_plugin", version: "1.2.0", instanceEntrypoint: "x" }]);
|
||||
});
|
||||
afterEach(function() {
|
||||
lib.Config.pluginVersions.clear();
|
||||
});
|
||||
after(function() {
|
||||
for (const Config of [lib.ControllerConfig, lib.HostConfig, lib.InstanceConfig]) {
|
||||
for (const name of Object.keys(Config.fieldDefinitions)) {
|
||||
if (name.startsWith("from_config_plugin.")) {
|
||||
delete Config.fieldDefinitions[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
it("should save instances whose config changed while loading", async function() {
|
||||
const databaseDirectory = path.join(dir, "database");
|
||||
const instancesPath = path.join(databaseDirectory, "instances.json");
|
||||
await fs.mkdir(databaseDirectory);
|
||||
const instanceConfig = new InstanceConfig("controller", {
|
||||
"instance.id": 7, "from_config_plugin.version": "1.0.0",
|
||||
});
|
||||
await fs.writeFile(instancesPath, JSON.stringify([
|
||||
{ config: instanceConfig.toJSON(), status: "unassigned" },
|
||||
]));
|
||||
lib.Config.pluginVersions.set("from_config_plugin", "1.2.0");
|
||||
const config = new ControllerConfig("controller", {
|
||||
"controller.version": controllerVersion,
|
||||
"controller.database_directory": databaseDirectory,
|
||||
"controller.mods_directory": path.join(dir, "mods"),
|
||||
});
|
||||
|
||||
const loaded = await Controller.fromConfig(config, [], {});
|
||||
await loaded.instances.records.save();
|
||||
const [saved] = JSON.parse(await fs.readFile(instancesPath, "utf8"));
|
||||
assert.equal(saved.config["from_config_plugin.version"], "1.2.0");
|
||||
});
|
||||
});
|
||||
});
|
||||
describe(".loadJsonObject()", function() {
|
||||
let dir;
|
||||
before(async function() {
|
||||
|
||||
@@ -62,6 +62,44 @@ describe("host/src/Host", function() {
|
||||
});
|
||||
});
|
||||
describe("class Host", function() {
|
||||
describe(".fromConfig()", function() {
|
||||
const modsDirectory = path.join("temp", "test", "host_from_config", "mods");
|
||||
beforeEach(async function() {
|
||||
await fs.rm(path.dirname(modsDirectory), { recursive: true, force: true });
|
||||
});
|
||||
it("should create the mods directory and load the mod store", async function() {
|
||||
const hostConfig = new lib.HostConfig("host", { "host.mods_directory": modsDirectory });
|
||||
const host = await Host.fromConfig(hostConfig, [], new HostConnector(hostConfig, []), true, false);
|
||||
assert(host instanceof Host);
|
||||
assert.equal(host.canRestart, true);
|
||||
assert.equal(host.recoveryMode, false);
|
||||
assert.equal(host.modStore.modsDirectory, modsDirectory);
|
||||
await fs.access(modsDirectory);
|
||||
});
|
||||
});
|
||||
describe("._connectInstance()", function() {
|
||||
const originalFromConfig = Instance.fromConfig;
|
||||
afterEach(function() {
|
||||
Instance.fromConfig = originalFromConfig;
|
||||
});
|
||||
it("should reject a second start while the instance is starting", async function() {
|
||||
const hostConfig = new lib.HostConfig("host");
|
||||
const host = new Host(new HostConnector(hostConfig, []), hostConfig, []);
|
||||
const instanceConfig = new lib.InstanceConfig("host", { "instance.id": 7 });
|
||||
host.assignedInstances.set(7, { path: "dir", config: instanceConfig });
|
||||
let fail;
|
||||
Instance.fromConfig = () => new Promise((resolve, reject) => { fail = reject; });
|
||||
|
||||
const first = host._connectInstance(7);
|
||||
await assert.rejects(host._connectInstance(7), /Instance with ID 7 is running/);
|
||||
assert.equal(host._unconnectedStatus(7), "starting");
|
||||
|
||||
fail(new Error("start failed"));
|
||||
await assert.rejects(first, /start failed/);
|
||||
assert.equal(host._unconnectedStatus(7), "stopped");
|
||||
assert.equal(host.instanceConnections.has(7), false);
|
||||
});
|
||||
});
|
||||
describe(".handleSyncUserListsEvent()", function() {
|
||||
const hostAddress = lib.Address.fromShorthand({ hostId: 1 });
|
||||
const instanceAddress = lib.Address.fromShorthand({ instanceId: 1 });
|
||||
|
||||
+75
-2
@@ -1,4 +1,5 @@
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
import * as lib from "@clusterio/lib";
|
||||
@@ -12,6 +13,79 @@ const addr = lib.Address.fromShorthand;
|
||||
|
||||
|
||||
describe("class Instance", function() {
|
||||
describe(".fromConfig()", function() {
|
||||
const factorioDir = path.join("test", "file", "factorio");
|
||||
const dir = path.join("temp", "test", "instance_from_config");
|
||||
let host;
|
||||
let sent;
|
||||
let saveListUpdates;
|
||||
let requests;
|
||||
function makeConfig(fields) {
|
||||
return new lib.InstanceConfig("host", { "instance.id": 5, "instance.name": "foo", ...fields });
|
||||
}
|
||||
function fromConfig(config) {
|
||||
const connector = new MockConnector(addr({ instanceId: 5 }), addr({ hostId: 1 }));
|
||||
return Instance.fromConfig(config, host, connector, dir, factorioDir);
|
||||
}
|
||||
beforeEach(async function() {
|
||||
await fs.rm(dir, { recursive: true, force: true });
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
sent = [];
|
||||
saveListUpdates = [];
|
||||
requests = [];
|
||||
host = {
|
||||
assignFreeGamePort: async () => 34100,
|
||||
send(event) { sent.push(event); },
|
||||
async sendTo(dst, request) {
|
||||
requests.push(request.constructor.name);
|
||||
if (request instanceof lib.FactorioVersionsRequest) {
|
||||
return [];
|
||||
}
|
||||
if (request instanceof lib.LatestReleasesRequest) {
|
||||
return { stable: { headless: "0.1.1" } };
|
||||
}
|
||||
throw new Error(`Unexpected request ${request.constructor.name}`);
|
||||
},
|
||||
async sendSaveListUpdate(instanceId, savesDir) { saveListUpdates.push([instanceId, savesDir]); },
|
||||
};
|
||||
});
|
||||
|
||||
it("should create an instance with a server for the installed version", async function() {
|
||||
const instance = await fromConfig(makeConfig({}));
|
||||
assert.equal(instance.status, "starting");
|
||||
assert.equal(instance.server.version, "0.1.2");
|
||||
assert.equal(instance.server.gamePort, 34100);
|
||||
assert.deepEqual(sent, [new lib.InstanceStatusChangedEvent(5, "starting", 34100, "latest")]);
|
||||
assert.deepEqual(requests, ["FactorioVersionsRequest"]);
|
||||
});
|
||||
it("should use the configured game port", async function() {
|
||||
const instance = await fromConfig(makeConfig({ "factorio.game_port": 34200 }));
|
||||
assert.equal(instance.server.gamePort, 34200);
|
||||
});
|
||||
it("should resolve a release channel through the controller", async function() {
|
||||
const instance = await fromConfig(makeConfig({ "factorio.version": "stable" }));
|
||||
assert.equal(instance.server.version, "0.1.1");
|
||||
assert.deepEqual(requests, ["FactorioVersionsRequest", "LatestReleasesRequest"]);
|
||||
});
|
||||
it("should load player stats", async function() {
|
||||
await fs.writeFile(path.join(dir, "instance-stats.json"), JSON.stringify({
|
||||
players: [["player", { join_count: 2 }]],
|
||||
player_autosave_slot: 3,
|
||||
}));
|
||||
const instance = await fromConfig(makeConfig({}));
|
||||
assert.equal(instance.playerStats.get("player").joinCount, 2);
|
||||
assert.equal(instance._playerAutosaveSlot, 3);
|
||||
});
|
||||
it("should report the instance stopped if the version is not installed", async function() {
|
||||
await assert.rejects(fromConfig(makeConfig({ "factorio.version": "0.1.5" })), /0\.1\.5/);
|
||||
assert.deepEqual(sent, [
|
||||
new lib.InstanceStatusChangedEvent(5, "starting", 34100, "0.1.5"),
|
||||
new lib.InstanceStatusChangedEvent(5, "stopped", 34100, "0.1.5"),
|
||||
]);
|
||||
assert.deepEqual(saveListUpdates, [[5, path.join(dir, "saves")]]);
|
||||
});
|
||||
});
|
||||
|
||||
let src;
|
||||
let dst = addr({ hostId: 1 });
|
||||
let instance;
|
||||
@@ -21,8 +95,7 @@ describe("class Instance", function() {
|
||||
instanceConfig.set("instance.name", "foo");
|
||||
src = addr({ instanceId: instanceConfig.get("instance.id") });
|
||||
connector = new MockConnector(src, dst);
|
||||
instance = new Instance({ assignGamePort: () => 1 }, connector, "dir", "factorioDir", instanceConfig);
|
||||
instance.server = new MockServer();
|
||||
instance = new Instance({}, connector, "dir", instanceConfig, new MockServer());
|
||||
});
|
||||
|
||||
describe(".name", function() {
|
||||
|
||||
+1
-2
@@ -9,8 +9,7 @@ describe("host/src/export", function() {
|
||||
let testServer;
|
||||
before(async function() {
|
||||
let writePath = path.join("temp", "test", "server");
|
||||
testServer = new FactorioServer(path.join("test", "file", "factorio"), writePath, {});
|
||||
await testServer.init();
|
||||
testServer = await FactorioServer.fromOptions(path.join("test", "file", "factorio"), writePath, {});
|
||||
});
|
||||
|
||||
describe("exportLocale()", function() {
|
||||
|
||||
+52
-51
@@ -223,7 +223,10 @@ describe("host/server", function() {
|
||||
|
||||
describe("class FactorioServer", function() {
|
||||
let writePath = path.join("temp", "test", "server");
|
||||
let server = new hostServer.FactorioServer(path.join("test", "file", "factorio"), writePath, {});
|
||||
let server;
|
||||
before(async function() {
|
||||
server = await hostServer.FactorioServer.fromOptions(path.join("test", "file", "factorio"), writePath, {});
|
||||
});
|
||||
|
||||
describe("constructor()", function() {
|
||||
it("should handle dashes in write path with strapPaths enabled", function() {
|
||||
@@ -231,28 +234,37 @@ describe("host/server", function() {
|
||||
new hostServer.FactorioServer(
|
||||
path.join("test", "file", "factorio"),
|
||||
path.join("temp", "test", "server-1"),
|
||||
{ stripPaths: true }
|
||||
path.join("test", "file", "factorio", "0.1.2", "data"),
|
||||
"0.1.2",
|
||||
{ stripPaths: true, rconPassword: "" }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".setTargetVersion()", function() {
|
||||
it("should override the target version before init", function() {
|
||||
const fresh = new hostServer.FactorioServer(
|
||||
path.join("test", "file", "factorio"), writePath, {}
|
||||
describe(".fromOptions()", function() {
|
||||
it("should resolve the requested version", async function() {
|
||||
const found = await hostServer.FactorioServer.fromOptions(
|
||||
path.join("test", "file", "factorio"), writePath, { version: "0.1.1" }
|
||||
);
|
||||
fresh.setTargetVersion("0.1.5");
|
||||
assert.equal(fresh._targetVersion, "0.1.5");
|
||||
assert.equal(found.version, "0.1.1");
|
||||
assert.equal(found.dataPath(), path.join("test", "file", "factorio", "0.1.1", "data"));
|
||||
assert.equal(found._state, "init");
|
||||
});
|
||||
});
|
||||
|
||||
describe(".init()", function() {
|
||||
it("should not throw on first call", async function() {
|
||||
await server.init();
|
||||
it("should generate an RCON password if none is given", async function() {
|
||||
assert.equal(typeof server.rconPassword, "string");
|
||||
assert.equal(server.rconPassword.length, 10);
|
||||
const given = await hostServer.FactorioServer.fromOptions(
|
||||
path.join("test", "file", "factorio"), writePath, { rconPassword: "secret" }
|
||||
);
|
||||
assert.equal(given.rconPassword, "secret");
|
||||
});
|
||||
|
||||
it("should throw if called twice", async function() {
|
||||
await assert.rejects(server.init(), new Error("Expected state new but state is init"));
|
||||
it("should reject if the version is not installed", async function() {
|
||||
await assert.rejects(
|
||||
hostServer.FactorioServer.fromOptions(
|
||||
path.join("test", "file", "factorio"), writePath, { version: "0.1.5" }
|
||||
),
|
||||
/Unable to find Factorio version 0.1.5/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -314,7 +326,7 @@ describe("host/server", function() {
|
||||
describe(".handle()", function() {
|
||||
it("should log validation errors from ipc handlers", async function() {
|
||||
let logged = [];
|
||||
let ipcServer = new hostServer.FactorioServer(
|
||||
let ipcServer = await hostServer.FactorioServer.fromOptions(
|
||||
path.join("test", "file", "factorio"), writePath,
|
||||
{ logger: { error: msg => { logged.push(msg); } } }
|
||||
);
|
||||
@@ -571,7 +583,7 @@ describe("host/server", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe(".checkForUpdates()", function() {
|
||||
describe("checkForUpdates()", function() {
|
||||
let _fetch;
|
||||
let fetchCalledWith;
|
||||
let _platform = process.platform;
|
||||
@@ -589,7 +601,6 @@ describe("host/server", function() {
|
||||
});
|
||||
afterEach(function() {
|
||||
global.fetch = _fetch;
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
Object.defineProperty(process, "platform", {
|
||||
value: _platform,
|
||||
});
|
||||
@@ -602,18 +613,17 @@ describe("host/server", function() {
|
||||
]) {
|
||||
/* eslint-disable no-loop-func */
|
||||
describe(suite, function() {
|
||||
const factorioDir = path.join("test", "file", "factorio");
|
||||
// A target that matches the 0.1.5 download but not the installed 0.1.1
|
||||
const newerTarget = target === "0.1.1" ? "0.1.5" : target;
|
||||
it("should do nothing when there are no versions", async function() {
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
server._targetVersion = target;
|
||||
await server.checkForUpdates([]);
|
||||
await hostServer.checkForUpdates(factorioDir, target, []);
|
||||
|
||||
assert.equal(fetchCalledWith, null);
|
||||
});
|
||||
if (target !== "latest") {
|
||||
it("should do nothing when no version matches", async function() {
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
server._targetVersion = target;
|
||||
await server.checkForUpdates([{
|
||||
await hostServer.checkForUpdates(factorioDir, target, [{
|
||||
stable: true,
|
||||
version: "0.2.1",
|
||||
headlessUrl: "test1",
|
||||
@@ -627,9 +637,7 @@ describe("host/server", function() {
|
||||
});
|
||||
}
|
||||
it("should do nothing when there is no newer version", async function() {
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
server._targetVersion = target;
|
||||
await server.checkForUpdates([{
|
||||
await hostServer.checkForUpdates(factorioDir, target, [{
|
||||
stable: true,
|
||||
version: "0.1.1",
|
||||
headlessUrl: "test1",
|
||||
@@ -642,9 +650,7 @@ describe("host/server", function() {
|
||||
assert.equal(fetchCalledWith, null);
|
||||
});
|
||||
it("should do nothing for direct installs", async function() {
|
||||
server._factorioDir = path.join("test", "file", "factorio", "0.1.1");
|
||||
server._targetVersion = target === "0.1.1" ? "0.1.5" : target;
|
||||
await server.checkForUpdates([{
|
||||
await hostServer.checkForUpdates(path.join(factorioDir, "0.1.1"), newerTarget, [{
|
||||
stable: true,
|
||||
version: "0.1.5",
|
||||
headlessUrl: "test1",
|
||||
@@ -662,12 +668,10 @@ describe("host/server", function() {
|
||||
});
|
||||
it("should do nothing when on windows", async function() {
|
||||
let logLine = null;
|
||||
server._logger = { info: line => { logLine = line; } };
|
||||
const logger = { info: line => { logLine = line; } };
|
||||
Object.defineProperty(process, "platform", { value: "win32" });
|
||||
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
server._targetVersion = target === "0.1.1" ? "0.1.5" : target;
|
||||
await server.checkForUpdates([{
|
||||
await hostServer.checkForUpdates(factorioDir, newerTarget, [{
|
||||
stable: true,
|
||||
version: "0.1.5",
|
||||
headlessUrl: "test1",
|
||||
@@ -679,7 +683,7 @@ describe("host/server", function() {
|
||||
stable: false,
|
||||
version: "0.1.0",
|
||||
headlessUrl: "test3",
|
||||
}]);
|
||||
}], logger);
|
||||
|
||||
assert.equal(fetchCalledWith, null);
|
||||
assert.ok(logLine !== null);
|
||||
@@ -687,12 +691,10 @@ describe("host/server", function() {
|
||||
});
|
||||
it("should do attempt to download on linux", async function() {
|
||||
let logLine = null;
|
||||
server._logger = { info: line => { logLine = line; } };
|
||||
const logger = { info: line => { logLine = line; } };
|
||||
Object.defineProperty(process, "platform", { value: "linux" });
|
||||
|
||||
server._factorioDir = path.join("test", "file", "factorio");
|
||||
server._targetVersion = target === "0.1.1" ? "0.1.5" : target;
|
||||
await assert.rejects(server.checkForUpdates([{
|
||||
await assert.rejects(hostServer.checkForUpdates(factorioDir, newerTarget, [{
|
||||
stable: true,
|
||||
version: "0.1.5",
|
||||
headlessUrl: "test1",
|
||||
@@ -704,7 +706,7 @@ describe("host/server", function() {
|
||||
stable: false,
|
||||
version: "0.1.0",
|
||||
headlessUrl: "test3",
|
||||
}]), new Error("Failed to fetch test1: -1 Fetch called"));
|
||||
}], logger), new Error("Failed to fetch test1: -1 Fetch called"));
|
||||
|
||||
assert.equal(fetchCalledWith, "test1");
|
||||
assert.ok(logLine !== null);
|
||||
@@ -716,7 +718,7 @@ describe("host/server", function() {
|
||||
describe("concurrent downloads", function() {
|
||||
const versions = [{ stable: true, version: "0.1.5", headlessUrl: "test1" }];
|
||||
|
||||
// Holds the mocked download open until release() and resolves waiting once a server waits for it
|
||||
// Holds the mocked download open until release() and resolves waiting once a check waits for it
|
||||
function holdDownload(factorioDir) {
|
||||
const held = { fetches: 0 };
|
||||
const release = new Promise(resolve => { held.release = resolve; });
|
||||
@@ -731,7 +733,7 @@ describe("host/server", function() {
|
||||
let waited;
|
||||
held.waiting = new Promise(resolve => { waited = resolve; });
|
||||
const logger = { info: line => line.endsWith("waiting for download...") && waited() };
|
||||
held.servers = [0, 1].map(() => new hostServer.FactorioServer(factorioDir, writePath, { logger }));
|
||||
held.check = () => hostServer.checkForUpdates(factorioDir, "latest", versions, logger);
|
||||
Object.defineProperty(process, "platform", { value: "linux" });
|
||||
return held;
|
||||
}
|
||||
@@ -739,7 +741,7 @@ describe("host/server", function() {
|
||||
it("should download once when servers check at the same time", async function() {
|
||||
const held = holdDownload(path.join("test", "file", "factorio"));
|
||||
let settled = 0;
|
||||
const checks = held.servers.map(s => s.checkForUpdates(versions).finally(() => { settled += 1; }));
|
||||
const checks = [0, 1].map(() => held.check().finally(() => { settled += 1; }));
|
||||
|
||||
await held.waiting;
|
||||
assert.equal(held.fetches, 1);
|
||||
@@ -754,14 +756,14 @@ describe("host/server", function() {
|
||||
await fs.rm(factorioDir, { force: true, recursive: true });
|
||||
await fs.mkdir(factorioDir, { recursive: true });
|
||||
const held = holdDownload(factorioDir);
|
||||
const first = held.servers[0].checkForUpdates(versions);
|
||||
const first = held.check();
|
||||
await held.fetching;
|
||||
|
||||
// The extraction has written the changelog but has not finished
|
||||
const dataDir = path.join(factorioDir, "0.1.5", "data");
|
||||
await fs.mkdir(dataDir, { recursive: true });
|
||||
await fs.writeFile(path.join(dataDir, "changelog.txt"), "Version: 0.1.5\n");
|
||||
const second = held.servers[1].checkForUpdates(versions);
|
||||
const second = held.check();
|
||||
|
||||
await held.waiting;
|
||||
held.release();
|
||||
@@ -776,12 +778,11 @@ describe("host/server", function() {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
server._factorioDir = path.join("temp", "test", "factorioDownload");
|
||||
server._targetVersion = "latest";
|
||||
const factorioDir = path.join("temp", "test", "factorioDownload");
|
||||
global.fetch = _fetch;
|
||||
await fs.rm(server._factorioDir, { force: true, recursive: true, maxRetries: 10 });
|
||||
await fs.mkdir(server._factorioDir, { recursive: true });
|
||||
await server.checkForUpdates([{
|
||||
await fs.rm(factorioDir, { force: true, recursive: true, maxRetries: 10 });
|
||||
await fs.mkdir(factorioDir, { recursive: true });
|
||||
await hostServer.checkForUpdates(factorioDir, "latest", [{
|
||||
stable: true,
|
||||
version: "2.0.73",
|
||||
headlessUrl: "https://www.factorio.com/get-download/2.0.73/headless/linux64",
|
||||
|
||||
@@ -15,7 +15,10 @@ class FakeProcess extends events.EventEmitter {
|
||||
}
|
||||
|
||||
function createStartedServer() {
|
||||
const server = new FactorioServer(path.join("test", "file", "factorio"), path.join("temp", "test", "server"), {});
|
||||
const server = new FactorioServer(
|
||||
path.join("test", "file", "factorio"), path.join("temp", "test", "server"),
|
||||
path.join("test", "file", "factorio", "0.1.2", "data"), "0.1.2", { rconPassword: "" },
|
||||
);
|
||||
server._state = "running";
|
||||
server._rconReady = false;
|
||||
server._server = new FakeProcess();
|
||||
|
||||
@@ -12,8 +12,7 @@ describe("Integration of lib/factorio/export", function() {
|
||||
requiresFactorio(this);
|
||||
it("returns a nested Map with base game locale information", async function() {
|
||||
let writePath = path.join("temp", "test", "server");
|
||||
let testServer = new FactorioServer(factorioDir, writePath, {});
|
||||
await testServer.init();
|
||||
let testServer = await FactorioServer.fromOptions(factorioDir, writePath, {});
|
||||
|
||||
let locale = await _exportLocale(testServer, new Map(), new Map(), ["base"], "en");
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { Host, Instance, InstanceConnection } from "@clusterio/host";
|
||||
import { ControlConnection, Controller, HostConnection, InstanceRecord } from "@clusterio/controller";
|
||||
import { MockServer } from "../mock.js";
|
||||
|
||||
// Although there are no imports, the file has side-effects such as "before" which can effect tests
|
||||
import "./index.js";
|
||||
@@ -39,7 +40,7 @@ function connectInstance(controller, host, instanceId) {
|
||||
instanceConfig.set("instance.assigned_host", host.config.get("host.id"));
|
||||
host.assignedInstances.set(instanceId, { path: "invalid", config: instanceConfig });
|
||||
controller.instances.records.set(new InstanceRecord(instanceConfig, "stopped"));
|
||||
const instance = new Instance(host, instanceSide, "invalid", "invalid", instanceConfig);
|
||||
const instance = new Instance(host, instanceSide, "invalid", instanceConfig, new MockServer());
|
||||
const instanceConnection = new InstanceConnection(hostSide, host, instance);
|
||||
host.instanceConnections.set(instanceId, instanceConnection);
|
||||
return instance;
|
||||
|
||||
@@ -40,12 +40,14 @@ describe("Integration of host/src/server", function() {
|
||||
|
||||
describe("class FactorioServer", function() {
|
||||
let writePath = path.join("temp", "test", "integration");
|
||||
let server = new FactorioServer(factorioDir, writePath, {});
|
||||
// Let Factorio bind an OS-assigned UDP port instead of guessing a free one.
|
||||
server.gamePort = 0;
|
||||
let server;
|
||||
let logFile;
|
||||
|
||||
before(async function() {
|
||||
server = await FactorioServer.fromOptions(factorioDir, writePath, {});
|
||||
// Let Factorio bind an OS-assigned UDP port instead of guessing a free one.
|
||||
server.gamePort = 0;
|
||||
|
||||
// Delete result from previous run of these tests
|
||||
await fs.rm(writePath, { force: true, recursive: true, maxRetries: 10 });
|
||||
|
||||
@@ -63,9 +65,6 @@ describe("Integration of host/src/server", function() {
|
||||
}
|
||||
logFile.write(`${JSON.stringify(output)}\n`);
|
||||
});
|
||||
|
||||
// Initialize sever.
|
||||
await server.init();
|
||||
});
|
||||
|
||||
after(function() {
|
||||
|
||||
+29
-21
@@ -50,13 +50,6 @@ describe("lib/datastore", function() {
|
||||
it("can be constructed", function() {
|
||||
assert.deepEqual(datastoreProvider.methodCalls[0], ["constructor"]);
|
||||
});
|
||||
|
||||
it("can be bootstrapped", async function() {
|
||||
const rtn = await datastoreProvider.bootstrap();
|
||||
assert.equal(rtn[0], datastoreProvider);
|
||||
assert.equal(rtn[1], datastoreProvider.value);
|
||||
assert.deepEqual(datastoreProvider.methodCalls[1], ["load"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("class MemoryDatastoreProvider", function() {
|
||||
@@ -70,13 +63,13 @@ describe("lib/datastore", function() {
|
||||
|
||||
describe("constructor", function() {
|
||||
it("has a default value", async function() {
|
||||
const [, value] = await new lib.MemoryDatastoreProvider().bootstrap();
|
||||
const value = await new lib.MemoryDatastoreProvider().load();
|
||||
assert.notEqual(value, undefined);
|
||||
assert.notEqual(value, null);
|
||||
});
|
||||
it("can have a value given", async function() {
|
||||
const expectedValue = new Map([["foo", "bar"]]);
|
||||
const [, value] = await new lib.MemoryDatastoreProvider(expectedValue).bootstrap();
|
||||
const value = await new lib.MemoryDatastoreProvider(expectedValue).load();
|
||||
assert.deepEqual(value, expectedValue);
|
||||
});
|
||||
});
|
||||
@@ -116,7 +109,7 @@ describe("lib/datastore", function() {
|
||||
it("has default values", async function() {
|
||||
datastoreProvider = new lib.JsonDatastoreProvider(dataPath);
|
||||
|
||||
const [, value] = await datastoreProvider.bootstrap();
|
||||
const value = await datastoreProvider.load();
|
||||
assert.deepEqual(value, new Map(initialData));
|
||||
});
|
||||
it("can accept a fromJson method", async function() {
|
||||
@@ -125,7 +118,7 @@ describe("lib/datastore", function() {
|
||||
json => [json]
|
||||
);
|
||||
|
||||
const [, value] = await datastoreProvider.bootstrap();
|
||||
const value = await datastoreProvider.load();
|
||||
assert.deepEqual(value, new Map(initialData.map(([k, v]) => [k, [v]])));
|
||||
});
|
||||
it("can accept a migration method", async function() {
|
||||
@@ -135,7 +128,7 @@ describe("lib/datastore", function() {
|
||||
rawJson => ({ c: "foo", ...rawJson })
|
||||
);
|
||||
|
||||
const [, value] = await datastoreProvider.bootstrap();
|
||||
const value = await datastoreProvider.load();
|
||||
assert.deepEqual(value, new Map([...initialData, ["c", "foo"]]));
|
||||
});
|
||||
it("can accept a finalise method", async function() {
|
||||
@@ -146,7 +139,7 @@ describe("lib/datastore", function() {
|
||||
obj => [obj]
|
||||
);
|
||||
|
||||
const [, value] = await datastoreProvider.bootstrap();
|
||||
const value = await datastoreProvider.load();
|
||||
assert.deepEqual(value, new Map(initialData.map(([k, v]) => [k, [v]])));
|
||||
});
|
||||
});
|
||||
@@ -210,7 +203,7 @@ describe("lib/datastore", function() {
|
||||
it("has default values", async function() {
|
||||
datastoreProvider = new lib.JsonIdDatastoreProvider(dataPath);
|
||||
|
||||
const [, value] = await datastoreProvider.bootstrap();
|
||||
const value = await datastoreProvider.load();
|
||||
assert.deepEqual(value, new Map(initialData));
|
||||
});
|
||||
it("can accept a fromJson method", async function() {
|
||||
@@ -219,7 +212,7 @@ describe("lib/datastore", function() {
|
||||
json => ({...json, foo: true})
|
||||
);
|
||||
|
||||
const [, value] = await datastoreProvider.bootstrap();
|
||||
const value = await datastoreProvider.load();
|
||||
assert.deepEqual(value, new Map(initialData.map(v => ([v[0], {...v[1], foo: true}]))));
|
||||
});
|
||||
it("can accept a migration method", async function() {
|
||||
@@ -229,7 +222,7 @@ describe("lib/datastore", function() {
|
||||
rawJson => ([{ id: "c" }, ...rawJson])
|
||||
);
|
||||
|
||||
const [, value] = await datastoreProvider.bootstrap();
|
||||
const value = await datastoreProvider.load();
|
||||
assert.deepEqual(value, new Map([...initialData, ["c", { id: "c" }]]));
|
||||
});
|
||||
it("can accept a finalise method", async function() {
|
||||
@@ -240,7 +233,7 @@ describe("lib/datastore", function() {
|
||||
obj => ({...obj, foo: true})
|
||||
);
|
||||
|
||||
const [, value] = await datastoreProvider.bootstrap();
|
||||
const value = await datastoreProvider.load();
|
||||
assert.deepEqual(value, new Map(initialData.map(v => ([v[0], {...v[1], foo: true}]))));
|
||||
});
|
||||
});
|
||||
@@ -306,10 +299,6 @@ describe("lib/datastore", function() {
|
||||
datastore = new MockDatastore(datastoreProvider, datastoreProvider.value);
|
||||
assert.deepEqual(datastore.getData(), datastoreProvider.value);
|
||||
});
|
||||
it("accepts the return of bootstrap", async function() {
|
||||
datastore = new MockDatastore(...await datastoreProvider.bootstrap());
|
||||
assert.deepEqual(datastore.getData(), datastoreProvider.value);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getFilePath", function() {
|
||||
@@ -478,6 +467,15 @@ describe("lib/datastore", function() {
|
||||
datastore.on("update", updates => eventsRaised.push(updates));
|
||||
});
|
||||
|
||||
describe("fromProvider", function() {
|
||||
it("loads data from the provider", async function() {
|
||||
datastore = await lib.KeyValueDatastore.fromProvider(datastoreProvider);
|
||||
assert(datastore instanceof lib.KeyValueDatastore);
|
||||
assert.equal(datastore.get("foo"), "fooValue");
|
||||
assert.deepEqual(datastoreProvider.methodCalls[1], ["load"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("set", function() {
|
||||
it("can set a value", function() {
|
||||
datastore.set("test", "testValue");
|
||||
@@ -571,6 +569,16 @@ describe("lib/datastore", function() {
|
||||
datastore.on("update", updates => eventsRaised.push(updates));
|
||||
});
|
||||
|
||||
describe("fromProvider", function() {
|
||||
it("loads data from the provider", async function() {
|
||||
datastore = await lib.SubscribableDatastore.fromProvider(datastoreProvider);
|
||||
assert(datastore instanceof lib.SubscribableDatastore);
|
||||
assert.equal(datastore.get("foo").id, "foo");
|
||||
assert.equal(datastore.lastUpdatedAtMs, 10);
|
||||
assert.deepEqual(datastoreProvider.methodCalls[1], ["load"]);
|
||||
});
|
||||
});
|
||||
|
||||
function forceFuture() {
|
||||
const future = {id: "future"};
|
||||
for (let i = 1; i < 100; i++) {
|
||||
|
||||
Reference in new issue
Block a user