Merge pull request #1129 from bbassie/refactor/async-factory

Replace bootstrap methods with async factories
This commit is contained in:
Cooldude2606 authored and GitHub committed 2026-10-27 21:31:49 +00:00
commit e36a76acc5
18 files changed
+540 -281

No files matched your search

+72
View File
@@ -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() {
+38
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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",
+4 -1
View File
@@ -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();
+1 -2
View File
@@ -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");
+2 -1
View File
@@ -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;
+5 -6
View File
@@ -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
View File
@@ -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++) {