test(04-03): the four CS landmines + the CR1-CR15 /cari gate suite
- commands.test.mjs: commandSources gains commands/cari.ts; CS4 documents the SECOND inline-keyboard carve (venue card allowed, reply keyboard still banned in commands/); CS5 menu becomes help/privacy/cari; CS5b's future-stub ban narrows to /pergi_ke (reserved for Phase 5, D-01); CS6 registers cari+search in the alias set - cari.test.mjs NEW: CR1-CR15 — payload shapes captured through the real createBot + fake baseFetch (getMe included), plus source-introspection gates with the stripComments helper: venue fields, OPEN URL = configured origin + plain slug id, NO-CRYPTO law (D-11), one-message alternates, callback_data discipline, honest refusal, 128-char cap, always-answer, category-only logging (D-15), bilingual copy, registration script, swap semantics, hostile-id drop - register-commands.mjs: menu list gains cari (two fetch calls unchanged) - [Rule 1] location-fallback.ts: the message handler now passes non-location messages through (next) — it matched EVERY message and silently starved all downstream message-stage handlers, so /cari never fired through the real factory; caught by the fake-transport suite - suites: bot 65/65 (15 CR + 9 CS + all prior), web 33/33, search 57/57, bot:typecheck green
This commit is contained in:
+48
-15
@@ -9,11 +9,24 @@
|
||||
* (location / routes / history) and the stored TTL wording
|
||||
* CS4 D-02 structural gate — command tree source contains no reply-menu
|
||||
* or inline-button surface (grep: zero "keyboard"-shaped tokens)
|
||||
* CS5 D-03 menu gap-freedom — helpMenu lists exactly /help and /privacy,
|
||||
* and the registration script derives both scopes from it (no
|
||||
* inline description literals, no future-command stubs)
|
||||
* CARVE (the SECOND documented exception, after the GEO-02 reply
|
||||
* keyboard in handlers/location-fallback.ts): commands/cari.ts ships
|
||||
* the D-02/D-10 venue card whose INLINE keyboard (InlineKeyboard +
|
||||
* reply_markup with inline_keyboard: the OPEN IN ARAHARAH web_app
|
||||
* button + alternates) is a command's output — hiding it from this
|
||||
* gate would be the more dangerous choice, so the gate scans cari.ts
|
||||
* for the REPLY-keyboard shape specifically and still bans it there.
|
||||
* A reply keyboard remains a handlers/ surface only.
|
||||
* CS4b link previews — any command source calling ctx.reply( disables
|
||||
* link previews (a query echoed into a refusal must not become a
|
||||
* link preview)
|
||||
* CS5 D-03 menu gap-freedom — helpMenu lists exactly /help, /privacy,
|
||||
* and /cari, and the registration script derives all three scopes
|
||||
* from it (no inline description literals; /pergi_ke stays a banned
|
||||
* future stub — reserved for Phase 5, D-01)
|
||||
* CS6 D-04 alias completeness — command stack registers exactly
|
||||
* /start /mula /help /privacy /privasi (help unprefixed-alias only)
|
||||
* /start /mula /help /privacy /privasi /cari /search (help
|
||||
* unprefixed-alias only)
|
||||
*
|
||||
* Source-introspection style (privacy.test.mjs precedent): structural
|
||||
* claims are pinned by reading the shipped source, not by behavior mocks.
|
||||
@@ -27,9 +40,13 @@ import { test } from "./harness.mjs";
|
||||
const { strings, pickLocale } = await import("../src/i18n/strings.ts");
|
||||
|
||||
const BOT_SRC = path.join(fileURLToPath(new URL(import.meta.url)), "..", "..", "src", "bot");
|
||||
const commandSources = ["bot.ts", "commands/start.ts", "commands/help.ts", "commands/privacy.ts"].map((rel) =>
|
||||
path.join(BOT_SRC, ...rel.split("/")),
|
||||
);
|
||||
const commandSources = [
|
||||
"bot.ts",
|
||||
"commands/start.ts",
|
||||
"commands/help.ts",
|
||||
"commands/privacy.ts",
|
||||
"commands/cari.ts",
|
||||
].map((rel) => path.join(BOT_SRC, ...rel.split("/")));
|
||||
|
||||
/* ————— CS1 — en/ms key-set identity (D-05) ————— */
|
||||
|
||||
@@ -75,9 +92,25 @@ test("CS3 PRIV-01: stored-facts copy carries the TTL-minutes wording in both loc
|
||||
|
||||
/* ————— CS4 — D-02 structural gate ————— */
|
||||
|
||||
test("CS4 D-02: the command tree source has no reply-menu or inline-button surface", () => {
|
||||
test("CS4 D-02: the command tree has no reply-menu surface — the /cari inline-keyboard carve", () => {
|
||||
for (const file of commandSources) {
|
||||
const source = readFileSync(file, "utf8");
|
||||
if (file.endsWith("commands/cari.ts")) {
|
||||
// The documented carve (second exception after the GEO-02 reply
|
||||
// keyboard): the venue card's INLINE keyboard is allowed, but a
|
||||
// REPLY keyboard must never appear in a command source.
|
||||
assert.doesNotMatch(
|
||||
source,
|
||||
/reply_markup:\s*\{\s*keyboard/,
|
||||
"cari.ts — a reply-keyboard shape appeared (reply keyboards live in handlers/, not commands/)",
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
source,
|
||||
/ReplyKeyboardMarkup/,
|
||||
"cari.ts — ReplyKeyboardMarkup appeared (the venue card carries an INLINE keyboard only)",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
assert.doesNotMatch(
|
||||
source,
|
||||
/keyboard/i,
|
||||
@@ -110,13 +143,13 @@ function menuEntries(helpMenu) {
|
||||
});
|
||||
}
|
||||
|
||||
test("CS5 D-03: helpMenu lists exactly /help and /privacy — no future-command stubs", () => {
|
||||
test("CS5 D-03: helpMenu lists exactly /help, /privacy, and /cari — no future-command stubs", () => {
|
||||
for (const locale of ["en", "ms"]) {
|
||||
const entries = menuEntries(strings[locale].helpMenu);
|
||||
assert.deepEqual(
|
||||
entries.map((e) => e.command),
|
||||
["help", "privacy"],
|
||||
`${locale} menu must contain exactly the two live commands (D-03)`,
|
||||
["help", "privacy", "cari"],
|
||||
`${locale} menu must contain exactly the live commands, in helpMenu order (D-03; /pergi_ke stays reserved for Phase 5, D-01)`,
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -140,14 +173,14 @@ test("CS5 D-03: register-commands derives both scopes from helpMenu — two fetc
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
source,
|
||||
/cari|pergi_ke/,
|
||||
"no future-command stubs may be registered before their phases exist (D-03)",
|
||||
/pergi_ke/,
|
||||
"no future-command stubs may be registered before their phases exist (/pergi_ke stays reserved for Phase 5, D-01; /cari is a live command since 04-03)",
|
||||
);
|
||||
});
|
||||
|
||||
/* ————— CS6 — D-04 alias completeness on the command stack ————— */
|
||||
|
||||
test("CS6 D-04: command stack registers exactly /start /mula /help /privacy /privasi", () => {
|
||||
test("CS6 D-04: command stack registers exactly /start /mula /help /privacy /privasi /cari /search", () => {
|
||||
const registered = new Set();
|
||||
for (const file of commandSources) {
|
||||
const source = readFileSync(file, "utf8");
|
||||
@@ -157,7 +190,7 @@ test("CS6 D-04: command stack registers exactly /start /mula /help /privacy /pri
|
||||
}
|
||||
assert.deepEqual(
|
||||
[...registered].sort(),
|
||||
["help", "mula", "privasi", "privacy", "start"].sort(),
|
||||
["cari", "help", "mula", "privasi", "privacy", "search", "start"].sort(),
|
||||
"the command stack must be exactly the phase surface (D-04; no drift, no extras)",
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user