mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 23:04:52 +09:00
445 lines
14 KiB
JavaScript
445 lines
14 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import * as lib from "@clusterio/lib";
|
|
const validators = lib.Config.validators;
|
|
import { ControllerConfig, HostConfig, InstanceConfig, CtlConfig } from "@clusterio/lib";
|
|
|
|
describe("lib/config/validators", function() {
|
|
describe("all", function() {
|
|
it("should call all validators in order", function() {
|
|
let calls = [];
|
|
|
|
function v1(value, config) {
|
|
calls.push("v1");
|
|
}
|
|
function v2(value, config) {
|
|
calls.push("v2");
|
|
}
|
|
|
|
const combined = validators.all(v1, v2);
|
|
combined("value", {});
|
|
|
|
assert.deepEqual(calls, ["v1", "v2"], "validators were not called in order");
|
|
});
|
|
|
|
it("should propagate errors from validators", function() {
|
|
function v1() {}
|
|
function v2() {
|
|
throw new Error("failure");
|
|
}
|
|
|
|
const combined = validators.all(v1, v2);
|
|
|
|
assert.throws(
|
|
() => combined("value", {}),
|
|
/failure/,
|
|
"error from validator was not propagated"
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("optional", function() {
|
|
it("should skip validation when value is null", function() {
|
|
let called = false;
|
|
|
|
function validator() {
|
|
called = true;
|
|
}
|
|
|
|
const wrapped = validators.optional(validator);
|
|
wrapped(null, {});
|
|
|
|
assert.equal(called, false, "validator should not have been called");
|
|
});
|
|
|
|
it("should run validator when value is not null", function() {
|
|
let called = false;
|
|
|
|
function validator() {
|
|
called = true;
|
|
}
|
|
|
|
const wrapped = validators.optional(validator);
|
|
wrapped("value", {});
|
|
|
|
assert.equal(called, true, "validator was not called");
|
|
});
|
|
});
|
|
|
|
describe("greaterThan", function() {
|
|
it("should allow values greater than min", function() {
|
|
const validator = validators.greaterThan(5);
|
|
assert.doesNotThrow(() => validator(6));
|
|
});
|
|
|
|
it("should throw if value equals min", function() {
|
|
const validator = validators.greaterThan(5);
|
|
|
|
assert.throws(
|
|
() => validator(5),
|
|
/Value must be greater than 5/,
|
|
"did not throw on equal value"
|
|
);
|
|
});
|
|
|
|
it("should throw if value is less than min", function() {
|
|
const validator = validators.greaterThan(5);
|
|
|
|
assert.throws(
|
|
() => validator(4),
|
|
/Value must be greater than 5/,
|
|
"did not throw on smaller value"
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("greaterThanZero", function() {
|
|
it("should allow values greater than zero", function() {
|
|
assert.doesNotThrow(() => validators.greaterThanZero(1));
|
|
});
|
|
|
|
it("should throw on zero", function() {
|
|
assert.throws(
|
|
() => validators.greaterThanZero(0),
|
|
/Value must be greater than 0/
|
|
);
|
|
});
|
|
|
|
it("should throw on negative values", function() {
|
|
assert.throws(
|
|
() => validators.greaterThanZero(-1),
|
|
/Value must be greater than 0/
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("greaterThanEqual", function() {
|
|
it("should allow values greater than min", function() {
|
|
const validator = validators.greaterThanEqual(5);
|
|
assert.doesNotThrow(() => validator(6));
|
|
});
|
|
|
|
it("should allow values equal to min", function() {
|
|
const validator = validators.greaterThanEqual(5);
|
|
assert.doesNotThrow(() => validator(5));
|
|
});
|
|
|
|
it("should throw if value is less than min", function() {
|
|
const validator = validators.greaterThanEqual(5);
|
|
|
|
assert.throws(
|
|
() => validator(4),
|
|
/Value must be greater than 5/,
|
|
"did not throw on smaller value"
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("greaterThanEqualZero", function() {
|
|
it("should allow zero", function() {
|
|
assert.doesNotThrow(() => validators.greaterThanEqualZero(0));
|
|
});
|
|
|
|
it("should allow positive values", function() {
|
|
assert.doesNotThrow(() => validators.greaterThanEqualZero(1));
|
|
});
|
|
|
|
it("should throw on negative values", function() {
|
|
assert.throws(
|
|
() => validators.greaterThanEqualZero(-1),
|
|
/Value must be greater than 0/
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("integer", function() {
|
|
it("should allow integer values", function() {
|
|
assert.doesNotThrow(() => validators.integer(10));
|
|
});
|
|
|
|
it("should throw on non-integer values", function() {
|
|
assert.throws(
|
|
() => validators.integer(1.5),
|
|
/Value must be an integer/,
|
|
"did not throw on float"
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("filePath", function() {
|
|
it("should accept valid paths", function() {
|
|
assert.doesNotThrow(() => validators.filePath("test/file.txt"));
|
|
assert.doesNotThrow(() => validators.filePath("/absolute/path"));
|
|
});
|
|
|
|
it("should throw if value is not a string", function() {
|
|
// This is the only case that will cause path.resolve to throw on posix
|
|
assert.throws(
|
|
() => validators.filePath(null),
|
|
/Value must be a valid path/
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("lib/config/definitions/validators", function() {
|
|
describe("Controller Config", function() {
|
|
it("should validate controller.static_url", function() {
|
|
const config = new ControllerConfig("controller");
|
|
|
|
// valid cases
|
|
config.set("controller.static_url", "");
|
|
config.set("controller.static_url", null);
|
|
config.set("controller.static_url", "static/");
|
|
config.set("controller.static_url", "./static/");
|
|
config.set("controller.static_url", "/static/");
|
|
config.set("controller.static_url", "http://example/static/");
|
|
|
|
// invalid cases
|
|
assert.throws(
|
|
() => config.set("controller.static_url", "/foo/"),
|
|
"Path component of static_url must be \/static\/ not \/foo\/."
|
|
);
|
|
assert.throws(
|
|
() => config.set("controller.static_url", "http://example/foo/static/"),
|
|
"Path component of static_url must be \/static\/ not \/foo\/static\/."
|
|
);
|
|
assert.throws(
|
|
() => config.set("controller.static_url", "http://example/static"),
|
|
"Path component of static_url must be \/static\/ not \/static."
|
|
);
|
|
assert.throws(
|
|
() => config.set("controller.static_url", "http://:80"),
|
|
/Invalid URL/,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Host Config", function() {
|
|
|
|
});
|
|
|
|
describe("Instance Config", function() {
|
|
it("should validate factorio.version", function() {
|
|
const config = new InstanceConfig("controller");
|
|
|
|
// valid cases
|
|
assert.doesNotThrow(() => config.set("factorio.version", "latest"));
|
|
assert.doesNotThrow(() => config.set("factorio.version", "1.1"));
|
|
assert.doesNotThrow(() => config.set("factorio.version", "1.1.87"));
|
|
|
|
// invalid case
|
|
assert.throws(
|
|
() => config.set("factorio.version", "invalidValue"),
|
|
/Value must be 'latest', a release channel, or match X.Y or X.Y.Z/
|
|
);
|
|
});
|
|
|
|
describe("factorio.player_online_autosave_slots", function() {
|
|
it("should validate against autosave slots", function() {
|
|
const config = new InstanceConfig("controller", {
|
|
"factorio.settings": {
|
|
autosave_slots: 5,
|
|
},
|
|
});
|
|
|
|
// valid: equal
|
|
assert.doesNotThrow(() => config.set("factorio.player_online_autosave_slots", 5));
|
|
|
|
// valid: greater
|
|
assert.doesNotThrow(() => config.set("factorio.player_online_autosave_slots", 6));
|
|
|
|
// invalid: less than autosave_slots
|
|
assert.throws(
|
|
() => config.set("factorio.player_online_autosave_slots", 4),
|
|
/Value cannot be less than the number of autosave slots/
|
|
);
|
|
});
|
|
it("should allow disabling player online autosaves", function() {
|
|
const config = new InstanceConfig("controller", {
|
|
"factorio.settings": {
|
|
autosave_slots: 5,
|
|
},
|
|
});
|
|
|
|
assert.doesNotThrow(() => config.set("factorio.player_online_autosave_slots", 0));
|
|
});
|
|
it("should allow when autosave_slots is not a number", function() {
|
|
const config = new InstanceConfig("controller", {
|
|
"factorio.settings": {
|
|
autosave_slots: null,
|
|
},
|
|
});
|
|
|
|
assert.doesNotThrow(() => config.set("factorio.player_online_autosave_slots", 0));
|
|
assert.doesNotThrow(() => config.set("factorio.player_online_autosave_slots", 1));
|
|
});
|
|
});
|
|
|
|
describe("plugin.load_plugin", function() {
|
|
const initialConfigFields = {};
|
|
|
|
before(function() {
|
|
for (const fieldName of Object.keys(InstanceConfig.fieldDefinitions)) {
|
|
if (fieldName.endsWith("load_plugin")) {
|
|
initialConfigFields[fieldName] = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
it("should validate against factorio.enable_save_patching", function() {
|
|
class TestInstanceConfig extends InstanceConfig {
|
|
static fieldDefinitions = { ...InstanceConfig.fieldDefinitions };
|
|
}
|
|
|
|
lib.addPluginFieldDefinitions(
|
|
{name: "save_patching", features: ["SavePatching"] },
|
|
"instanceConfigFields", TestInstanceConfig
|
|
);
|
|
|
|
const config = new TestInstanceConfig("controller", initialConfigFields);
|
|
|
|
// valid: does not require script commands
|
|
assert.doesNotThrow(() => config.set("factorio.enable_script_commands", false));
|
|
|
|
// valid: can have load disabled
|
|
assert.doesNotThrow(() => config.set("save_patching.load_plugin", false));
|
|
|
|
// valid: does not require save patching when not loaded
|
|
assert.doesNotThrow(() => config.set("factorio.enable_save_patching", false));
|
|
|
|
// invalid: requires save patching when loaded
|
|
assert.throws(
|
|
() => config.set("save_patching.load_plugin", true),
|
|
/requires save patching/
|
|
);
|
|
});
|
|
it("should validate against factorio.enable_script_commands", function() {
|
|
class TestInstanceConfig extends InstanceConfig {
|
|
static fieldDefinitions = { ...InstanceConfig.fieldDefinitions };
|
|
}
|
|
|
|
lib.addPluginFieldDefinitions(
|
|
{ name: "script_commands", features: ["ScriptCommands"] },
|
|
"instanceConfigFields", TestInstanceConfig
|
|
);
|
|
|
|
const config = new TestInstanceConfig("controller", initialConfigFields);
|
|
|
|
// valid: does not require save patching
|
|
assert.doesNotThrow(() => config.set("factorio.enable_save_patching", false));
|
|
|
|
// valid: can have load disabled
|
|
assert.doesNotThrow(() => config.set("script_commands.load_plugin", false));
|
|
|
|
// valid: does not require script commands when not loaded
|
|
assert.doesNotThrow(() => config.set("factorio.enable_script_commands", false));
|
|
|
|
// invalid: requires script commands when loaded
|
|
assert.throws(
|
|
() => config.set("script_commands.load_plugin", true),
|
|
/requires script commands/
|
|
);
|
|
});
|
|
it("should validate against factorio.enable_lua_udp", function() {
|
|
class TestInstanceConfig extends InstanceConfig {
|
|
static fieldDefinitions = { ...InstanceConfig.fieldDefinitions };
|
|
}
|
|
|
|
lib.addPluginFieldDefinitions(
|
|
{ name: "lua_udp", features: ["LuaUdp"] },
|
|
"instanceConfigFields", TestInstanceConfig
|
|
);
|
|
|
|
const config = new TestInstanceConfig("controller", initialConfigFields);
|
|
|
|
// valid: does not require save patching or script commands
|
|
assert.doesNotThrow(() => config.set("factorio.enable_save_patching", false));
|
|
assert.doesNotThrow(() => config.set("factorio.enable_script_commands", false));
|
|
|
|
// valid: can have load disabled
|
|
assert.doesNotThrow(() => config.set("lua_udp.load_plugin", false));
|
|
|
|
// invalid: requires lua udp when loaded (disabled by default)
|
|
assert.throws(
|
|
() => config.set("lua_udp.load_plugin", true),
|
|
/requires Lua UDP/
|
|
);
|
|
|
|
// valid: requirement satisfied
|
|
assert.doesNotThrow(() => config.set("factorio.enable_lua_udp", true));
|
|
assert.doesNotThrow(() => config.set("lua_udp.load_plugin", true));
|
|
});
|
|
it("should default load_plugin to false when a required feature is disabled", function() {
|
|
class TestInstanceConfig extends InstanceConfig {
|
|
static fieldDefinitions = { ...InstanceConfig.fieldDefinitions };
|
|
}
|
|
|
|
lib.addPluginFieldDefinitions(
|
|
{ name: "requires_features", features: ["SavePatching", "ScriptCommands"] },
|
|
"instanceConfigFields", TestInstanceConfig
|
|
);
|
|
|
|
assert.equal(new TestInstanceConfig("controller").get("requires_features.load_plugin"), true);
|
|
for (const field of ["factorio.enable_save_patching", "factorio.enable_script_commands"]) {
|
|
const config = new TestInstanceConfig("controller", { [field]: false });
|
|
assert.equal(config.get("requires_features.load_plugin"), false);
|
|
// Other fields can be edited since the config is valid
|
|
assert.doesNotThrow(() => config.set("instance.name", "test"));
|
|
}
|
|
|
|
// An explicit value is kept
|
|
const config = new TestInstanceConfig("controller", {
|
|
"factorio.enable_save_patching": false,
|
|
"requires_features.load_plugin": true,
|
|
});
|
|
assert.equal(config.get("requires_features.load_plugin"), true);
|
|
});
|
|
it("should reject disabling a feature required by a loaded plugin", function() {
|
|
class TestInstanceConfig extends InstanceConfig {
|
|
static fieldDefinitions = { ...InstanceConfig.fieldDefinitions };
|
|
static _fieldDependents; // Don't reuse the cache from InstanceConfig
|
|
}
|
|
|
|
lib.addPluginFieldDefinitions(
|
|
{ name: "save_patching", features: ["SavePatching"] },
|
|
"instanceConfigFields", TestInstanceConfig
|
|
);
|
|
|
|
const config = new TestInstanceConfig("controller");
|
|
assert.throws(
|
|
() => config.set("factorio.enable_save_patching", false),
|
|
/requires save patching/
|
|
);
|
|
assert.equal(config.get("factorio.enable_save_patching"), true);
|
|
});
|
|
it("should allow when no feature flags are present", function() {
|
|
class TestInstanceConfig extends InstanceConfig {
|
|
static fieldDefinitions = { ...InstanceConfig.fieldDefinitions };
|
|
}
|
|
|
|
lib.addPluginFieldDefinitions(
|
|
{ name: "no_features", features: [] },
|
|
"instanceConfigFields", TestInstanceConfig
|
|
);
|
|
|
|
const config = new TestInstanceConfig("controller", initialConfigFields);
|
|
|
|
// valid: does not require save patching
|
|
assert.doesNotThrow(() => config.set("factorio.enable_save_patching", false));
|
|
|
|
// valid: does not require script commands
|
|
assert.doesNotThrow(() => config.set("factorio.enable_script_commands", false));
|
|
|
|
// valid: can have load disabled
|
|
assert.doesNotThrow(() => config.set("no_features.load_plugin", false));
|
|
|
|
// no invalid case
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("Ctl Config", function() {
|
|
|
|
});
|
|
});
|