mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-04-17 15:20:58 +00:00
feat: scaffold plug-n-play telegram bot on cloudflare workers
grammY-based bot with a module plugin system loaded from the MODULES env var. Three command visibility levels (public/protected/private) share a unified command namespace with conflict detection at registry build. - 4 initial modules (util, wordle, loldle, misc); util fully implemented, others are stubs proving the plugin system end-to-end - util: /info (chat/thread/sender ids) + /help (pure renderer over the registry, HTML parse mode, escapes user-influenced strings) - KVStore interface with CFKVStore and a per-module prefixing factory; getJSON/putJSON convenience helpers; other backends drop in via one file - Webhook at POST /webhook with secret-token validation via grammY's webhookCallback; no admin HTTP surface - Post-deploy register script (npm run deploy = wrangler deploy && node --env-file=.env.deploy scripts/register.js) for setWebhook and setMyCommands; --dry-run flag for preview - 56 vitest unit tests across 7 suites covering registry, db wrapper, dispatcher, help renderer, validators, and HTML escaper - biome for lint + format; phased implementation plan under plans/
This commit is contained in:
104
tests/modules/util/help-command.test.js
Normal file
104
tests/modules/util/help-command.test.js
Normal file
@@ -0,0 +1,104 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { renderHelp } from "../../../src/modules/util/help-command.js";
|
||||
|
||||
const noop = async () => {};
|
||||
|
||||
/**
|
||||
* Build a synthetic registry directly — no loader involved. Lets us test
|
||||
* the pure renderer with exact inputs.
|
||||
*/
|
||||
function makeRegistry(modules) {
|
||||
const publicCommands = new Map();
|
||||
const protectedCommands = new Map();
|
||||
const privateCommands = new Map();
|
||||
const allCommands = new Map();
|
||||
|
||||
for (const mod of modules) {
|
||||
for (const cmd of mod.commands) {
|
||||
const entry = { module: mod, cmd, visibility: cmd.visibility };
|
||||
allCommands.set(cmd.name, entry);
|
||||
if (cmd.visibility === "public") publicCommands.set(cmd.name, entry);
|
||||
else if (cmd.visibility === "protected") protectedCommands.set(cmd.name, entry);
|
||||
else privateCommands.set(cmd.name, entry);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
publicCommands,
|
||||
protectedCommands,
|
||||
privateCommands,
|
||||
allCommands,
|
||||
modules,
|
||||
};
|
||||
}
|
||||
|
||||
const cmd = (name, visibility, description) => ({
|
||||
name,
|
||||
visibility,
|
||||
description,
|
||||
handler: noop,
|
||||
});
|
||||
|
||||
describe("renderHelp", () => {
|
||||
it("groups commands by module in env.MODULES order", () => {
|
||||
const reg = makeRegistry([
|
||||
{ name: "a", commands: [cmd("one", "public", "A-one")] },
|
||||
{ name: "b", commands: [cmd("two", "public", "B-two")] },
|
||||
]);
|
||||
const out = renderHelp(reg);
|
||||
const aIdx = out.indexOf("<b>a</b>");
|
||||
const bIdx = out.indexOf("<b>b</b>");
|
||||
expect(aIdx).toBeGreaterThanOrEqual(0);
|
||||
expect(bIdx).toBeGreaterThanOrEqual(0);
|
||||
expect(aIdx).toBeLessThan(bIdx);
|
||||
});
|
||||
|
||||
it("appends (protected) suffix to protected commands", () => {
|
||||
const reg = makeRegistry([{ name: "a", commands: [cmd("admin", "protected", "Admin tool")] }]);
|
||||
const out = renderHelp(reg);
|
||||
expect(out).toContain("/admin — Admin tool (protected)");
|
||||
});
|
||||
|
||||
it("hides modules whose only commands are private", () => {
|
||||
const reg = makeRegistry([
|
||||
{ name: "a", commands: [cmd("visible", "public", "V")] },
|
||||
{ name: "b", commands: [cmd("hidden", "private", "H")] },
|
||||
]);
|
||||
const out = renderHelp(reg);
|
||||
expect(out).toContain("<b>a</b>");
|
||||
expect(out).not.toContain("<b>b</b>");
|
||||
expect(out).not.toContain("hidden");
|
||||
});
|
||||
|
||||
it("NEVER leaks private command names into output", () => {
|
||||
const reg = makeRegistry([
|
||||
{
|
||||
name: "a",
|
||||
commands: [cmd("show", "public", "Public"), cmd("secret", "private", "Secret")],
|
||||
},
|
||||
]);
|
||||
const out = renderHelp(reg);
|
||||
expect(out).toContain("/show");
|
||||
expect(out).not.toContain("/secret");
|
||||
expect(out).not.toContain("Secret");
|
||||
});
|
||||
|
||||
it("HTML-escapes module name and description", () => {
|
||||
const reg = makeRegistry([
|
||||
{
|
||||
name: "a&b",
|
||||
commands: [cmd("foo", "public", "runs <script>")],
|
||||
},
|
||||
]);
|
||||
const out = renderHelp(reg);
|
||||
expect(out).toContain("<b>a&b</b>");
|
||||
expect(out).toContain("runs <script>");
|
||||
// The literal unescaped sequence must NOT appear in output.
|
||||
expect(out).not.toContain("<script>");
|
||||
});
|
||||
|
||||
it("returns a placeholder when no commands are visible", () => {
|
||||
const reg = makeRegistry([{ name: "a", commands: [cmd("hidden", "private", "H")] }]);
|
||||
expect(renderHelp(reg)).toBe("no commands registered");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user