Cover the create-scenario command handler and module loading

Adds tests for handleCreateScenarioCommand using a fake Factorio
install with the test scenario as freeplay, for loadModules skipping
plugins without a module and rejecting a name clash with a bundled
module, and an integration test running clusteriohost create-scenario.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
bbassieandClaude Fable 5.1 committed 2026-09-10 00:36:35 +00:00
1 parent c419f3e9b2
commit 1072e5f1de
3 files changed
+128 -1

No files matched your search

+86 -1
View File
@@ -5,7 +5,9 @@ const JSZip = require("jszip");
const path = require("path");
const lib = require("@clusterio/lib");
const { createScenario } = require("@clusterio/host/dist/node/src/create_scenario");
const {
createScenario, createScenarioCommand, handleCreateScenarioCommand,
} = require("@clusterio/host/dist/node/src/create_scenario");
const { SaveModule, PatchInfo } = require("@clusterio/host/dist/node/src/patch");
@@ -90,4 +92,87 @@ describe("host/create_scenario", function() {
new Error("Unable to patch save, unknown scenario (3acc3be3861144e55604f5ac2f2555071885ebc4)")
);
});
describe("createScenarioCommand()", function() {
it("should define the output positional and options", function() {
const positionals = [];
const options = [];
const yargs = {
positional(name) { positionals.push(name); return yargs; },
option(name) { options.push(name); return yargs; },
};
createScenarioCommand(yargs);
assert.deepEqual(positionals, ["output"]);
assert.deepEqual(options, ["scenario", "factorio-version", "plugins"]);
});
});
describe("handleCreateScenarioCommand()", function() {
const factorioDir = path.join(tempDir, "factorio");
const scenario = path.join(tempDir, "scenario");
let hostConfig;
let pluginInfos;
before(async function() {
// Fake install with the test scenario as freeplay
await fs.cp(path.join("test", "file", "factorio", "0.1.2"), factorioDir, { recursive: true });
const freeplay = path.join(factorioDir, "data", "base", "scenarios", "freeplay");
await fs.cp(scenario, freeplay, { recursive: true });
pluginInfos = await lib.loadPluginInfos(new Map([
["research_sync", path.resolve("plugins/research_sync")],
["test_plugin", path.resolve("test/file/test_plugin")],
]));
lib.addPluginConfigFields(pluginInfos);
hostConfig = new lib.HostConfig("host", { "host.factorio_directory": factorioDir });
});
it("should patch freeplay with the enabled plugins by default", async function() {
const output = path.join(tempDir, "default");
await handleCreateScenarioCommand({ output, factorioVersion: "latest" }, hostConfig, pluginInfos);
const files = await readDir(output);
assert.equal(files.get("scenario.lua"), scenarioFiles.get("scenario.lua"));
const info = JSON.parse(files.get("clusterio.json"));
assert.deepEqual(info.modules.map(m => m.name), ["clusterio", "research_sync"]);
});
it("should skip plugins disabled in the host config", async function() {
const output = path.join(tempDir, "disabled");
hostConfig.set("research_sync.load_plugin", false);
try {
await handleCreateScenarioCommand({ output, factorioVersion: "latest" }, hostConfig, pluginInfos);
} finally {
hostConfig.set("research_sync.load_plugin", true);
}
const info = JSON.parse((await readDir(output)).get("clusterio.json"));
assert.deepEqual(info.modules.map(m => m.name), ["clusterio"]);
});
it("should use the given scenario and plugins", async function() {
const output = path.join(tempDir, "selected");
await handleCreateScenarioCommand(
{ output, scenario, plugins: ["research_sync"] }, hostConfig, pluginInfos
);
const info = JSON.parse((await readDir(output)).get("clusterio.json"));
assert.deepEqual(info.modules.map(m => m.name), ["clusterio", "research_sync"]);
});
it("should throw on unknown plugin", async function() {
await assert.rejects(
handleCreateScenarioCommand(
{ output: path.join(tempDir, "unknown_plugin"), scenario, plugins: ["nope"] },
hostConfig,
pluginInfos,
),
new Error("Plugin nope is not in the plugin list")
);
});
it("should throw on invalid Factorio version", async function() {
await assert.rejects(
handleCreateScenarioCommand(
{ output: path.join(tempDir, "bad_version"), factorioVersion: "1..2" }, hostConfig, pluginInfos
),
new Error("Invalid Factorio version 1..2")
);
});
});
});
+29
View File
@@ -240,6 +240,35 @@ describe("host/patch", function() {
assert.deepEqual([...modules.keys()], ["research_sync", "clusterio"]);
assert(modules.get("research_sync").files.has("modules/research_sync/sync.lua"));
});
it("should skip plugins without a module", async function() {
const pluginInfos = await lib.loadPluginInfos(
new Map([["test_plugin", path.resolve("test/file/test_plugin")]])
);
const modules = await patch.loadModules(pluginInfos);
assert.deepEqual([...modules.keys()], ["clusterio"]);
});
it("should throw on a plugin module named like a bundled module", async function() {
const pluginDir = path.join("temp", "test", "clusterio_plugin");
await fs.mkdir(path.join(pluginDir, "module"), { recursive: true });
await fs.writeFile(path.join(pluginDir, "package.json"), "{}");
await fs.writeFile(path.join(pluginDir, "module", "module.json"), "{}");
await assert.rejects(
patch.loadModules([{ name: "clusterio", version: "1.0.0", requirePath: path.resolve(pluginDir) }]),
new Error("Module with name clusterio already exists in a plugin")
);
});
});
describe("class SaveModule", function() {
describe("static fromPlugin()", function() {
it("should load the module of the plugin", async function() {
const [info] = await lib.loadPluginInfos(
new Map([["research_sync", path.resolve("plugins/research_sync")]])
);
const pluginModule = await patch.SaveModule.fromPlugin({ info });
assert.equal(pluginModule.info.name, "research_sync");
});
});
});
describe("patch()", function() {
+13
View File
@@ -257,6 +257,19 @@ describe("Integration of Clusterio", function() {
});
describe("clusteriohost", function() {
describe("create-scenario", function() {
it("should create a patched scenario", async function() {
slowTest(this);
requiresFactorio(this);
const output = path.join("temp", "test", "patched-scenario");
await fs.rm(output, { recursive: true, force: true });
await execHost("create-scenario patched-scenario --plugins research_sync");
const control = await fs.readFile(path.join(output, "control.lua"), "utf8");
assert(control.includes('require("modules/research_sync/sync")'));
await fs.access(path.join(output, "scenario.lua"));
});
});
describe("hostUpdateEventHandler()", function() {
it("should trigger when a new host is added", async function() {
slowTest(this);