mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-15 23:24:52 +09:00
Merge pull request #478 from Hornwitser/mod-management
Add mod pack oriented mod management
This commit is contained in:
61 files changed
+5005
-569
No files matched your search
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "empty_mod",
|
||||
"version": "1.0.0",
|
||||
"title": "An Empty Mod",
|
||||
"author": "Me",
|
||||
"description": "An empty mod for testing",
|
||||
"factorio_version": "1.1",
|
||||
"dependencies": []
|
||||
}
|
||||
@@ -5,6 +5,9 @@ const path = require("path");
|
||||
const events = require("events");
|
||||
const phin = require("phin");
|
||||
|
||||
const libBuildMod = require("@clusterio/lib/build_mod");
|
||||
const libData = require("@clusterio/lib/data");
|
||||
const libHash = require("@clusterio/lib/hash");
|
||||
const libLink = require("@clusterio/lib/link");
|
||||
const libUsers = require("@clusterio/lib/users");
|
||||
const { wait } = require("@clusterio/lib/helpers");
|
||||
@@ -303,13 +306,19 @@ describe("Integration of Clusterio", function() {
|
||||
let exportPath = path.join("temp", "test", "static");
|
||||
await fs.remove(exportPath);
|
||||
await execCtl("instance export-data test");
|
||||
let response = await get("/api/export-manifest");
|
||||
let manifest = JSON.parse(response.body);
|
||||
assert(Object.keys(manifest).length > 1, "Export manifest is empty");
|
||||
assert(
|
||||
await fs.exists(path.join(exportPath, "..", manifest["item-metadata"])),
|
||||
"Export was not written to filesystem"
|
||||
);
|
||||
let result = await libLink.messages.getDefaultModPack.send(getControl());
|
||||
let modPack = new libData.ModPack(result.mod_pack);
|
||||
let assets = modPack.exportManifest.assets;
|
||||
assert(Object.keys(assets).length > 1, "Export assets is empty");
|
||||
for (let key of ["settings", "prototypes", "item-metadata", "item-spritesheet", "locale"]) {
|
||||
assert(assets[key], `Missing ${key} from assets`);
|
||||
assert(
|
||||
await fs.exists(path.join(exportPath, assets[key])),
|
||||
`Manifest entry for ${key} was not written to filesystem`
|
||||
);
|
||||
}
|
||||
let prototypes = JSON.parse(await fs.readFile(path.join(exportPath, assets["prototypes"])));
|
||||
assert(Object.keys(prototypes).length > 50, "Expected there to be more than 50 prototype types");
|
||||
await checkInstanceStatus(44, "stopped");
|
||||
});
|
||||
});
|
||||
@@ -818,6 +827,180 @@ describe("Integration of Clusterio", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("mod-pack create", function() {
|
||||
it("should create a mod-pack", async function() {
|
||||
await execCtl("mod-pack create empty-pack 1.1.0");
|
||||
let response = await libLink.messages.listModPacks.send(getControl());
|
||||
assert(response.list.some(modPack => modPack.name === "empty-pack"), "created pack is not in the list");
|
||||
});
|
||||
it("should allow setting all fields", async function() {
|
||||
await execCtl(
|
||||
"mod-pack create full-pack 0.17.59 " +
|
||||
"--description Description " +
|
||||
"--mods empty_mod:1.0.0 " +
|
||||
"--bool-setting startup MyBool true " +
|
||||
"--int-setting runtime-global MyInt 1235 " +
|
||||
"--double-setting runtime-global MyDouble 12.25 " +
|
||||
"--string-setting runtime-per-user MyString a-string"
|
||||
);
|
||||
let response = await libLink.messages.listModPacks.send(getControl());
|
||||
let modPack = response.list.find(entry => entry.name === "full-pack");
|
||||
assert(modPack, "created mod pack not found");
|
||||
let reference = new libData.ModPack();
|
||||
reference.id = modPack.id;
|
||||
reference.name = "full-pack";
|
||||
reference.description = "Description";
|
||||
reference.factorioVersion = "0.17.59";
|
||||
reference.mods.set("empty_mod", { name: "empty_mod", enabled: true, version: "1.0.0" });
|
||||
reference.settings["startup"].set("MyBool", { value: true });
|
||||
reference.settings["runtime-global"].set("MyInt", { value: 1235 });
|
||||
reference.settings["runtime-global"].set("MyDouble", { value: 12.25 });
|
||||
reference.settings["runtime-per-user"].set("MyString", { value: "a-string" });
|
||||
assert.deepEqual(new libData.ModPack(modPack), reference);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mod-pack list", function() {
|
||||
it("runs", async function() {
|
||||
await execCtl("mod-pack list");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mod-pack show", function() {
|
||||
it("runs", async function() {
|
||||
await execCtl("mod-pack show empty-pack");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mod-pack import/export", function() {
|
||||
it("should should roundtrip a mod-pack", async function() {
|
||||
let reference = new libData.ModPack();
|
||||
reference.name = "imported-pack";
|
||||
reference.description = "Description";
|
||||
reference.factorioVersion = "0.17.59";
|
||||
reference.mods.set("empty_mod", { name: "empty_mod", enabled: true, version: "1.0.0" });
|
||||
reference.settings["startup"].set("MyBool", { value: true });
|
||||
reference.settings["runtime-global"].set("MyInt", { value: 1235 });
|
||||
reference.settings["runtime-global"].set("MyDouble", { value: 12.25 });
|
||||
reference.settings["runtime-per-user"].set("MyString", { value: "a-string" });
|
||||
await execCtl(`mod-pack import ${reference.toModPackString()}`);
|
||||
const result = await execCtl("mod-pack export imported-pack");
|
||||
const roundtrip = libData.ModPack.fromModPackString(result.stdout.trim());
|
||||
roundtrip.id = reference.id;
|
||||
assert.deepEqual(roundtrip, reference);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mod-pack edit", function() {
|
||||
it("runs", async function() {
|
||||
await execCtl("mod-pack edit full-pack --factorio-version 1.2.0");
|
||||
let response = await libLink.messages.listModPacks.send(getControl());
|
||||
let modPack = response.list.find(entry => entry.name === "full-pack");
|
||||
assert(modPack, "created mod pack not found");
|
||||
assert.equal(modPack.factorio_version, "1.2.0");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mod-pack delete", function() {
|
||||
it("deletes the pack", async function() {
|
||||
await execCtl("mod-pack delete full-pack");
|
||||
let response = await libLink.messages.listModPacks.send(getControl());
|
||||
let modPack = response.list.find(entry => entry.name === "full-pack");
|
||||
assert(!modPack, "mod pack not deleted");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mod upload", function() {
|
||||
it("uploads a mod", async function() {
|
||||
await libBuildMod.build({
|
||||
build: true,
|
||||
pack: true,
|
||||
sourceDir: path.join("test", "file", "empty_mod"),
|
||||
outputDir: path.join("temp", "test"),
|
||||
});
|
||||
await execCtl("mod upload empty_mod_1.0.0.zip");
|
||||
assert(
|
||||
await fs.pathExists(path.join("temp", "test", "mods", "empty_mod_1.0.0.zip")),
|
||||
"mod not present in mods directory"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mod show", function() {
|
||||
it("gives details of a mod", async function() {
|
||||
let result = await execCtl("mod show empty_mod 1.0.0");
|
||||
let hash = await libHash.hashFile(path.join("temp", "test", "mods", "empty_mod_1.0.0.zip"));
|
||||
let stat = await fs.stat(path.join("temp", "test", "mods", "empty_mod_1.0.0.zip"));
|
||||
assert.equal(
|
||||
result.stdout,
|
||||
"name: empty_mod\n" +
|
||||
"version: 1.0.0\n" +
|
||||
"title: An Empty Mod\n" +
|
||||
"author: Me\n" +
|
||||
"description: An empty mod for testing\n" +
|
||||
"factorio_version: 1.1\n" +
|
||||
"dependencies:\n" +
|
||||
`size: ${stat.size}\n` +
|
||||
`sha1: ${hash}\n`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mod list", function() {
|
||||
it("shows the list of mods", async function() {
|
||||
let result = await execCtl("mod list");
|
||||
assert(result.stdout.indexOf("empty_mod") !== -1, "empty_mod is not in the list");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mod search", function() {
|
||||
it("searches the list of mods", async function() {
|
||||
let result = await execCtl("mod search 1.1 name:empty_mod");
|
||||
assert(result.stdout.indexOf("empty_mod") !== -1, "empty_mod is not in the result");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mod download", function() {
|
||||
it("downloads a mod", async function() {
|
||||
await fs.unlink(path.join("temp", "test", "empty_mod_1.0.0.zip"));
|
||||
await execCtl("mod download empty_mod 1.0.0");
|
||||
assert(
|
||||
await fs.pathExists(path.join("temp", "test", "empty_mod_1.0.0.zip")),
|
||||
"mod not downloaded to cwd"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mod delete", function() {
|
||||
it("deletes a mod", async function() {
|
||||
await execCtl("mod delete empty_mod 1.0.0");
|
||||
assert(
|
||||
!await fs.pathExists(path.join("temp", "test", "mods", "empty_mod_1.0.0.zip")),
|
||||
"mod still present in mods dir"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("modUpdateEventHandler()", function() {
|
||||
it("should have triggered for the previous mod updates", function() {
|
||||
let eventsToCheck = new Set(["updated", "deleted"]);
|
||||
let eventsNotSeen = new Set(eventsToCheck);
|
||||
|
||||
for (let modUpdate of getControl().modUpdates) {
|
||||
if (modUpdate.name !== "empty_mod" || modUpdate.version !== "1.0.0") {
|
||||
continue;
|
||||
}
|
||||
if (modUpdate.is_deleted) {
|
||||
eventsNotSeen.delete("deleted");
|
||||
} else {
|
||||
eventsNotSeen.delete("updated");
|
||||
}
|
||||
}
|
||||
|
||||
assert(eventsNotSeen.size === 0, `Did not see the events ${[...eventsNotSeen]}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("permission list", function() {
|
||||
it("runs", async function() {
|
||||
await execCtl("permission list");
|
||||
|
||||
@@ -8,6 +8,7 @@ const phin = require("phin");
|
||||
const util = require("util");
|
||||
const events = require("events");
|
||||
|
||||
const libData = require("@clusterio/lib/data");
|
||||
const libLink = require("@clusterio/lib/link");
|
||||
const { LineSplitter } = require("@clusterio/lib/stream");
|
||||
const { ConsoleTransport, logger } = require("@clusterio/lib/logging");
|
||||
@@ -28,6 +29,8 @@ class TestControl extends libLink.Link {
|
||||
this.slaveUpdates = [];
|
||||
this.instanceUpdates = [];
|
||||
this.saveListUpdates = [];
|
||||
this.modUpdates = [];
|
||||
this.modPackUpdates = [];
|
||||
this.userUpdates = [];
|
||||
|
||||
this.connector.on("connect", () => {
|
||||
@@ -40,6 +43,9 @@ class TestControl extends libLink.Link {
|
||||
libLink.messages.setSaveListSubscriptions.send(
|
||||
this, { all: true, instance_ids: [] }
|
||||
).catch(err => logger.error(`Error setting save list subscriptions:\n${err.stack}`));
|
||||
libLink.messages.setModSubscriptions.send(
|
||||
this, { all: true, mod_names: [] }
|
||||
).catch(err => logger.error(`Error setting mod subscriptions:\n${err.stack}`));
|
||||
libLink.messages.setUserSubscriptions.send(
|
||||
this, { all: true, names: [] }
|
||||
).catch(err => logger.error(`Error setting user subscriptions:\n${err.stack}`));
|
||||
@@ -67,6 +73,14 @@ class TestControl extends libLink.Link {
|
||||
this.saveListUpdates.push(message.data);
|
||||
}
|
||||
|
||||
async modUpdateEventHandler(message) {
|
||||
this.modUpdates.push(message.data.mod);
|
||||
}
|
||||
|
||||
async modPackUpdateEventHandler(message) {
|
||||
this.modPackUpdates.push(message.data.mod_pack);
|
||||
}
|
||||
|
||||
async userUpdateEventHandler(message) {
|
||||
this.userUpdates.push(message.data);
|
||||
}
|
||||
@@ -207,6 +221,15 @@ before(async function() {
|
||||
controlConnector.token = controlToken;
|
||||
control = new TestControl(controlConnector);
|
||||
await controlConnector.connect();
|
||||
|
||||
const testPack = new libData.ModPack();
|
||||
testPack.id = 12;
|
||||
testPack.name = "subspace_storage-pack";
|
||||
testPack.factorioVersion = "1.1.0";
|
||||
testPack.mods.set("clusterio_lib", { name: "clusterio_lib", enabled: true, version: "0.1.2" });
|
||||
testPack.mods.set("subspace_storage", { name: "subspace_storage", enabled: true, version: "1.99.8" });
|
||||
await libLink.messages.createModPack.send(control, { mod_pack: testPack.toJSON() });
|
||||
await libLink.messages.setMasterConfigField.send(control, { field: "master.default_mod_pack_id", value: "12" });
|
||||
});
|
||||
|
||||
after(async function() {
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
"use strict";
|
||||
const assert = require("assert").strict;
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
|
||||
const libSchema = require("@clusterio/lib/schema");
|
||||
const libHash = require("@clusterio/lib/hash");
|
||||
const libBuildMod = require("@clusterio/lib/build_mod");
|
||||
const ModInfo = require("@clusterio/lib/data/ModInfo");
|
||||
|
||||
|
||||
describe("lib/data/ModInfo", function() {
|
||||
describe("class ModInfo", function() {
|
||||
it("should round trip serialize", function() {
|
||||
const validate = libSchema.compile(ModInfo.jsonSchema);
|
||||
function check(pack) {
|
||||
const json = JSON.parse(JSON.stringify(pack));
|
||||
if (!validate(json)) {
|
||||
throw validate.errors;
|
||||
}
|
||||
assert.deepEqual(new ModInfo(json), pack);
|
||||
}
|
||||
|
||||
check(new ModInfo());
|
||||
check(new ModInfo({ name: "MyMod" }));
|
||||
check(new ModInfo({ version: "1.0.0" }));
|
||||
check(new ModInfo({ title: "My Extravagent Mod" }));
|
||||
check(new ModInfo({ author: "Me" }));
|
||||
check(new ModInfo({ contact: "Use my email" }));
|
||||
check(new ModInfo({ homepage: "It's over there" }));
|
||||
check(new ModInfo({ description: "My Description" }));
|
||||
check(new ModInfo({ factorio_version: "2.0" }));
|
||||
check(new ModInfo({ dependencies: [] }));
|
||||
check(new ModInfo({ dependencies: ["UltraMod", "SuperLib >= 1.00", "! bad-mod"] }));
|
||||
check(new ModInfo({ filename: "MyMod_1.0.0.zip" }));
|
||||
check(new ModInfo({ size: 1024 }));
|
||||
check(new ModInfo({ sha1: "verified-as-MyMod" }));
|
||||
check(new ModInfo({ is_deleted: true }));
|
||||
|
||||
// All at once
|
||||
check(new ModInfo({
|
||||
name: "MyMod",
|
||||
version: "1.0.0",
|
||||
title: "My Extravagent Mod",
|
||||
author: "Me",
|
||||
contact: "Use my email",
|
||||
homepage: "It's over there",
|
||||
description: "My Description",
|
||||
factorio_version: "2.0",
|
||||
dependencies: ["UltraMod", "SuperLib >= 1.00", "! bad-mod"],
|
||||
filename: "MyMod_1.0.0.zip",
|
||||
size: 1024,
|
||||
sha1: "verified-as-MyMod",
|
||||
is_deleted: true,
|
||||
}));
|
||||
});
|
||||
|
||||
it("should sort integer mod versions lexicographically", function() {
|
||||
let unsortedVersions = ["1.0.0", "1.1.0", "0.1.0", "3.0.0", "1.2.0", "0.3.1", "0.3.3", "2.1.1", "0.0.1"];
|
||||
let sortedVersions = ["0.0.1", "0.1.0", "0.3.1", "0.3.3", "1.0.0", "1.1.0", "1.2.0", "2.1.1", "3.0.0"];
|
||||
let mods = unsortedVersions.map(v => new ModInfo({ version: v }));
|
||||
mods.sort((a, b) => a.integerVersion - b.integerVersion);
|
||||
assert.deepEqual(mods.map(mod => mod.version), sortedVersions);
|
||||
});
|
||||
it("should sort integer factorio versions lexicographically", function() {
|
||||
let unsortedVersions = ["1.0", "1.1", "0.1", "3.0", "1.2", "0.3", "2.1"];
|
||||
let sortedVersions = ["0.1", "0.3", "1.0", "1.1", "1.2", "2.1", "3.0"];
|
||||
let factorioMods = unsortedVersions.map(v => new ModInfo({ factorio_version: v }));
|
||||
factorioMods.sort((a, b) => a.integerFactorioVersion - b.integerFactorioVersion);
|
||||
assert.deepEqual(factorioMods.map(mod => mod.factorioVersion), sortedVersions);
|
||||
});
|
||||
|
||||
it("should load from a mod zip file", async function() {
|
||||
await libBuildMod.build({
|
||||
build: true,
|
||||
pack: true,
|
||||
sourceDir: path.join("test", "file", "empty_mod"),
|
||||
outputDir: path.join("temp", "test"),
|
||||
});
|
||||
const filePath = path.join("temp", "test", "empty_mod_1.0.0.zip");
|
||||
const hash = await libHash.hashFile(filePath);
|
||||
const stat = await fs.stat(filePath);
|
||||
const mod = await ModInfo.fromModFile(filePath);
|
||||
assert.deepEqual(
|
||||
mod,
|
||||
new ModInfo({
|
||||
name: "empty_mod",
|
||||
version: "1.0.0",
|
||||
title: "An Empty Mod",
|
||||
author: "Me",
|
||||
description: "An empty mod for testing",
|
||||
factorio_version: "1.1",
|
||||
dependencies: [],
|
||||
filename: "empty_mod_1.0.0.zip",
|
||||
size: stat.size,
|
||||
sha1: hash,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,267 @@
|
||||
"use strict";
|
||||
const assert = require("assert").strict;
|
||||
const zlib = require("zlib");
|
||||
|
||||
const libSchema = require("@clusterio/lib/schema");
|
||||
const ExportManifest = require("@clusterio/lib/data/ExportManifest");
|
||||
const ModPack = require("@clusterio/lib/data/ModPack");
|
||||
|
||||
|
||||
describe("lib/data/ModPack", function() {
|
||||
describe("class ModPack", function() {
|
||||
it("should round trip serialize", function() {
|
||||
const validate = libSchema.compile(ModPack.jsonSchema);
|
||||
function check(pack) {
|
||||
const json = JSON.parse(JSON.stringify(pack));
|
||||
if (!validate(json)) {
|
||||
throw validate.errors;
|
||||
}
|
||||
assert.deepEqual(new ModPack(json), pack);
|
||||
const packStringed = ModPack.fromModPackString(pack.toModPackString());
|
||||
packStringed.id = pack.id;
|
||||
packStringed.exportManifest = pack.exportManifest;
|
||||
assert.deepEqual(packStringed, pack);
|
||||
}
|
||||
|
||||
check(new ModPack());
|
||||
check(new ModPack({ name: "MyPack" }));
|
||||
check(new ModPack({ description: "My Description" }));
|
||||
check(new ModPack({ factorio_version: "2.0" }));
|
||||
check(new ModPack({ mods: [
|
||||
{ name: "subspace_storage", enabled: true, version: "1.99.8" },
|
||||
{ name: "clusterio_lib", enabled: true, version: "0.1.2", sha1: "012345abcd" },
|
||||
]}));
|
||||
check(new ModPack({ settings: {
|
||||
"startup": {
|
||||
"bool-setting": { "value": true },
|
||||
},
|
||||
"runtime-global": {
|
||||
"number-setting": { "value": 123 },
|
||||
},
|
||||
"runtime-per-user": {
|
||||
"string-setting": { "value": "a string" },
|
||||
},
|
||||
}}));
|
||||
check(new ModPack({ export_manifest: { assets: { setting: "settings.json" }}}));
|
||||
check(new ModPack({ deleted: true }));
|
||||
check(new ModPack({
|
||||
name: "Super pack",
|
||||
description: "Every option at once.",
|
||||
factorio_version: "2.0",
|
||||
mods: [
|
||||
{ name: "subspace_storage", enabled: true, version: "1.99.8" },
|
||||
{ name: "clusterio_lib", enabled: true, version: "0.1.2", sha1: "012345abcd" },
|
||||
],
|
||||
settings: {
|
||||
"startup": {
|
||||
"bool-setting": { "value": true },
|
||||
},
|
||||
"runtime-global": {
|
||||
"number-setting": { "value": 123 },
|
||||
},
|
||||
"runtime-per-user": {
|
||||
"string-setting": { "value": "a string" },
|
||||
},
|
||||
},
|
||||
export_manifest: { assets: { setting: "settings.json" }},
|
||||
deleted: true,
|
||||
}));
|
||||
});
|
||||
|
||||
it("should sort integer factorio versions lexicographically", function() {
|
||||
let unsortedVersions = ["1.0", "1.1.0", "0.1", "3.0.0", "1.2", "0.3.1", "0.3.3", "2.1.1", "0.0.1"];
|
||||
let sortedVersions = ["0.0.1", "0.1", "0.3.1", "0.3.3", "1.0", "1.1.0", "1.2", "2.1.1", "3.0.0"];
|
||||
let factorioMods = unsortedVersions.map(v => new ModPack({ factorio_version: v }));
|
||||
factorioMods.sort((a, b) => a.integerFactorioVersion - b.integerFactorioVersion);
|
||||
assert.deepEqual(factorioMods.map(mod => mod.factorioVersion), sortedVersions);
|
||||
});
|
||||
|
||||
describe(".fillDefaultSettings()", function() {
|
||||
const prototypes = {
|
||||
"bool-setting": {
|
||||
"bool": {
|
||||
name: "bool",
|
||||
type: "bool-setting",
|
||||
setting_type: "startup",
|
||||
default_value: true,
|
||||
},
|
||||
},
|
||||
"double-setting": {
|
||||
"number": {
|
||||
name: "number",
|
||||
type: "double-setting",
|
||||
setting_type: "runtime-global",
|
||||
default_value: 123,
|
||||
},
|
||||
},
|
||||
"string-setting": {
|
||||
"string": {
|
||||
name: "string",
|
||||
type: "string-setting",
|
||||
setting_type: "runtime-per-user",
|
||||
default_value: "a string",
|
||||
},
|
||||
},
|
||||
};
|
||||
const mockLogger = { warn: () => {} };
|
||||
it("should fill in defaults for settings", function() {
|
||||
const pack = new ModPack();
|
||||
pack.fillDefaultSettings(prototypes, mockLogger);
|
||||
assert.deepEqual(pack.toJSON().settings, {
|
||||
"startup": {
|
||||
"bool": { "value": true },
|
||||
},
|
||||
"runtime-global": {
|
||||
"number": { "value": 123 },
|
||||
},
|
||||
"runtime-per-user": {
|
||||
"string": { "value": "a string" },
|
||||
},
|
||||
});
|
||||
});
|
||||
it("should not overwrite existing values", function() {
|
||||
const pack = new ModPack({ settings: {
|
||||
"startup": {
|
||||
"bool": { "value": false },
|
||||
},
|
||||
"runtime-global": {
|
||||
"number": { "value": 2 },
|
||||
},
|
||||
"runtime-per-user": {
|
||||
"string": { "value": "spam" },
|
||||
},
|
||||
}});
|
||||
pack.fillDefaultSettings(prototypes, mockLogger);
|
||||
assert.deepEqual(pack.toJSON().settings, {
|
||||
"startup": {
|
||||
"bool": { "value": false },
|
||||
},
|
||||
"runtime-global": {
|
||||
"number": { "value": 2 },
|
||||
},
|
||||
"runtime-per-user": {
|
||||
"string": { "value": "spam" },
|
||||
},
|
||||
});
|
||||
});
|
||||
it("should ignore unknown setting_type and missing default_value", function() {
|
||||
const pack = new ModPack();
|
||||
pack.fillDefaultSettings({
|
||||
"string-setting": {
|
||||
"foo": {
|
||||
name: "foo",
|
||||
type: "string-setting",
|
||||
setting_type: "magic-that-does-not-exist",
|
||||
default_value: "a string",
|
||||
},
|
||||
"bar": {
|
||||
name: "bar",
|
||||
type: "string-setting",
|
||||
setting_type: "startup",
|
||||
},
|
||||
},
|
||||
}, mockLogger);
|
||||
assert.deepEqual(pack.toJSON().settings, {
|
||||
"startup": {},
|
||||
"runtime-global": {},
|
||||
"runtime-per-user": {},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(".fromModPackString()", function() {
|
||||
it("should handle malformed strings", function() {
|
||||
assert.throws(
|
||||
() => ModPack.fromModPackString("AMalformedString"),
|
||||
new Error("Malformed mod pack string: zlib inflate failed")
|
||||
);
|
||||
let badJsonMsg;
|
||||
try {
|
||||
JSON.parse("Not Json");
|
||||
} catch (err) {
|
||||
badJsonMsg = err.message;
|
||||
}
|
||||
assert.throws(
|
||||
// eslint-disable-next-line node/no-sync
|
||||
() => ModPack.fromModPackString(Buffer.from(zlib.deflateSync("Not Json")).toString("base64")),
|
||||
new Error(`Malformed mod pack string: ${badJsonMsg}`)
|
||||
);
|
||||
assert.throws(
|
||||
// eslint-disable-next-line node/no-sync
|
||||
() => ModPack.fromModPackString(Buffer.from(zlib.deflateSync('{"i":1}')).toString("base64")),
|
||||
new Error("Malformed mod pack string: Schema validation failed")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("toModSettingsDat()", function() {
|
||||
it("should properly serialise settings", function() {
|
||||
const pack = new ModPack({ settings: {
|
||||
"startup": {
|
||||
"bool-setting": { "value": true },
|
||||
},
|
||||
"runtime-global": {
|
||||
"number-setting": { "value": 123 },
|
||||
},
|
||||
"runtime-per-user": {
|
||||
"string-setting": { "value": "a string" },
|
||||
},
|
||||
}});
|
||||
|
||||
function istr(str) {
|
||||
return [
|
||||
Uint8Array.from([0, str.length]),
|
||||
Buffer.from(str),
|
||||
];
|
||||
}
|
||||
/* eslint-disable indent */
|
||||
assert.deepEqual(
|
||||
pack.toModSettingsDat(),
|
||||
Buffer.concat([
|
||||
new Uint8Array(Uint16Array.from([1, 1, 0, 0]).buffer), // version
|
||||
Uint8Array.from([0]), // reserved
|
||||
|
||||
Uint8Array.from([5, 0]), // dictionary
|
||||
new Uint8Array(Uint32Array.from([3]).buffer), // items
|
||||
|
||||
...istr("startup"), // name
|
||||
Uint8Array.from([5, 0]), // dictionary
|
||||
new Uint8Array(Uint32Array.from([1]).buffer), // items
|
||||
|
||||
...istr("bool-setting"), // name
|
||||
Uint8Array.from([5, 0]), // dictinary
|
||||
new Uint8Array(Uint32Array.from([1]).buffer), // items
|
||||
|
||||
...istr("value"), // name
|
||||
Uint8Array.from([1, 0, 1]), // boolean
|
||||
|
||||
...istr("runtime-global"), // name
|
||||
Uint8Array.from([5, 0]), // dictionary
|
||||
new Uint8Array(Uint32Array.from([1]).buffer), // items
|
||||
|
||||
...istr("number-setting"), // name
|
||||
Uint8Array.from([5, 0]), // dictinary
|
||||
new Uint8Array(Uint32Array.from([1]).buffer), // items
|
||||
|
||||
...istr("value"), // name
|
||||
Uint8Array.from([2, 0]), // number
|
||||
new Uint8Array(Float64Array.from([123]).buffer),
|
||||
|
||||
...istr("runtime-per-user"), // name
|
||||
Uint8Array.from([5, 0]), // dictionary
|
||||
new Uint8Array(Uint32Array.from([1]).buffer), // items
|
||||
|
||||
...istr("string-setting"), // name
|
||||
Uint8Array.from([5, 0]), // dictinary
|
||||
new Uint8Array(Uint32Array.from([1]).buffer), // items
|
||||
|
||||
...istr("value"), // name
|
||||
Uint8Array.from([3, 0, 0, "a string".length]), // string
|
||||
Buffer.from("a string"),
|
||||
])
|
||||
);
|
||||
/* eslint-enable indent */
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,47 +8,6 @@ const libFactorio = require("@clusterio/lib/factorio");
|
||||
|
||||
|
||||
describe("lib/factorio", function() {
|
||||
describe("findRoot()", function() {
|
||||
it("should find the root of a zip", function() {
|
||||
let zip = new JSZip();
|
||||
zip.file("root/foo.txt", "");
|
||||
zip.file("root/bar.txt", "");
|
||||
|
||||
assert.equal(libFactorio._findRoot(zip), "root");
|
||||
});
|
||||
|
||||
it("should throw if files are at the root of the zip", function() {
|
||||
let zip = new JSZip();
|
||||
zip.file("root/foo.txt", "");
|
||||
zip.file("bar.txt", "");
|
||||
|
||||
assert.throws(
|
||||
() => libFactorio._findRoot(zip),
|
||||
new Error("Zip contains file 'bar.txt' in root dir")
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw if there are multiple root dirs", function() {
|
||||
let zip = new JSZip();
|
||||
zip.file("root-1/foo.txt", "");
|
||||
zip.file("root-2/bar.txt", "");
|
||||
|
||||
assert.throws(
|
||||
() => libFactorio._findRoot(zip),
|
||||
new Error("Zip contains multiple root folders")
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw if given an empty zip file", function() {
|
||||
let zip = new JSZip();
|
||||
|
||||
assert.throws(
|
||||
() => libFactorio._findRoot(zip),
|
||||
new Error("Empty zip file")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("generateLoader()", function() {
|
||||
let reference = [
|
||||
"-- Auto generated scenario module loader created by Clusterio",
|
||||
|
||||
@@ -84,4 +84,109 @@ describe("lib/file_ops", function() {
|
||||
assert.equal(await fs.readFile(target, "utf8"), "current");
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkFilename()", function() {
|
||||
it("should allow a basic name", function() {
|
||||
libFileOps.checkFilename("file");
|
||||
});
|
||||
|
||||
function check(item, msg) {
|
||||
assert.throws(() => libFileOps.checkFilename(item), new Error(msg));
|
||||
}
|
||||
|
||||
it("should throw on non-string", function() {
|
||||
check(undefined, "must be a string");
|
||||
check(null, "must be a string");
|
||||
check({}, "must be a string");
|
||||
check([], "must be a string");
|
||||
check(0, "must be a string");
|
||||
check(false, "must be a string");
|
||||
});
|
||||
|
||||
it("should throw on empty name", function() {
|
||||
check("", "cannot be empty");
|
||||
});
|
||||
|
||||
it("should throw on <>:\"\\/|?* \\x00\\r\\n\\t", function() {
|
||||
for (let char of '<>:"\\/|?*\x00\r\n\t') {
|
||||
check(char, 'cannot contain <>:"\\/|=* or control characters');
|
||||
}
|
||||
});
|
||||
|
||||
it("should throw on CON, PRN, AUX, NUL, COM1, LPT1", function() {
|
||||
for (let bad of ["CON", "PRN", "AUX", "NUL", "COM1", "LPT1"]) {
|
||||
check(bad, "cannot be named any of CON PRN AUX NUL COM1-9 and LPT1-9");
|
||||
check(`${bad}.zip`, "cannot be named any of CON PRN AUX NUL COM1-9 and LPT1-9");
|
||||
check(`${bad}.anything.txt`, "cannot be named any of CON PRN AUX NUL COM1-9 and LPT1-9");
|
||||
check(`${bad}....a`, "cannot be named any of CON PRN AUX NUL COM1-9 and LPT1-9");
|
||||
check(`${bad}.`, "cannot be named any of CON PRN AUX NUL COM1-9 and LPT1-9");
|
||||
}
|
||||
});
|
||||
|
||||
it("should throw on . and ..", function() {
|
||||
for (let bad of [".", ".."]) {
|
||||
check(bad, `cannot be named ${bad}`);
|
||||
}
|
||||
});
|
||||
|
||||
it("should throw on names ending with . or space", function() {
|
||||
check("a ", "cannot end with . or space");
|
||||
check("a.", "cannot end with . or space");
|
||||
});
|
||||
});
|
||||
|
||||
describe("cleanFilename()", function() {
|
||||
function clean(item, expected) {
|
||||
assert.equal(libFileOps.cleanFilename(item), expected);
|
||||
}
|
||||
|
||||
it("should allow a basic name", function() {
|
||||
clean("file", "file");
|
||||
});
|
||||
|
||||
function check(item, msg) {
|
||||
assert.throws(() => libFileOps.cleanFilename(item), new Error(msg));
|
||||
}
|
||||
|
||||
it("should throw on non-string", function() {
|
||||
check(undefined, "name must be a string");
|
||||
check(null, "name must be a string");
|
||||
check({}, "name must be a string");
|
||||
check([], "name must be a string");
|
||||
check(0, "name must be a string");
|
||||
check(false, "name must be a string");
|
||||
});
|
||||
|
||||
it("should clean empty name", function() {
|
||||
clean("", "_");
|
||||
});
|
||||
|
||||
it("should clean <>:\"\\/|?* \\x00\\r\\n\\t", function() {
|
||||
for (let char of '<>:"\\/|?*\x00\r\n\t') {
|
||||
clean(char, "_");
|
||||
}
|
||||
});
|
||||
|
||||
it("should clean CON, PRN, AUX, NUL, COM1, LPT1", function() {
|
||||
for (let bad of ["CON", "PRN", "AUX", "NUL", "COM1", "LPT1"]) {
|
||||
clean(bad, `${bad}_`);
|
||||
clean(`${bad}.zip`, `${bad}_.zip`);
|
||||
clean(`${bad}.anything.txt`, `${bad}_.anything.txt`);
|
||||
clean(`${bad}....a`, `${bad}_....a`);
|
||||
clean(`${bad}.`, `${bad}__`);
|
||||
}
|
||||
});
|
||||
|
||||
it("should clean . and ..", function() {
|
||||
for (let bad of [".", ".."]) {
|
||||
clean(bad, `${bad}_`);
|
||||
}
|
||||
});
|
||||
|
||||
it("should clean names ending with . or space", function() {
|
||||
clean("a ", "a_");
|
||||
clean("a.", "a_");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -43,4 +43,113 @@ describe("lib/helpers", function() {
|
||||
), "timeout");
|
||||
});
|
||||
});
|
||||
|
||||
function parse(input, attributes) {
|
||||
const result = libHelpers.parseSearchString(input, attributes);
|
||||
if (!result.issues.length) {
|
||||
return result.terms;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
describe("parseSearchString()", function() {
|
||||
function word(value, opts = {}) {
|
||||
return { type: "word", value, ...opts };
|
||||
}
|
||||
function attr(name, value) {
|
||||
return { type: "attribute", name, value: typeof value === "string" ? word(value) : value };
|
||||
}
|
||||
it("should split words", function() {
|
||||
assert.deepEqual(parse(""), [].map(word));
|
||||
assert.deepEqual(parse("word"), ["word"].map(word));
|
||||
assert.deepEqual(parse("some words here"), ["some", "words", "here"].map(word));
|
||||
});
|
||||
it("should trim whitespace", function() {
|
||||
assert.deepEqual(parse(" "), [].map(word));
|
||||
assert.deepEqual(parse("word "), ["word"].map(word));
|
||||
assert.deepEqual(parse(" some words here"), ["some", "words", "here"].map(word));
|
||||
});
|
||||
it("should parse negated words", function() {
|
||||
assert.deepEqual(parse("this -that"), [word("this"), word("that", { exclude: true })]);
|
||||
assert.deepEqual(parse("not-negated"), [word("not-negated")]);
|
||||
});
|
||||
it("should not split quoted words", function() {
|
||||
assert.deepEqual(parse('"some quoted words" here'), ["some quoted words", "here"].map(word));
|
||||
assert.deepEqual(parse('"a quoted string"'), ["a quoted string"].map(word));
|
||||
assert.deepEqual(parse('words and "a quoted string"'), ["words", "and", "a quoted string"].map(word));
|
||||
});
|
||||
it("should split on quotation mark", function() {
|
||||
assert.deepEqual(parse('"quoted"unquoted'), ["quoted", "unquoted"].map(word));
|
||||
assert.deepEqual(parse('"quoted""double"'), ["quoted", "double"].map(word));
|
||||
assert.deepEqual(parse('word"with"quote'), ["word", "with", "quote"].map(word));
|
||||
assert.deepEqual(
|
||||
parse('word-"with"-negation'),
|
||||
[word("word-"), word("with"), word("negation", { exclude: true })]
|
||||
);
|
||||
});
|
||||
it("should allow colons in quotes", function() {
|
||||
assert.deepEqual(parse('"word:value"'), ["word:value"].map(word));
|
||||
});
|
||||
it("should parse attribute:value", function() {
|
||||
assert.deepEqual(parse("author:Me", { author: "word" }), [attr("author", "Me")]);
|
||||
assert.deepEqual(parse("word author:Me", { author: "word" }), [word("word"), attr("author", "Me")]);
|
||||
assert.deepEqual(
|
||||
parse("author:Me and author:You", { author: "word" }),
|
||||
[attr("author", "Me"), word("and"), attr("author", "You")]
|
||||
);
|
||||
assert.deepEqual(
|
||||
parse("author:Me"),
|
||||
{
|
||||
terms: [word("Me")],
|
||||
issues: ['Unregonized attribute "author", use quotes to escape colons'],
|
||||
}
|
||||
);
|
||||
assert.deepEqual(
|
||||
parse("constructor:value"),
|
||||
{
|
||||
terms: [word("value")],
|
||||
issues: ['Unregonized attribute "constructor", use quotes to escape colons'],
|
||||
}
|
||||
);
|
||||
});
|
||||
it("should throw on bad attribute format", function() {
|
||||
assert.throws(
|
||||
() => parse("author:Me", { author: "bad" }),
|
||||
new Error("Bad attribute format bad for author")
|
||||
);
|
||||
});
|
||||
it("should ignore attribute case", function() {
|
||||
assert.deepEqual(parse("Author:Me", { author: "word" }), [attr("author", "Me")]);
|
||||
assert.deepEqual(parse("AUTHOR:Me", { author: "word" }), [attr("author", "Me")]);
|
||||
assert.deepEqual(parse("auThOR:Me", { author: "word" }), [attr("author", "Me")]);
|
||||
});
|
||||
it("should parse partial inputs", function() {
|
||||
assert.deepEqual(parse("-"), [word("", { exclude: true })]);
|
||||
assert.deepEqual(parse('"'), [""].map(word));
|
||||
assert.deepEqual(parse('"word'), ["word"].map(word));
|
||||
assert.deepEqual(parse("author:", { author: "word" }), [attr("author", "")]);
|
||||
assert.deepEqual(parse("author:-", { author: "word" }), [attr("author", word("", { exclude: true }))]);
|
||||
});
|
||||
});
|
||||
describe("wordMatches()", function() {
|
||||
const match = libHelpers.wordMatches;
|
||||
it("should match any inputs", function() {
|
||||
assert(match(parse("word")[0], "a word in a string"), "one text");
|
||||
assert(!match(parse("word")[0], "a string without"), "one text no match");
|
||||
assert(match(parse("word")[0], "no match", "no things", "a word", "bad text", ""), "five text");
|
||||
assert(!match(parse("unword")[0], "no match", "no things", "a word", "bad text", ""), "five text no match");
|
||||
});
|
||||
it("should invert match if using exclude", function() {
|
||||
assert(!match(parse("-word")[0], "a word in a string"), "one text");
|
||||
assert(match(parse("-word")[0], "a string without"), "one text no match");
|
||||
assert(!match(parse("-word")[0], "no match", "no things", "a word", "bad text", ""), "five text");
|
||||
assert(match(parse("-unword")[0], "no match", "no things", "a word", "bad text", ""), "five text no match");
|
||||
});
|
||||
it("should throw on bad input", function() {
|
||||
assert.throws(
|
||||
() => match(parse("attr:value", { attr: "word" })[0], "a string"),
|
||||
new Error("wordMatches: parameter is not a word"),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -45,9 +45,16 @@ describe("lib/plugin_loader", function() {
|
||||
);
|
||||
});
|
||||
it("should reject on broken plugin", async function() {
|
||||
let brokenMessage;
|
||||
try {
|
||||
// eslint-disable-next-line node/global-require
|
||||
require(path.resolve(brokenPlugin, "info.js"));
|
||||
} catch (err) {
|
||||
brokenMessage = err.message;
|
||||
}
|
||||
await assert.rejects(
|
||||
libPluginLoader.loadPluginInfos(new Map([["broken", path.resolve(brokenPlugin)]])),
|
||||
{ message: "PluginError: Unexpected identifier" }
|
||||
{ message: `PluginError: ${brokenMessage}` }
|
||||
);
|
||||
});
|
||||
it("should reject on invalid plugin", async function() {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
"use strict";
|
||||
const assert = require("assert").strict;
|
||||
const JSZip = require("jszip");
|
||||
|
||||
const { findRoot } = require("@clusterio/lib/zip_ops");
|
||||
|
||||
|
||||
describe("lib/factorio", function() {
|
||||
describe("findRoot()", function() {
|
||||
it("should find the root of a zip", function() {
|
||||
let zip = new JSZip();
|
||||
zip.file("root/foo.txt", "");
|
||||
zip.file("root/bar.txt", "");
|
||||
|
||||
assert.equal(findRoot(zip), "root");
|
||||
});
|
||||
|
||||
it("should throw if files are at the root of the zip", function() {
|
||||
let zip = new JSZip();
|
||||
zip.file("root/foo.txt", "");
|
||||
zip.file("bar.txt", "");
|
||||
|
||||
assert.throws(
|
||||
() => findRoot(zip),
|
||||
new Error("Zip contains file 'bar.txt' in root dir")
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw if there are multiple root dirs", function() {
|
||||
let zip = new JSZip();
|
||||
zip.file("root-1/foo.txt", "");
|
||||
zip.file("root-2/bar.txt", "");
|
||||
|
||||
assert.throws(
|
||||
() => findRoot(zip),
|
||||
new Error("Zip contains multiple root folders")
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw if given an empty zip file", function() {
|
||||
let zip = new JSZip();
|
||||
|
||||
assert.throws(
|
||||
() => findRoot(zip),
|
||||
new Error("Empty zip file")
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
-163
@@ -1,11 +1,8 @@
|
||||
"use strict";
|
||||
const assert = require("assert").strict;
|
||||
const path = require("path");
|
||||
const fs = require("fs-extra");
|
||||
|
||||
const libLink = require("@clusterio/lib/link");
|
||||
const libConfig = require("@clusterio/lib/config");
|
||||
const Instance = require("@clusterio/slave/src/Instance");
|
||||
const Slave = require("@clusterio/slave/src/Slave");
|
||||
|
||||
describe("Slave testing", function() {
|
||||
@@ -13,166 +10,6 @@ describe("Slave testing", function() {
|
||||
libConfig.InstanceConfig.finalize();
|
||||
});
|
||||
|
||||
describe("checkFilename()", function() {
|
||||
it("should allow a basic name", function() {
|
||||
Slave._checkFilename("file");
|
||||
});
|
||||
|
||||
function check(item, msg) {
|
||||
assert.throws(() => Slave._checkFilename(item), new Error(msg));
|
||||
}
|
||||
|
||||
it("should throw on non-string", function() {
|
||||
check(undefined, "must be a string");
|
||||
check(null, "must be a string");
|
||||
check({}, "must be a string");
|
||||
check([], "must be a string");
|
||||
check(0, "must be a string");
|
||||
check(false, "must be a string");
|
||||
});
|
||||
|
||||
it("should throw on empty name", function() {
|
||||
check("", "cannot be empty");
|
||||
});
|
||||
|
||||
it("should throw on <>:\"\\/|?* \\x00\\r\\n\\t", function() {
|
||||
for (let char of '<>:"\\/|?*\x00\r\n\t') {
|
||||
check(char, 'cannot contain <>:"\\/|=* or control characters');
|
||||
}
|
||||
});
|
||||
|
||||
it("should throw on CON, PRN, AUX, NUL, COM1, LPT1", function() {
|
||||
for (let bad of ["CON", "PRN", "AUX", "NUL", "COM1", "LPT1"]) {
|
||||
check(bad, "cannot be named any of . .. CON PRN AUX NUL COM1-9 and LPT1-9");
|
||||
}
|
||||
});
|
||||
|
||||
it("should throw on . and ..", function() {
|
||||
for (let bad of [".", ".."]) {
|
||||
check(bad, "cannot be named any of . .. CON PRN AUX NUL COM1-9 and LPT1-9");
|
||||
}
|
||||
});
|
||||
|
||||
it("should throw on names ending with . or space", function() {
|
||||
check("a ", "cannot end with . or space");
|
||||
check("a.", "cannot end with . or space");
|
||||
});
|
||||
});
|
||||
|
||||
describe("cleanFilename()", function() {
|
||||
function clean(item, expected) {
|
||||
assert.equal(Slave._cleanFilename(item), expected);
|
||||
}
|
||||
|
||||
it("should allow a basic name", function() {
|
||||
clean("file", "file");
|
||||
});
|
||||
|
||||
function check(item, msg) {
|
||||
assert.throws(() => Slave._cleanFilename(item), new Error(msg));
|
||||
}
|
||||
|
||||
it("should throw on non-string", function() {
|
||||
check(undefined, "name must be a string");
|
||||
check(null, "name must be a string");
|
||||
check({}, "name must be a string");
|
||||
check([], "name must be a string");
|
||||
check(0, "name must be a string");
|
||||
check(false, "name must be a string");
|
||||
});
|
||||
|
||||
it("should clean empty name", function() {
|
||||
clean("", "_");
|
||||
});
|
||||
|
||||
it("should clean <>:\"\\/|?* \\x00\\r\\n\\t", function() {
|
||||
for (let char of '<>:"\\/|?*\x00\r\n\t') {
|
||||
clean(char, "_");
|
||||
}
|
||||
});
|
||||
|
||||
it("should clean CON, PRN, AUX, NUL, COM1, LPT1", function() {
|
||||
for (let bad of ["CON", "PRN", "AUX", "NUL", "COM1", "LPT1"]) {
|
||||
clean(bad, `${bad}_`);
|
||||
}
|
||||
});
|
||||
|
||||
it("should clean . and ..", function() {
|
||||
for (let bad of [".", ".."]) {
|
||||
clean(bad, `${bad}_`);
|
||||
}
|
||||
});
|
||||
|
||||
it("should clean names ending with . or space", function() {
|
||||
clean("a ", "a_");
|
||||
clean("a.", "a_");
|
||||
});
|
||||
});
|
||||
|
||||
describe("symlinkMods()", function() {
|
||||
let testDir = path.join("temp", "test", "symlink");
|
||||
|
||||
let instance;
|
||||
before(async function() {
|
||||
// Remove previous test output
|
||||
await fs.remove(testDir);
|
||||
|
||||
// Add some test mods
|
||||
await fs.outputFile(path.join(testDir, "shared", "mod_a.zip"), "a");
|
||||
await fs.outputFile(path.join(testDir, "shared", "mod_b.zip"), "b");
|
||||
await fs.outputFile(path.join(testDir, "shared", "mod.dat"), "c");
|
||||
await fs.outputFile(path.join(testDir, "shared", "mod-list.json"), "d");
|
||||
|
||||
let instanceConfig = new libConfig.InstanceConfig("slave");
|
||||
await instanceConfig.init();
|
||||
instanceConfig.set("instance.name", "test");
|
||||
instance = new Instance(
|
||||
{}, new libLink.VirtualConnector(), path.join(testDir, "instance"), "factorioDir", instanceConfig
|
||||
);
|
||||
await fs.outputFile(instance.path("mods", "mod_i.zip"), "i");
|
||||
});
|
||||
|
||||
it("should link mods and data files", async function() {
|
||||
await Instance._symlinkMods(instance, path.join(testDir, "shared"));
|
||||
|
||||
assert.equal(await fs.readFile(instance.path("mods", "mod_a.zip"), "utf-8"), "a");
|
||||
assert.equal(await fs.readFile(instance.path("mods", "mod_b.zip"), "utf-8"), "b");
|
||||
assert.equal(await fs.readFile(instance.path("mods", "mod.dat"), "utf-8"), "c");
|
||||
assert.equal(await fs.readFile(instance.path("mods", "mod-list.json"), "utf-8"), "d");
|
||||
assert.equal(await fs.readFile(instance.path("mods", "mod_i.zip"), "utf-8"), "i");
|
||||
});
|
||||
|
||||
it("should ignore directories", async function() {
|
||||
await fs.ensureDir(path.join(testDir, "shared", "dir"));
|
||||
await Instance._symlinkMods(instance, path.join(testDir, "shared"));
|
||||
|
||||
assert(!await fs.exists(instance.path("mods", "dir"), "utf-8"), "dir was unxpectedly linked");
|
||||
});
|
||||
|
||||
it("should ignore files", async function() {
|
||||
await fs.outputFile(path.join(testDir, "shared", "file"), "f");
|
||||
await Instance._symlinkMods(instance, path.join(testDir, "shared"));
|
||||
|
||||
assert(!await fs.exists(instance.path("mods", "file"), "utf-8"), "dir was unxpectedly linked");
|
||||
});
|
||||
|
||||
it("should unlink removed mods", async function() {
|
||||
// This does not work on Windows
|
||||
if (process.platform === "win32") {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
await fs.unlink(path.join(testDir, "shared", "mod_a.zip"));
|
||||
await Instance._symlinkMods(instance, path.join(testDir, "shared"));
|
||||
|
||||
await assert.rejects(fs.lstat(instance.path("mods", "mod_a.zip")), { code: "ENOENT" });
|
||||
assert.equal(await fs.readFile(instance.path("mods", "mod_b.zip"), "utf-8"), "b");
|
||||
assert.equal(await fs.readFile(instance.path("mods", "mod.dat"), "utf-8"), "c");
|
||||
assert.equal(await fs.readFile(instance.path("mods", "mod-list.json"), "utf-8"), "d");
|
||||
assert.equal(await fs.readFile(instance.path("mods", "mod_i.zip"), "utf-8"), "i");
|
||||
});
|
||||
});
|
||||
|
||||
describe("discoverInstances()", function() {
|
||||
it("should discover test instance", async function() {
|
||||
let instancePath = path.join("test", "file", "instances");
|
||||
|
||||
Reference in new issue
Block a user