Lumi/scripts/verify-command-preview-confirmations.js
2026-06-12 11:54:46 +02:00

138 lines
5.3 KiB
JavaScript

const assert = require("assert");
const fs = require("fs");
const path = require("path");
const {
generateCommandPreview,
findDynamicSegments,
normalizePreviewText,
previewParts
} = require("../src/services/command-preview");
const {
DELAY_MS,
issueConfirmation,
consumeConfirmation,
isDestructivePath
} = require("../src/services/destructive-confirm");
const { db, migrate } = require("../src/services/db");
async function run() {
migrate();
const columns = db.prepare("PRAGMA table_info(custom_commands)").all().map((row) => row.name);
for (const column of [
"preview_text",
"preview_status",
"preview_error",
"preview_generated_at",
"preview_dynamic_segments"
]) {
assert(columns.includes(column), `Missing custom command preview column: ${column}`);
}
const preview = await generateCommandPreview({
language: "js",
code: `
function run(ctx) {
ctx.reply("Hello " + ctx.user.username);
return "Balance 1234, id " + ctx.user.id + ", date " + new Date().toISOString();
}
`
});
assert.equal(preview.preview_status, "ready");
assert.match(preview.preview_text, /Hello some_user/);
assert.equal(preview.preview_text.includes("Some User"), false);
const segments = JSON.parse(preview.preview_dynamic_segments);
for (const type of ["username", "number", "id", "date"]) {
assert(segments.some((segment) => segment.type === type), `Missing ${type} dynamic segment`);
}
assert(previewParts(preview.preview_text, segments).some((part) => part.dynamic));
assert.equal(normalizePreviewText("Some User and some-user"), "some_user and some_user");
assert(findDynamicSegments("some_user 42 user_123 2026-01-02T12:34:56.000Z").length, 4);
const noSideEffects = await generateCommandPreview({
language: "js",
code: `
async function run(ctx) {
await ctx.economy.add(ctx.user.id, 999999);
await ctx.inventory.remove(ctx.user.id, "all");
await ctx.moderation.ban(ctx.user.id);
await ctx.files.write("outside.txt", "blocked");
ctx.db.prepare("DELETE FROM users").run();
return "Balance " + await ctx.economy.getBalance(ctx.user.id);
}
`
});
assert.equal(noSideEffects.preview_status, "ready");
assert.equal(noSideEffects.preview_text, "Balance 1234");
const blocked = await generateCommandPreview({
language: "js",
code: "function run() { return process.cwd(); }"
});
assert.equal(blocked.preview_status, "unavailable");
assert.match(blocked.preview_error, /blocks filesystem, network, process/i);
const blockedImport = await generateCommandPreview({
language: "python",
code: "import os\ndef run(ctx):\n return os.getcwd()"
});
assert.equal(blockedImport.preview_status, "unavailable");
assert.match(blockedImport.preview_error, /blocks imports/i);
const timedOut = await generateCommandPreview({
language: "js",
code: "function run() { while (true) {} }"
});
assert.equal(timedOut.preview_status, "unavailable");
assert.match(timedOut.preview_error, /timed out/i);
const req = fakeRequest();
assert.equal(isDestructivePath("/admin/commands/1/delete"), true);
assert.equal(isDestructivePath("/admin/commands/1/update"), false);
const first = issueConfirmation(req, "/admin/commands/1/delete");
assert.equal(first.delay_seconds, DELAY_MS / 1000);
assert.equal(consumeConfirmation(req, "/admin/commands/1/delete", first.token).reason, "too_early");
const second = issueConfirmation(req, "/admin/commands/1/delete");
req.session.destructive_confirmations[second.token].not_before = Date.now() - 1;
assert.equal(consumeConfirmation(req, "/admin/pages/1/delete", second.token).reason, "action_mismatch");
const third = issueConfirmation(req, "/admin/commands/1/delete");
req.session.destructive_confirmations[third.token].not_before = Date.now() - 1;
assert.equal(consumeConfirmation(req, "/admin/commands/1/delete", third.token).valid, true);
assert.equal(consumeConfirmation(req, "/admin/commands/1/delete", third.token).valid, false);
const appScript = fs.readFileSync(path.join(__dirname, "..", "src", "web", "public", "app.js"), "utf8");
const layout = fs.readFileSync(path.join(__dirname, "..", "src", "web", "views", "partials", "layout-bottom.ejs"), "utf8");
const commandView = fs.readFileSync(path.join(__dirname, "..", "src", "web", "views", "admin-commands.ejs"), "utf8");
assert(appScript.includes("Confirm in ${remaining}"));
assert(appScript.includes('button.disabled = remaining > 0'));
assert(appScript.includes('fetch("/api/destructive-confirmations"'));
assert(appScript.includes("event.preventDefault();"));
assert(appScript.includes("expiryTimer"));
assert(appScript.includes("resetDestructive"));
assert(layout.includes("data-destructive-modal"));
assert(layout.includes("data-destructive-confirm disabled"));
assert(commandView.includes("Preview unavailable"));
assert(commandView.includes("preview-dynamic"));
assert.equal(commandView.includes('"Advanced (" + command.language + ")"'), false);
console.log("Command preview and destructive confirmation verification passed.");
}
function fakeRequest() {
return {
session: {
user: { id: "admin-user", isAdmin: true }
},
body: {},
get() {
return null;
}
};
}
run().catch((error) => {
console.error(error);
process.exitCode = 1;
});