mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-04-17 19:22:09 +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:
46
tests/fakes/fake-kv-namespace.js
Normal file
46
tests/fakes/fake-kv-namespace.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @file fake-kv-namespace — in-memory `Map`-backed KVNamespace for tests.
|
||||
*
|
||||
* Matches the real CF KVNamespace shape that `CFKVStore` depends on:
|
||||
* get(key, {type: 'text'}) → string | null
|
||||
* put(key, value, opts?) → stores, records opts for assertion
|
||||
* delete(key) → removes
|
||||
* list({prefix, limit, cursor}) → { keys: [{name}], list_complete, cursor }
|
||||
*
|
||||
* `listLimit` is applied BEFORE `list_complete` is computed so tests can
|
||||
* exercise pagination.
|
||||
*/
|
||||
|
||||
export function makeFakeKv() {
|
||||
/** @type {Map<string, string>} */
|
||||
const store = new Map();
|
||||
/** @type {Array<{key: string, value: string, opts?: any}>} */
|
||||
const putLog = [];
|
||||
|
||||
return {
|
||||
store,
|
||||
putLog,
|
||||
async get(key, _opts) {
|
||||
return store.has(key) ? store.get(key) : null;
|
||||
},
|
||||
async put(key, value, opts) {
|
||||
store.set(key, value);
|
||||
putLog.push({ key, value, opts });
|
||||
},
|
||||
async delete(key) {
|
||||
store.delete(key);
|
||||
},
|
||||
async list({ prefix = "", limit = 1000, cursor } = {}) {
|
||||
const allKeys = [...store.keys()].filter((k) => k.startsWith(prefix)).sort();
|
||||
const start = cursor ? Number.parseInt(cursor, 10) : 0;
|
||||
const slice = allKeys.slice(start, start + limit);
|
||||
const nextStart = start + slice.length;
|
||||
const complete = nextStart >= allKeys.length;
|
||||
return {
|
||||
keys: slice.map((name) => ({ name })),
|
||||
list_complete: complete,
|
||||
cursor: complete ? undefined : String(nextStart),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user