Remove tests that pin implementation details

Drops tests that restate message declarations, check that removed
commands are gone, test internal helpers or repeat the same behaviour
at another layer.
This commit is contained in:
Danielv123 committed 2026-10-30 10:11:14 +01:00
1 parent 87d4f5c64e
commit 37e79d9c41
10 files changed
+1 -464

No files matched your search

-37
View File
@@ -299,17 +299,6 @@ describe("controller/src/ControlConnection", function() {
lib.logger.audit = originalAudit;
});
it("registers the host pairing and revoke handlers", function() {
for (const Request of [
lib.HostRevokeRequest,
lib.HostPairingListRequest,
lib.HostPairingApproveRequest,
lib.HostPairingRejectRequest,
]) {
assert(connection._requestHandlers.has(Request), `${Request.name} is not handled`);
}
});
describe(".handleHostRevokeRequest()", function() {
let record;
let broadcasts;
@@ -448,26 +437,6 @@ describe("controller/src/ControlConnection", function() {
assert.equal(controller.hostPairing.poll(requestId).hostId, hostId);
assert(audits.some(msg => msg.startsWith(`User admin approved host Host A (${hostId})`)));
});
it("approves a request with a different name", async function() {
const { requestId, code } = controller.hostPairing.create("Host A", "", "10.0.0.1");
const hostId = await connection.handleHostPairingApproveRequest(
new lib.HostPairingApproveRequest(code, "Renamed")
);
assert.equal(controller.hosts.get(hostId).name, "Renamed");
assert.equal(controller.hostPairing.poll(requestId).name, "Renamed");
});
it("rejects approving an unknown code or with an invalid name", async function() {
await assert.rejects(
connection.handleHostPairingApproveRequest(new lib.HostPairingApproveRequest("ZZZ-ZZZ")),
new lib.NotFoundError("No pending host request with code ZZZ-ZZZ"),
);
const { code } = controller.hostPairing.create("Host A", "", "10.0.0.1");
await assert.rejects(
connection.handleHostPairingApproveRequest(new lib.HostPairingApproveRequest(code, " ")),
new lib.ValidationError("Host name is required"),
);
assert.equal(controller.hosts.size, 0);
});
it("rejects a request", async function() {
const { requestId, code } = controller.hostPairing.create("Host A", "", "10.0.0.1");
await connection.handleHostPairingRejectRequest(new lib.HostPairingRejectRequest(code.toLowerCase()));
@@ -476,12 +445,6 @@ describe("controller/src/ControlConnection", function() {
assert.equal(controller.hosts.size, 0);
assert(audits.some(msg => msg.startsWith(`User admin rejected host Host A with code ${code}`)));
});
it("throws NotFoundError when rejecting an unknown code", async function() {
await assert.rejects(
connection.handleHostPairingRejectRequest(new lib.HostPairingRejectRequest("ZZZ-ZZZ")),
new lib.NotFoundError("No pending host request with code ZZZ-ZZZ"),
);
});
});
});
});
+1 -71
View File
@@ -5,7 +5,7 @@ import jwt from "jsonwebtoken";
import * as lib from "@clusterio/lib";
import { Controller, HostPairingManager, HostPairingError, HostRecord } from "@clusterio/controller";
import {
addressKey, normaliseCode, pollTimeoutMs, maxPendingMs, maxPending, maxPendingPerAddress,
pollTimeoutMs, maxPendingMs, maxPending, maxPendingPerAddress,
maxCreatesPerMinute, maxNameLength,
} from "@clusterio/controller/dist/node/src/HostPairingManager.js";
@@ -49,41 +49,6 @@ function assertPairingError(fn, status, message) {
}
describe("controller/src/HostPairingManager", function() {
describe("addressKey()", function() {
it("should return IPv4 addresses unchanged", function() {
assert.equal(addressKey("10.0.0.1"), "10.0.0.1");
});
it("should strip the IPv4 mapped prefix", function() {
assert.equal(addressKey("::ffff:10.0.0.1"), "10.0.0.1");
});
it("should group IPv6 addresses by their /64", function() {
assert.equal(addressKey("2001:db8:0:1::5"), "2001:db8:0:1::/64");
assert.equal(addressKey("2001:db8::1:ffff:0:0:3"), "2001:db8:0:1::/64");
assert.equal(addressKey("2001:0DB8:0000:0001:aaaa:bbbb:cccc:dddd"), "2001:db8:0:1::/64");
assert.equal(addressKey("2001:db8:0:2::5"), "2001:db8:0:2::/64");
});
it("should expand compressed groups at the start and end", function() {
assert.equal(addressKey("::1"), "0:0:0:0::/64");
assert.equal(addressKey("::"), "0:0:0:0::/64");
assert.equal(addressKey("fe80::1"), "fe80:0:0:0::/64");
assert.equal(addressKey("2001:db8::"), "2001:db8:0:0::/64");
});
it("should return other values unchanged", function() {
assert.equal(addressKey(""), "");
assert.equal(addressKey("not an address"), "not an address");
assert.equal(addressKey("::ffff:not-ipv4"), "::ffff:not-ipv4");
});
});
describe("normaliseCode()", function() {
it("should format codes as shown by the installer", function() {
assert.equal(normaliseCode("ABC-DEF"), "ABC-DEF");
assert.equal(normaliseCode("abcdef"), "ABC-DEF");
assert.equal(normaliseCode("abc-def"), "ABC-DEF");
assert.equal(normaliseCode(" abc def "), "ABC-DEF");
assert.equal(normaliseCode("a.b_c-d e/f"), "ABC-DEF");
});
});
describe("HostPairingManager.validateName()", function() {
it("should require a non-empty string", function() {
@@ -245,12 +210,6 @@ describe("controller/src/HostPairingManager", function() {
assert.equal(await manager.approve(code, undefined, "admin"), 1234);
assert.equal(manager.create("Host B", "", "10.0.0.2").code, "333-333");
});
it("should use distinct request ids", function() {
const first = manager.create("Host A", "", "10.0.0.1");
const second = manager.create("Host A", "", "10.0.0.1");
assert.notEqual(first.requestId, second.requestId);
assert.notEqual(first.code, second.code);
});
});
describe("limits", function() {
@@ -568,14 +527,6 @@ describe("controller/src/HostPairingManager", function() {
assert.equal(manager.requests.has(code), false);
assertPairingError(() => manager.poll(requestId), 404);
});
it("should expire requests when creating and polling", function() {
const first = manager.create("Host A", "", "10.0.0.1");
idle(first.requestId, pollTimeoutMs + 1);
const second = manager.create("Host B", "", "10.0.0.1");
assert.equal(manager.requests.has(first.code), false);
idle(second.requestId, pollTimeoutMs + 1);
assertPairingError(() => manager.poll(second.requestId), 404);
});
it("should drop and warn about an approved host never picked up", async function() {
const { requestId, code } = manager.create("Host A", "", "10.0.0.1");
const hostId = await manager.approve(code, undefined, "admin");
@@ -595,27 +546,6 @@ describe("controller/src/HostPairingManager", function() {
assertPairingError(() => manager.poll(requestId), 404);
assert.equal(warnings.length, 0);
});
it("should forget creation times older than a minute", function() {
manager.create("Host A", "", "10.0.0.1");
assert.equal(manager._recentCreates.size, 1);
manager.prune();
assert.equal(manager._recentCreates.size, 1);
for (const times of manager._recentCreates.values()) {
times.forEach((ms, index) => { times[index] = ms - 60e3 - 1; });
}
manager.prune();
assert.equal(manager._recentCreates.size, 0);
});
});
describe(".start()/.stop()", function() {
it("should run an unreferenced prune interval while started", function() {
manager.start();
assert(manager._pruneInterval, "prune interval not started");
assert.equal(manager._pruneInterval.hasRef(), false);
manager.stop();
assert.equal(manager._pruneInterval, undefined);
});
});
describe("Controller integration", function() {
-16
View File
@@ -190,22 +190,6 @@ describe("controller/src/WsServer", function() {
assert.equal(socket.closed, null);
assert(wsServer.hostConnections.has(5));
});
it("should reject a token without an issue time", async function() {
addHost(5);
await register(jwt.sign({ aud: "host", host: 5 }, secret, { noTimestamp: true }));
assertRejected("Authentication failed: invalid token");
});
it("should reject a token for another host", async function() {
addHost(5);
addHost(6);
await register(controller.generateHostToken(6), 5);
assertRejected("Authentication failed: missmatched host id");
});
it("should reject a token signed with another secret", async function() {
addHost(5);
await register(jwt.sign({ aud: "host", host: 5 }, Buffer.from("OtherSecret")));
assertRejected("Authentication failed: invalid signature");
});
it("should reject a host revoked while its previous connection closes", async function() {
const record = addHost(5);
wsServer.hostConnections.set(5, {
-41
View File
@@ -447,23 +447,6 @@ describe("controller/src/routes", function() {
await assertResponse(await post("/cancel", { requestId: code }), 204);
assert(controller.hostPairing.requests.has(code), "cancelled by the code");
});
it("should respond with 400 without a requestId in the body", async function() {
const { requestId, code } = await createRequest();
await assertResponse(await post("/cancel", {}), 400, { error: "requestId is required" });
await assertResponse(
await post(`/cancel?requestId=${encodeURIComponent(requestId)}`, {}),
400, { error: "requestId is required" },
);
assert(controller.hostPairing.requests.has(code));
});
it("should respond with 503 in recovery mode", async function() {
const { requestId, code } = await createRequest();
controller.recoveryMode = true;
await assertResponse(
await post("/cancel", { requestId }), 503, { error: "Controller is in recovery mode" }
);
assert(controller.hostPairing.requests.has(code));
});
});
describe("request ids", function() {
@@ -502,36 +485,12 @@ describe("controller/src/routes", function() {
assert.equal((await check({ "Authorization": "Bearer invalid" })).status, 401);
assert.equal((await check({ "Authorization": `Bearer ${adminToken}` })).status, 401);
});
it("should respond with 401 for a host that is not registered", async function() {
const token = controller.generateHostToken(99);
assert.equal((await check({ "Authorization": `Bearer ${token}` })).status, 401);
});
it("should respond with 401 once the host is revoked", async function() {
const { hostId, token } = await controller.hostPairing.createHost("Host A");
await assertResponse(await check({ "Authorization": `Bearer ${token}` }), 200, { valid: true });
controller.hosts.delete(controller.hosts.get(hostId));
assert.equal((await check({ "Authorization": `Bearer ${token}` })).status, 401);
});
it("should respond with 401 for a token issued before tokenValidAfter", async function() {
const { hostId, token } = await controller.hostPairing.createHost("Host A");
const record = controller.hosts.getMutable(hostId);
record.tokenValidAfter = Math.floor((Date.now() + 60e3) / 1000);
controller.hosts.set(record);
assert.equal((await check({ "Authorization": `Bearer ${token}` })).status, 401);
});
});
describe("pairing flow", function() {
it("should pair a host approved while the installer polls", async function() {
const { requestId, code } = await createRequest("Host A");
await assertResponse(await post("/poll", { requestId }), 202, { status: "pending" });
const hostId = await controller.hostPairing.approve(code.toLowerCase(), undefined, "test");
const result = await (await post("/poll", { requestId })).json();
assert.deepEqual(result, { hostId, token: result.token, name: "Host A" });
await assertResponse(
await check({ "Authorization": `Bearer ${result.token}` }), 200, { valid: true }
);
});
});
});
describe("/api/upload-save", function() {
-145
View File
@@ -1,145 +0,0 @@
import assert from "node:assert/strict";
import * as lib from "@clusterio/lib";
import { hostCommands } from "@clusterio/ctl/dist/node/src/commands_host.js";
/** Stand-in for Ctl that answers requests from a map of handlers and records what was sent */
class FakeCtl {
constructor(handlers) {
this.handlers = new Map(handlers);
this.sent = [];
}
async send(request) {
return await this.sendTo("controller", request);
}
async sendTo(dst, request) {
this.sent.push(request);
const handler = this.handlers.get(request.constructor);
if (!handler) {
throw new Error(`Unexpected ${request.constructor.name}`);
}
return handler(request);
}
}
async function run(command, args, ctl) {
const lines = [];
const originalLog = console.log; // eslint-disable-line no-console
console.log = (...content) => lines.push(content.join(" ")); // eslint-disable-line no-console
try {
await hostCommands.get(command).run(args, ctl);
} finally {
console.log = originalLog; // eslint-disable-line no-console
}
return lines;
}
describe("ctl/commands_host", function() {
// Created when the request is handled, the ages printed depend on the current time.
const pending = () => [
new lib.HostPairingDetails("ABC-DEF", "new-box", "192.0.2.10", "2.0.3", Date.now() - 65500),
new lib.HostPairingDetails("XYZ-234", "other-box", "2001:db8::1", "", Date.now() - 3720500),
];
const hosts = [{ id: 7, name: "basement-box" }, { id: 12, name: "attic-box" }];
it("no longer has the token based commands", function() {
for (const name of ["generate-token", "revoke-token", "create-config"]) {
assert.equal(hostCommands.get(name), undefined, `host ${name} still exists`);
}
});
describe("host pending", function() {
it("should list pending requests with their age", async function() {
const ctl = new FakeCtl([[lib.HostPairingListRequest, pending]]);
const lines = await run("pending", {}, ctl);
const output = lines.join("\n");
assert.match(output, /code\s*\|\s*name\s*\|\s*address\s*\|\s*age/);
assert.match(output, /ABC-DEF\s*\|\s*new-box\s*\|\s*192\.0\.2\.10\s*\|\s*1m 5s/);
assert.match(output, /XYZ-234\s*\|\s*other-box\s*\|\s*2001:db8::1\s*\|\s*1h 2m/);
});
it("should say so when nothing is pending", async function() {
const ctl = new FakeCtl([[lib.HostPairingListRequest, () => []]]);
assert.deepEqual(await run("pending", {}, ctl), ["No pending host requests"]);
});
});
describe("host approve", function() {
it("should approve with the requested name and print it", async function() {
const ctl = new FakeCtl([
[lib.HostPairingListRequest, pending],
[lib.HostPairingApproveRequest, () => 42],
]);
const lines = await run("approve", { code: "abcdef" }, ctl);
const approve = ctl.sent.find(request => request instanceof lib.HostPairingApproveRequest);
assert.equal(approve.code, "abcdef");
assert.equal(approve.name, undefined);
assert.deepEqual(lines, ["Approved host new-box with id 42"]);
});
it("should pass --name and print it without listing requests", async function() {
const ctl = new FakeCtl([[lib.HostPairingApproveRequest, () => 43]]);
const lines = await run("approve", { code: "ABC-DEF", name: " renamed " }, ctl);
assert.equal(ctl.sent.length, 1);
assert.equal(ctl.sent[0].code, "ABC-DEF");
assert.equal(ctl.sent[0].name, " renamed ");
assert.deepEqual(lines, ["Approved host renamed with id 43"]);
});
it("should pass on errors from the controller", async function() {
const ctl = new FakeCtl([
[lib.HostPairingListRequest, () => []],
[lib.HostPairingApproveRequest, () => {
throw new lib.NotFoundError("No pending host request with code AAA-AAA");
}],
]);
await assert.rejects(
run("approve", { code: "AAA-AAA" }, ctl),
new lib.NotFoundError("No pending host request with code AAA-AAA"),
);
});
});
describe("host reject", function() {
it("should send the code to the controller", async function() {
const ctl = new FakeCtl([[lib.HostPairingRejectRequest, () => undefined]]);
assert.deepEqual(await run("reject", { code: "abc-def" }, ctl), []);
assert.equal(ctl.sent.length, 1);
assert(ctl.sent[0] instanceof lib.HostPairingRejectRequest);
assert.equal(ctl.sent[0].code, "abc-def");
});
});
describe("host revoke", function() {
function revokeCtl() {
return new FakeCtl([
[lib.HostListRequest, () => hosts],
[lib.HostRevokeRequest, () => undefined],
]);
}
it("should revoke a host given by name", async function() {
const ctl = revokeCtl();
await run("revoke", { host: "basement-box" }, ctl);
const revoke = ctl.sent.at(-1);
assert(revoke instanceof lib.HostRevokeRequest);
assert.equal(revoke.hostId, 7);
});
it("should revoke a host given by id", async function() {
const ctl = revokeCtl();
await run("revoke", { host: "12" }, ctl);
assert.equal(ctl.sent.at(-1).hostId, 12);
});
it("should fail for an unknown host", async function() {
const ctl = revokeCtl();
await assert.rejects(
run("revoke", { host: "missing" }, ctl),
new lib.CommandError("No host named missing"),
);
assert(!ctl.sent.some(request => request instanceof lib.HostRevokeRequest));
});
});
});
-9
View File
@@ -915,15 +915,6 @@ describe("Integration of Clusterio", function() {
});
});
describe("removed host token commands", function() {
it("should no longer exist", async function() {
slowTest(this);
for (const command of ["generate-token", "revoke-token", "create-config"]) {
await assert.rejects(execCtlProcess(`host ${command}`), new RegExp(`Unknown argument: ${command}`));
}
});
});
describe("instance list", function() {
it("runs", async function() {
await execCtl("instance list");
-67
View File
@@ -1,4 +1,3 @@
import assert from "node:assert/strict";
import { HostPairingDetails } from "@clusterio/lib";
import { testMatrix, testRoundTripJsonSerialisable } from "../../common.js";
@@ -16,70 +15,4 @@ describe("lib/data/HostPairingDetails", function () {
[undefined, true], // isDeleted
));
});
describe("constructor", function () {
it("should set defaults correctly", function () {
const details = new HostPairingDetails("ABC-DEF", "New Host", "10.0.0.1");
assert.equal(details.code, "ABC-DEF");
assert.equal(details.name, "New Host");
assert.equal(details.remoteAddress, "10.0.0.1");
assert.equal(details.version, "");
assert.equal(details.createdAtMs, 0);
assert.equal(details.updatedAtMs, 0);
assert.equal(details.isDeleted, false);
});
it("should use the code as the id", function () {
const details = new HostPairingDetails("ABC-DEF", "New Host", "10.0.0.1");
assert.equal(details.id, "ABC-DEF");
});
});
describe(".toJSON()", function () {
it("should only include required fields when optional fields are defaults", function () {
const details = new HostPairingDetails("ABC-DEF", "New Host", "10.0.0.1", "", 0, 0, false);
assert.deepEqual(details.toJSON(), {
code: "ABC-DEF",
name: "New Host",
remoteAddress: "10.0.0.1",
});
});
it("should include all optional fields when set", function () {
const details = new HostPairingDetails("ABC-DEF", "New Host", "10.0.0.1", "2.0.0", 100, 200, true);
assert.deepEqual(details.toJSON(), {
code: "ABC-DEF",
name: "New Host",
remoteAddress: "10.0.0.1",
version: "2.0.0",
createdAtMs: 100,
updatedAtMs: 200,
isDeleted: true,
});
});
it("should not include the id getter", function () {
const details = new HostPairingDetails("ABC-DEF", "New Host", "10.0.0.1");
assert.equal(Object.hasOwn(JSON.parse(JSON.stringify(details)), "id"), false);
});
});
describe("static fromJSON()", function () {
it("should construct minimal object", function () {
const details = HostPairingDetails.fromJSON({
code: "ABC-DEF",
name: "New Host",
remoteAddress: "10.0.0.1",
});
assert.equal(details.code, "ABC-DEF");
assert.equal(details.version, "");
assert.equal(details.createdAtMs, 0);
assert.equal(details.updatedAtMs, 0);
assert.equal(details.isDeleted, false);
});
});
});
-49
View File
@@ -192,39 +192,6 @@ describe("messages/controller", function() {
new lib.HostPairingDetails("GHJ-KMN", "Host B", "2001:db8::1", "", 0, 300, true),
];
it("defines the pairing messages for control links approving hosts", function() {
for (const Message of [
lib.HostPairingListRequest,
lib.HostPairingApproveRequest,
lib.HostPairingRejectRequest,
]) {
assert.equal(Message.type, "request", Message.name);
assert.equal(Message.src, "control", Message.name);
assert.equal(Message.dst, "controller", Message.name);
assert.equal(Message.permission, "core.host.approve", Message.name);
assert(lib.Link._requestsByClass.has(Message), `${Message.name} is not registered`);
}
assert.equal(lib.HostPairingUpdatesEvent.type, "event");
assert.equal(lib.HostPairingUpdatesEvent.src, "controller");
assert.equal(lib.HostPairingUpdatesEvent.dst, "control");
assert.equal(lib.HostPairingUpdatesEvent.permission, "core.host.approve");
assert(lib.Link._eventsByClass.has(lib.HostPairingUpdatesEvent));
});
it("removes the host token messages", function() {
for (const name of ["HostGenerateTokenRequest", "HostConfigCreateRequest", "HostRevokeTokensRequest"]) {
assert.equal(lib[name], undefined, `${name} is still exported`);
assert(!lib.Link._requestsByName.has(name), `${name} is still registered`);
}
});
it("defines the host permissions", function() {
for (const name of ["core.host.approve", "core.host.revoke"]) {
assert(lib.permissions.has(name), `${name} is not defined`);
assert.equal(lib.permissions.get(name).grantByDefault, false, `${name} is granted by default`);
}
for (const name of ["core.host.generate_token", "core.host.revoke_token", "core.host.create_config"]) {
assert.equal(lib.permissions.has(name), false, `${name} is still defined`);
}
});
it("round trips HostPairingListRequest responses", function() {
const validate = lib.compile(lib.HostPairingListRequest.Response.jsonSchema);
const json = JSON.parse(JSON.stringify(details));
@@ -245,22 +212,6 @@ describe("messages/controller", function() {
it("round trips HostPairingRejectRequest", function() {
testRoundTripJsonSerialisable(lib.HostPairingRejectRequest, [["ABC-DEF"], ["abcdef"]]);
});
it("approves a pending request", async function() {
controller.config.set("controller.auth_secret", Buffer.from("TestSecretDoNotUse").toString("base64"));
const { requestId, code } = controller.hostPairing.create("Host A", "", "10.0.0.1");
const list = await controlConnection.handleHostPairingListRequest(new lib.HostPairingListRequest());
assert.deepEqual(list.map(entry => entry.code), [code]);
const hostId = await controlConnection.handleHostPairingApproveRequest(
new lib.HostPairingApproveRequest(code)
);
assert.equal(controller.hosts.get(hostId).name, "Host A");
assert.equal(controller.hostPairing.poll(requestId).hostId, hostId);
});
it("rejects a pending request", async function() {
const { requestId, code } = controller.hostPairing.create("Host A", "", "10.0.0.1");
await controlConnection.handleHostPairingRejectRequest(new lib.HostPairingRejectRequest(code));
assert.throws(() => controller.hostPairing.poll(requestId), /Request was rejected/);
});
});
describe("InstanceConfigGetRequest", function() {
-7
View File
@@ -22,13 +22,6 @@ describe("messages/host", function() {
it("is round trip json serialisable", function() {
testRoundTripJsonSerialisable(lib.HostRevokeRequest, [[1], [2 ** 31 - 1]]);
});
it("is a control request requiring core.host.revoke", function() {
assert.equal(lib.HostRevokeRequest.type, "request");
assert.equal(lib.HostRevokeRequest.src, "control");
assert.equal(lib.HostRevokeRequest.dst, "controller");
assert.equal(lib.HostRevokeRequest.permission, "core.host.revoke");
assert(lib.Link._requestsByClass.has(lib.HostRevokeRequest));
});
});
describe("HostUpdateRequest", function() {
-22
View File
@@ -55,18 +55,6 @@ describe("web_ui/util/host_pairing", function() {
));
});
it("should check the permission after the connect handlers have run", async function() {
subscriber.subscribe(() => {});
await events.once(mockControl.connector, "send");
mockControl.connector.sentMessages.length = 0;
permitted = false;
mockControl.connector.emit("connect");
permitted = true;
await settle();
assert.equal(sentRequests().length, 1);
});
it("should resubscribe on account updates with permission", async function() {
subscriber.subscribe(() => {});
await events.once(mockControl.connector, "send");
@@ -128,15 +116,5 @@ describe("web_ui/util/host_pairing", function() {
assert.equal(subscriber.values.size, 1);
assert.equal(subscriber.lastUpdatedMs, 10);
});
it("should set synced false on drop and close", async function() {
subscriber.synced = true;
subscriber.handleConnectionEvent("drop");
assert.equal(subscriber.synced, false);
subscriber.synced = true;
subscriber.handleConnectionEvent("close");
assert.equal(subscriber.synced, false);
});
});
});