Refactor config to JSON data class pattern

Refactor the Config and ConfigGroup classes to match the JSON data class
pattern with toJSON, toRemote and fromJSON methods replacing the load,
init, and serialize methods.  Since fromJSON requires extra information
the messages structures passes around the plain JSON representation of
the configs instead of serialising instances of the config classes
automatically.
This commit is contained in:
Hornwitser committed 2024-01-06 19:53:32 +01:00
1 parent 27af142a87
commit 001db4befe
24 files changed
+269 -334

No files matched your search

+7 -8
View File
@@ -14,19 +14,18 @@ describe("controller/InstanceInfo", function() {
if (!validate(json)) {
throw validate.errors;
}
assert.deepEqual(InstanceInfo.fromJSON(json, info.config), info);
assert.deepEqual(InstanceInfo.fromJSON(json, "controller"), info);
}
if (!lib.InstanceConfig._finalized) {
lib.InstanceConfig.finalize();
}
const config = new lib.InstanceConfig("controller");
config.init();
const config = new lib.InstanceConfig("controller").toJSON();
check(InstanceInfo.fromJSON({ config, status: "stopped" }, config));
check(InstanceInfo.fromJSON({ config, status: "running" }, config));
check(InstanceInfo.fromJSON({ config, status: "running", gamePort: 34197 }, config));
check(InstanceInfo.fromJSON({ config, status: "running", updatedAt: Date.now() }, config));
check(InstanceInfo.fromJSON({ config, status: "stopped" }, "controller"));
check(InstanceInfo.fromJSON({ config, status: "running" }, "controller"));
check(InstanceInfo.fromJSON({ config, status: "running", gamePort: 34197 }, "controller"));
check(InstanceInfo.fromJSON({ config, status: "running", updatedAt: Date.now() }, "controller"));
// All at once
check(InstanceInfo.fromJSON({
@@ -34,7 +33,7 @@ describe("controller/InstanceInfo", function() {
status: "unknown",
gamePort: 10000,
updateAt: 20000,
}, config));
}, "controller"));
});
});
});
-1
View File
@@ -17,7 +17,6 @@ describe("Host testing", function() {
let instanceInfos = await _discoverInstances(instancePath);
let referenceConfig = new lib.InstanceConfig("host");
referenceConfig.init();
referenceConfig.set("instance.id", 1);
referenceConfig.set("instance.name", "test");
referenceConfig.dirty = false;
-1
View File
@@ -21,7 +21,6 @@ describe("class Instance", function() {
let connector;
beforeEach(function() {
let instanceConfig = new lib.InstanceConfig("host");
instanceConfig.init();
instanceConfig.set("instance.name", "foo");
src = addr({ instanceId: instanceConfig.get("instance.id") });
connector = new MockConnector(src, dst);
+2 -2
View File
@@ -206,7 +206,7 @@ describe("Integration of Clusterio", function() {
it("should not leak auth_secret", async function() {
let result = await getControl().send(new lib.ControllerConfigGetRequest());
let done = false;
for (let group of result.serializedConfig.groups) {
for (let group of result.groups) {
if (group.name === "controller") {
assert.equal(Object.prototype.hasOwnProperty.call(group.fields, "auth_secret"), false);
done = true;
@@ -221,7 +221,7 @@ describe("Integration of Clusterio", function() {
await execCtl('controller config set controller.name "Test Cluster"');
let result = await getControl().send(new lib.ControllerConfigGetRequest());
let done = false;
for (let group of result.serializedConfig.groups) {
for (let group of result.groups) {
if (group.name === "controller") {
assert.equal(group.fields.name, "Test Cluster");
done = true;
+52 -93
View File
@@ -163,13 +163,8 @@ describe("lib/config/classes", function() {
it("should create an instance", function() {
let testInstance = new TestGroup(mockConfig);
});
});
describe(".init()", function() {
it("should initialize all fields with defaults", function() {
let testInstance = new TestGroup(mockConfig);
testInstance.init();
assert.equal(testInstance.get("enum"), "b");
assert.equal(testInstance.get("test"), null);
assert.equal(testInstance.get("func"), 42);
@@ -177,7 +172,6 @@ describe("lib/config/classes", function() {
});
it("should not initalize inaccessible fields", function() {
let testInstance = new TestGroup({ location: "remote" });
testInstance.init();
assert.equal(testInstance._fields.get("enum"), "b");
assert.equal(testInstance._fields.get("priv"), undefined);
});
@@ -186,17 +180,14 @@ describe("lib/config/classes", function() {
describe("get name", function() {
it("should give the name of the group", function() {
let testInstance = new TestGroup(mockConfig);
testInstance.init();
assert.equal(testInstance.name, "test_group");
});
});
describe(".serialize()", function() {
describe(".toJSON()", function() {
it("should serialize a basic group", function() {
let testInstance = new TestGroup(mockConfig);
testInstance.init();
assert.deepEqual(testInstance.serialize(), {
assert.deepEqual(testInstance.toJSON(), {
name: "test_group",
fields: {
enum: "b",
@@ -208,11 +199,12 @@ describe("lib/config/classes", function() {
},
});
});
});
describe(".toRemote()", function() {
it("should leave out inaccessible fields", function() {
let testInstance = new TestGroup(mockConfig);
testInstance.init();
assert.deepEqual(testInstance.serialize("remote"), {
assert.deepEqual(testInstance.toRemote("remote"), {
name: "test_group",
fields: {
enum: "b",
@@ -225,18 +217,16 @@ describe("lib/config/classes", function() {
});
});
describe(".load()", function() {
describe(".fromJSON()", function() {
it("should load a basic group", function() {
let testInstance = new TestGroup(mockConfig);
testInstance.load({ name: "test_group", fields: {
let testInstance = TestGroup.fromJSON({ name: "test_group", fields: {
enum: "c",
test: "blah",
func: 22,
bool: null,
json: { valid: true },
priv: "bar",
}});
}}, mockConfig);
assert.equal(testInstance.get("enum"), "c");
assert.equal(testInstance.get("test"), "blah");
assert.equal(testInstance.get("func"), 22);
@@ -245,17 +235,16 @@ describe("lib/config/classes", function() {
assert.deepEqual(testInstance.get("json"), { valid: true });
});
it("should load defaults for missing serialized fields", function() {
let testInstance = new TestGroup(mockConfig);
testInstance.load({ name: "test_group", fields: { enum: "a" } });
let testInstance = TestGroup.fromJSON({ name: "test_group", fields: { enum: "a" } }, mockConfig);
assert.equal(testInstance.get("enum"), "a");
assert.equal(testInstance.get("test"), null);
assert.equal(testInstance.get("func"), 42);
});
it("should ignore inaccessible fields", function() {
let testInstance = new TestGroup(mockConfig);
testInstance.load({ name: "test_group", fields: { priv: "bad" } }, "remote");
assert.equal(testInstance.get("priv"), null);
let remoteConfig = new events.EventEmitter();
remoteConfig.location = "remote";
let testInstance = TestGroup.fromJSON({ name: "test_group", fields: { priv: "bad" } }, remoteConfig);
assert.equal(testInstance._fields.get("priv"), undefined);
});
it("should preserve unknown fields when serialized back", function() {
@@ -271,30 +260,24 @@ describe("lib/config/classes", function() {
gamma: 99,
};
let testInstance = new TestGroup(mockConfig);
testInstance.load({ name: "test_group", fields: testFields });
assert.deepEqual(testInstance.serialize().fields, testFields);
let testInstance = TestGroup.fromJSON({ name: "test_group", fields: testFields }, mockConfig);
assert.deepEqual(testInstance.toJSON().fields, testFields);
});
});
describe(".update()", function() {
it("should skip updating invalid values", function() {
let testInstance = new TestGroup(mockConfig);
testInstance.init();
testInstance.update({ name: "test_group", fields: { enum: "wrong", test: 3, func: null }}, false);
assert.equal(testInstance.get("enum"), "b");
assert.equal(testInstance.get("test"), null);
assert.equal(testInstance.get("func"), 42);
});
it("should skip updating inaccessible fields", function() {
let testInstance = new TestGroup(mockConfig);
testInstance.init();
testInstance.update(
{ name: "test_group", fields: { test: "a", priv: "bad" }}, false, "remote"
);
assert.equal(testInstance.get("test"), "a");
assert.equal(testInstance.get("priv"), null);
});
@@ -327,10 +310,10 @@ describe("lib/config/classes", function() {
describe(".canAccess()", function() {
let testInstance;
before(function() {
testInstance = new TestGroup(mockConfig);
testInstance.load({ name: "test_group", fields: {
enum: "a", priv: "blah", func: 27,
}});
testInstance = new TestGroup(mockConfig, {
name: "test_group",
fields: { enum: "a", priv: "blah", func: 27 },
});
});
it("should return true for fields that are accessible", function() {
for (let field of ["enum", "test", "func", "bool", "json"]) {
@@ -347,10 +330,10 @@ describe("lib/config/classes", function() {
describe(".get()", function() {
let testInstance;
before(function() {
testInstance = new TestGroup(mockConfig);
testInstance.load({ name: "test_group", fields: {
enum: "a", priv: "blah", func: 27,
}});
testInstance = new TestGroup(mockConfig, {
name: "test_group",
fields: { enum: "a", priv: "blah", func: 27 },
});
});
it("should retreive value of field", function() {
assert.equal(testInstance.get("enum"), "a");
@@ -368,8 +351,10 @@ describe("lib/config/classes", function() {
describe(".set()", function() {
let testInstance;
before(function() {
testInstance = new TestGroup(mockConfig);
testInstance.load({ name: "test_group", fields: { enum: "a", test: "blah", func: 27 }});
testInstance = TestGroup.fromJSON(
{ name: "test_group", fields: { enum: "a", test: "blah", func: 27 }},
mockConfig,
);
});
it("should throw if field does not exist", function() {
@@ -473,10 +458,10 @@ describe("lib/config/classes", function() {
describe(".setProp()", function() {
let testInstance;
before(function() {
testInstance = new TestGroup(mockConfig);
testInstance.load({ name: "test_group", fields: {
enum: "a", test: "blah", func: 27,
}});
testInstance = TestGroup.fromJSON(
{ name: "test_group", fields: { enum: "a", test: "blah", func: 27 }},
mockConfig,
);
});
it("should throw if field does not exist", function() {
@@ -585,22 +570,17 @@ describe("lib/config/classes", function() {
new Error("location must be a string")
);
});
});
describe(".init()", function() {
it("should initialize all fields", function() {
let testInstance = new TestConfig("local");
testInstance.init();
assert.equal(testInstance.get("alpha.foo"), null);
assert.deepEqual(testInstance.get("beta.bar"), {});
});
});
describe(".serialize()", function() {
describe(".toJSON()", function() {
it("should serialize a basic config", function() {
let testInstance = new TestConfig("local");
testInstance.init();
assert.deepEqual(testInstance.serialize(), {
assert.deepEqual(testInstance.toJSON(), {
groups: [
{
name: "alpha",
@@ -615,27 +595,24 @@ describe("lib/config/classes", function() {
});
});
describe(".load()", function() {
it("should reject on incorrect input passed", function() {
let testInstance = new TestConfig("local");
describe("static .fromJSON()", function() {
it("should throw on incorrect input passed", function() {
assert.throws(
testInstance.load(null),
new Error("Expected object, not null for config")
() => TestConfig.fromJSON(null, "local"),
new Error("Invalid config")
);
assert.throws(
testInstance.load(undefined),
new Error("Expected object, not undefined for config")
() => TestConfig.fromJSON(undefined, "local"),
new Error("Invalid config")
);
assert.throws(
testInstance.load({ groups: null }),
new Error("Expected groups to be an array, not null")
() => TestConfig.fromJSON({ groups: null }, "local"),
new Error("Invalid config")
);
});
it("should load defaults for missing serialized groups", function() {
let testInstance = new TestConfig("local");
testInstance.load({ groups: [{ name: "alpha", fields: { foo: "a" }}] });
let testInstance = TestConfig.fromJSON({ groups: [{ name: "alpha", fields: { foo: "a" }}] }, "local");
assert.equal(testInstance.get("alpha.foo"), "a");
assert.deepEqual(testInstance.get("beta.bar"), {});
});
@@ -655,18 +632,14 @@ describe("lib/config/classes", function() {
fields: { bar: { value: 20 }},
},
];
let testInstance = new TestConfig("local");
testInstance.load({ groups: testGroups });
assert.deepEqual(testInstance.serialize().groups, testGroups);
let testInstance = TestConfig.fromJSON({ groups: testGroups }, "local");
assert.deepEqual(testInstance.toJSON().groups, testGroups);
});
});
describe(".update()", function() {
it("should update a basic config", function() {
let testInstance = new TestConfig("local");
testInstance.init();
testInstance.update({ groups: [
{
name: "extra",
@@ -678,7 +651,7 @@ describe("lib/config/classes", function() {
},
]}, false);
assert.deepEqual(testInstance.serialize(), {
assert.deepEqual(testInstance.toJSON(), {
groups: [
{
name: "extra",
@@ -698,34 +671,25 @@ describe("lib/config/classes", function() {
});
describe(".group()", function() {
it("should throw if instance is not initialized", function() {
it("should retrieve a given group", function() {
let testInstance = new TestConfig("local");
assert.throws(
() => testInstance.group("test"),
new Error("TestConfig instance is uninitialized")
);
assert(testInstance.group("alpha") instanceof AlphaGroup);
});
});
describe(".canAccess()", function() {
it("should throw if group does not exist", function() {
let testInstance = new TestConfig("local");
testInstance.init();
assert.throws(() => testInstance.canAccess("invalid"), new Error("No config group named 'invalid'"));
});
it("should throw if no field is specified", function() {
let testInstance = new TestConfig("local");
testInstance.init();
assert.throws(() => testInstance.canAccess("alpha"), new Error("No field named ''"));
});
it("should return true for an accessible field", function() {
let testInstance = new TestConfig("local");
testInstance.init();
assert.equal(testInstance.canAccess("alpha.foo"), true);
});
});
@@ -733,15 +697,11 @@ describe("lib/config/classes", function() {
describe(".get()", function() {
it("should throw if group does not exist", function() {
let testInstance = new TestConfig("local");
testInstance.init();
assert.throws(() => testInstance.get("invalid"), new Error("No config group named 'invalid'"));
});
it("should throw if no field is specified", function() {
let testInstance = new TestConfig("local");
testInstance.init();
assert.throws(() => testInstance.get("alpha"), new Error("No field named ''"));
});
});
@@ -749,8 +709,6 @@ describe("lib/config/classes", function() {
describe(".set()", function() {
it("should set the value", function() {
let testInstance = new TestConfig("local");
testInstance.init();
testInstance.set("alpha.foo", "new value");
assert.equal(testInstance.get("alpha.foo"), "new value");
});
@@ -767,8 +725,6 @@ describe("lib/config/classes", function() {
let called;
beforeEach(function() {
testInstance = new TestConfig("local");
testInstance.init();
called = false;
testInstance.once("fieldChanged", (group, field, prev) => {
if (group.name === "beta" && field === "bar") {
@@ -783,7 +739,10 @@ describe("lib/config/classes", function() {
});
it("should be called when updating a config", function() {
testInstance.update({ groups: [{ name: "beta", fields: { bar: { value: 1 }}}]}, true);
testInstance.update({
location: "local",
groups: [{ name: "beta", fields: { bar: { value: 1 }}}],
}, true);
assert(called, "fieldChanged was not called");
});