Files
factorio-clusterio/test/lib/config/validators.test.js
T
Hornwitser 7c0d7faa24 Add config to set URL for static assets
Add option in the controller config to set where static assets are
loaded from in order to support deployments where the static assets are
server via a CDN while the API is directly accessed.

This is useful because some CDNs put low limits on how large requests
bodies can be, which causes issues with uploading large mods and saves.
2026-08-31 19:17:31 +02:00

374 lines
11 KiB
JavaScript

"use strict";
const assert = require("assert").strict;
const lib = require("@clusterio/lib");
const validators = lib.Config.validators;
const { ControllerConfig, HostConfig, InstanceConfig, ControlConfig } = 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 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("Control Config", function() {
});
});