mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 15:04:52 +09:00
Cover the player-sync ctl commands in tests
Codecov flagged ctl.ts at 21% patch coverage. Run each command through a fake control link with canned responses and check what it prints and sends. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
a81d98e981
commit
9cc5c64852
1 file changed
+135
@@ -1,8 +1,17 @@
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { CtlHooks } from "@clusterio/ctl";
|
||||
|
||||
import * as mock from "../../../test/mock.js";
|
||||
import { testRoundTripJsonSerialisable, testMatrix } from "../../../test/common.js";
|
||||
import * as msg from "../dist/node/messages.js";
|
||||
import { summarizeInventory, summarizePlayerInventories } from "../dist/node/player_data.js";
|
||||
import ctlEntrypoint from "../dist/node/ctl.js";
|
||||
import { plugin as info } from "../dist/node/index.js";
|
||||
|
||||
|
||||
function playerData(name, generation, extra = {}) {
|
||||
@@ -56,6 +65,10 @@ describe("player_sync plugin", function() {
|
||||
});
|
||||
it("should handle empty inventories", function() {
|
||||
assert.deepEqual(summarizeInventory({}), []);
|
||||
assert.deepEqual(
|
||||
summarizeInventory({ i: [{ n: "wood" }] }),
|
||||
[{ name: "wood", quality: undefined, count: 0 }],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,6 +90,128 @@ describe("player_sync plugin", function() {
|
||||
});
|
||||
it("should handle missing inventories", function() {
|
||||
assert.deepEqual(summarizePlayerInventories(playerData("player", 1)), []);
|
||||
assert.deepEqual(summarizePlayerInventories(playerData("player", 1, { character: {} })), []);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ctl commands", function() {
|
||||
let commands;
|
||||
let sent;
|
||||
let printed;
|
||||
let responses;
|
||||
const ctl = {
|
||||
async send(message) {
|
||||
sent.push(message);
|
||||
const handler = responses.get(message.constructor);
|
||||
if (!handler) {
|
||||
throw new Error(`Unexpected ${message.constructor.name}`);
|
||||
}
|
||||
return handler(message);
|
||||
},
|
||||
};
|
||||
const storedData = playerData("alice", 2, {
|
||||
character: { inventories: { main: { i: [{ n: "wood", c: 5, q: "rare" }, { e: "0eNq" }] } } },
|
||||
});
|
||||
let tempDir;
|
||||
|
||||
before(async function() {
|
||||
const root = new lib.CommandTree({ name: "clusterioctl", description: "root" });
|
||||
const hooks = new CtlHooks(new mock.MockLogger());
|
||||
await ctlEntrypoint({ hooks, logger: new mock.MockLogger(), plugin: info });
|
||||
await hooks.addCommands.invoke(root);
|
||||
commands = root.subCommands.get("player-sync").subCommands;
|
||||
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "player_sync-ctl-"));
|
||||
});
|
||||
after(async function() {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
beforeEach(function() {
|
||||
sent = [];
|
||||
printed = [];
|
||||
responses = new Map([
|
||||
[lib.InstanceDetailsGetRequest, request => ({ id: request.instanceId, name: "Test" })],
|
||||
[msg.ListPlayersRequest, () => [
|
||||
new msg.PlayerEntry("bob", undefined, undefined, 1),
|
||||
new msg.PlayerEntry("alice", 2, 100),
|
||||
]],
|
||||
[msg.GetPlayerDataRequest, request => new msg.GetPlayerDataResponse(
|
||||
request.playerName === "alice" ? storedData : null, request.playerName === "alice" ? 1 : undefined
|
||||
)],
|
||||
[msg.SetPlayerDataRequest, () => new msg.SetPlayerDataResponse(3)],
|
||||
[msg.DeletePlayerDataRequest, () => undefined],
|
||||
[msg.ForceReleaseRequest, () => undefined],
|
||||
]);
|
||||
});
|
||||
|
||||
async function run(name, args = {}) {
|
||||
/* eslint-disable no-console */
|
||||
const original = console.log;
|
||||
console.log = (...values) => printed.push(values.join(" "));
|
||||
try {
|
||||
await commands.get(name).run(args, ctl);
|
||||
} finally {
|
||||
console.log = original;
|
||||
}
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
|
||||
it("list should print sorted entries", async function() {
|
||||
await run("list");
|
||||
assert.deepEqual(printed, [
|
||||
"name | generation | size | acquired by",
|
||||
`alice | 2 | ${lib.formatBytes(100)} | -`,
|
||||
"bob | - | - | Test (1)",
|
||||
]);
|
||||
});
|
||||
it("show should print a summary", async function() {
|
||||
await run("show", { player: "alice" });
|
||||
assert.deepEqual(printed, [
|
||||
"acquired by: Test (1)",
|
||||
"generation: 2",
|
||||
`size: ${lib.formatBytes(JSON.stringify(storedData).length)}`,
|
||||
"controller: character",
|
||||
"force: player",
|
||||
"main:",
|
||||
" 5 wood (rare)",
|
||||
` 1 exported item ${lib.formatBytes(4)}`,
|
||||
]);
|
||||
});
|
||||
it("show should handle players without data", async function() {
|
||||
await run("show", { player: "carol" });
|
||||
assert.deepEqual(printed, ["acquired by: -", "No player data stored for carol"]);
|
||||
});
|
||||
it("export should print or write JSON", async function() {
|
||||
await run("export", { player: "alice" });
|
||||
assert.deepEqual(JSON.parse(printed.join("\n")), storedData);
|
||||
const file = path.join(tempDir, "alice.json");
|
||||
await run("export", { player: "alice", file });
|
||||
assert.deepEqual(JSON.parse(await fs.readFile(file, "utf8")), storedData);
|
||||
await assert.rejects(
|
||||
run("export", { player: "carol" }),
|
||||
new lib.CommandError("No player data stored for carol"),
|
||||
);
|
||||
});
|
||||
it("import should send the file contents", async function() {
|
||||
const file = path.join(tempDir, "import.json");
|
||||
await fs.writeFile(file, JSON.stringify({ name: "other", controller: "god" }));
|
||||
await run("import", { player: "alice", file });
|
||||
assert.deepEqual(sent, [
|
||||
new msg.SetPlayerDataRequest("alice", { name: "alice", controller: "god", generation: 0 }),
|
||||
]);
|
||||
assert.deepEqual(printed, ["Stored player data for alice as generation 3"]);
|
||||
});
|
||||
it("import should reject non-object JSON", async function() {
|
||||
const file = path.join(tempDir, "bad.json");
|
||||
await fs.writeFile(file, "[1]");
|
||||
await assert.rejects(
|
||||
run("import", { player: "alice", file }),
|
||||
new lib.CommandError("Player data must be a JSON object"),
|
||||
);
|
||||
});
|
||||
it("delete and release should send requests", async function() {
|
||||
await run("delete", { player: "alice" });
|
||||
await run("release", { player: "alice" });
|
||||
assert.deepEqual(sent, [new msg.DeletePlayerDataRequest("alice"), new msg.ForceReleaseRequest("alice")]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user