Files
oh-my-pi-telegram/tests/menu-settings.test.ts
evandro 6a6e75fe32 refactor: cut cyclomatic complexity of five hot spots and pin their behavior with tests
Risk labels, menu callback parsing, settings callbacks, table cell width and
markdown link targets now use tables and small helpers (CC <= 10).
2026-10-10 03:59:46 -03:00

370 lines
12 KiB
TypeScript

/**
* Regression tests for Telegram settings menu helpers
* Exercises settings text/markup, callback mutations, stale-message fallback, and runtime wiring
*/
import assert from "node:assert/strict";
import test from "node:test";
import {
buildProactivePushSettingsReplyMarkup,
buildProactivePushSettingsText,
buildAssistantRenderingSettingsReplyMarkup,
buildAssistantRenderingSettingsText,
buildDraftPreviewsSettingsReplyMarkup,
buildDraftPreviewsSettingsText,
buildTelegramSettingsMenuReplyMarkup,
buildTelegramSettingsMenuText,
buildTimeInjectionModeSettingsReplyMarkup,
buildVoiceReplyModeSettingsReplyMarkup,
createTelegramSettingsMenuRuntime,
handleTelegramSettingsMenuCallbackAction,
} from "../lib/menu-settings.ts";
test("Settings menu text and reply markup expose built-in controls", () => {
assert.equal(buildTelegramSettingsMenuText(), "<b>⚙️ Settings:</b>");
const markup = buildTelegramSettingsMenuReplyMarkup(
true,
false,
"manual",
"hidden",
undefined,
false,
);
assert.deepEqual(
markup.inline_keyboard.map((row) => row[0]?.callback_data),
[
"menu:back",
"settings:open:voice-reply",
"settings:open:time-injection",
"settings:open:draft-previews",
"settings:open:assistant-rendering",
"settings:open:proactive",
],
);
assert.equal(markup.inline_keyboard[1]?.[0]?.text, "👄 Voice reply: hidden");
assert.equal(
markup.inline_keyboard[2]?.[0]?.text,
"🕒 Time injection: hidden",
);
assert.equal(markup.inline_keyboard[3]?.[0]?.text, "📝 Draft previews: off");
assert.equal(markup.inline_keyboard[4]?.[0]?.text, "🧾 Rendering: rich");
assert.equal(markup.inline_keyboard[5]?.[0]?.text, "📌 Proactive push: on");
});
test("Settings detail markups show active values", () => {
assert.match(buildProactivePushSettingsText(true), /<code>on<\/code>/);
assert.match(buildDraftPreviewsSettingsText(false), /<code>off<\/code>/);
assert.equal(
buildDraftPreviewsSettingsReplyMarkup(true).inline_keyboard[1]?.[0]?.text,
"🟢 On",
);
assert.match(buildAssistantRenderingSettingsText("html"), /<code>html<\/code>/);
assert.equal(
buildAssistantRenderingSettingsReplyMarkup("rich").inline_keyboard[1]?.[0]
?.text,
"🟢 rich",
);
assert.equal(
buildProactivePushSettingsReplyMarkup(false).inline_keyboard[1]?.[1]?.text,
"🟡 Off",
);
assert.equal(
buildTimeInjectionModeSettingsReplyMarkup("interval")
.inline_keyboard[3]?.[0]?.text,
"🟢 interval",
);
assert.equal(
buildVoiceReplyModeSettingsReplyMarkup("mirror", true)
.inline_keyboard[3]?.[0]?.text,
"🟢 mirror",
);
assert.equal(
buildVoiceReplyModeSettingsReplyMarkup("manual", false)
.inline_keyboard[1]?.[0]?.text,
"🟢 hidden",
);
});
test("Settings callback action mutates voice, time, and proactive settings", async () => {
const calls: string[] = [];
const deps = {
isProactivePushEnabled: () => false,
getVoiceReplyMode: () => "manual" as const,
isVoiceReplyModeConfigured: () => true,
getTimeInjectionMode: () => "hidden" as const,
areDraftPreviewsEnabled: () => false,
getAssistantRenderingMode: () => "rich" as const,
setProactivePushEnabled: async (enabled: boolean) => {
calls.push(`proactive:${enabled}`);
},
setDraftPreviewsEnabled: async (enabled: boolean) => {
calls.push(`draft-previews:${enabled}`);
},
setAssistantRenderingMode: async (mode: "rich" | "html") => {
calls.push(`rendering:${mode}`);
},
setVoiceReplyMode: async (
mode: "manual" | "mirror" | "always" | undefined,
) => {
calls.push(`voice:${mode ?? "hidden"}`);
},
setTimeInjectionMode: async (mode: "hidden" | "always" | "interval") => {
calls.push(`time:${mode}`);
},
updateSettingsMessage: async (text: string) => {
calls.push(`update:${text.split("\n")[0]}`);
},
answerCallbackQuery: async (_id: string, text?: string) => {
calls.push(`answer:${text ?? ""}`);
},
};
assert.equal(
await handleTelegramSettingsMenuCallbackAction(
"q1",
"settings:set:voice-reply:hidden",
deps,
),
true,
);
assert.equal(
await handleTelegramSettingsMenuCallbackAction(
"q2",
"settings:set:time:off",
deps,
),
true,
);
assert.equal(
await handleTelegramSettingsMenuCallbackAction(
"q3",
"settings:set:draft-previews:on",
deps,
),
true,
);
assert.equal(
await handleTelegramSettingsMenuCallbackAction(
"q4",
"settings:set:assistant-rendering:html",
deps,
),
true,
);
assert.equal(
await handleTelegramSettingsMenuCallbackAction(
"q5",
"settings:set:proactive:on",
deps,
),
true,
);
assert.equal(
await handleTelegramSettingsMenuCallbackAction("q6", "other", deps),
false,
);
assert.deepEqual(calls, [
"voice:hidden",
"update:<b>👄 Voice reply mode:</b> <code>manual</code>",
"answer:Voice reply mode: hidden",
"time:hidden",
"update:<b>🕒 Time injection mode:</b> <code>hidden</code>",
"answer:Time injection: hidden",
"draft-previews:true",
"update:<b>📝 Draft previews:</b> <code>off</code>",
"answer:Draft previews enabled",
"rendering:html",
"update:<b>🧾 Assistant rendering:</b> <code>rich</code>",
"answer:Rendering: html",
"proactive:true",
"update:<b>📌 Proactive push:</b> <code>off</code>",
"answer:Proactive push enabled",
]);
});
test("Settings runtime opens menus and applies stale-message fallback toggles", async () => {
const state: any = {
chatId: 1,
messageId: 2,
mode: "status",
page: 0,
scope: "all",
scopedModels: [],
allModels: [],
};
const calls: string[] = [];
const runtime = createTelegramSettingsMenuRuntime({
isProactivePushEnabled: () => true,
getVoiceReplyMode: () => "manual",
isVoiceReplyModeConfigured: () => true,
getTimeInjectionMode: () => "hidden",
areDraftPreviewsEnabled: () => false,
getAssistantRenderingMode: () => "rich",
setProactivePushEnabled: async (enabled) => {
calls.push(`proactive:${enabled}`);
},
setDraftPreviewsEnabled: async (enabled) => {
calls.push(`draft-previews:${enabled}`);
},
setAssistantRenderingMode: async (mode) => {
calls.push(`rendering:${mode}`);
},
setVoiceReplyMode: async (mode) => {
calls.push(`voice:${mode ?? "hidden"}`);
},
setTimeInjectionMode: async (mode) => {
calls.push(`time:${mode}`);
},
getModelMenuState: async () => state,
getStoredModelMenuState: () => undefined,
storeModelMenuState: (nextState) => calls.push(`store:${nextState.mode}`),
editInteractiveMessage: async () => {
calls.push("edit");
},
sendInteractiveMessage: async (_chatId, _text, mode) => {
calls.push(`send:${mode}`);
return 99;
},
answerCallbackQuery: async (_id, text) => {
calls.push(`answer:${text ?? ""}`);
},
});
await runtime.openSettingsMenu(1, 2, "ctx");
assert.equal(state.messageId, 99);
assert.equal(state.mode, "settings");
assert.equal(
await runtime.handleCallbackQuery(
{ id: "q1", data: "settings:set:voice-reply:always" },
"ctx",
),
true,
);
assert.equal(
await runtime.handleCallbackQuery(
{ id: "q2", data: "settings:set:time:off" },
"ctx",
),
true,
);
assert.deepEqual(calls, [
"send:html",
"store:settings",
"voice:always",
"answer:Voice reply mode: always",
"time:hidden",
"answer:Time injection: hidden",
]);
});
function createSettingsCallbackHarness() {
const calls: string[] = [];
const deps = {
isProactivePushEnabled: () => false,
getVoiceReplyMode: () => "manual" as const,
isVoiceReplyModeConfigured: () => true,
getTimeInjectionMode: () => "hidden" as const,
areDraftPreviewsEnabled: () => false,
getAssistantRenderingMode: () => "rich" as const,
setProactivePushEnabled: async (enabled: boolean) => {
calls.push(`proactive:${enabled}`);
},
setDraftPreviewsEnabled: async (enabled: boolean) => {
calls.push(`draft-previews:${enabled}`);
},
setAssistantRenderingMode: async (mode: "rich" | "html") => {
calls.push(`rendering:${mode}`);
},
setVoiceReplyMode: async (
mode: "manual" | "mirror" | "always" | undefined,
) => {
calls.push(`voice:${mode ?? "hidden"}`);
},
setTimeInjectionMode: async (mode: "hidden" | "always" | "interval") => {
calls.push(`time:${mode}`);
},
updateSettingsMessage: async (text: string) => {
calls.push(`update:${text.split("\n")[0]}`);
},
answerCallbackQuery: async (_id: string, text?: string) => {
calls.push(`answer:${text ?? ""}`);
},
};
return { calls, deps };
}
test("Settings callback action opens each page, including legacy aliases", async () => {
const pages: Array<[string, string]> = [
["settings:list", "update:<b>⚙️ Settings:</b>"],
["settings:open:proactive", "update:<b>📌 Proactive push:</b> <code>off</code>"],
["settings:open:draft-previews", "update:<b>📝 Draft previews:</b> <code>off</code>"],
["settings:open:rich-drafts", "update:<b>📝 Draft previews:</b> <code>off</code>"],
["settings:open:assistant-rendering", "update:<b>🧾 Assistant rendering:</b> <code>rich</code>"],
["settings:open:voice-reply", "update:<b>👄 Voice reply mode:</b> <code>manual</code>"],
["settings:open:time-injection", "update:<b>🕒 Time injection mode:</b> <code>hidden</code>"],
["settings:open:time", "update:<b>🕒 Time injection mode:</b> <code>hidden</code>"],
];
for (const [data, update] of pages) {
const { calls, deps } = createSettingsCallbackHarness();
assert.equal(
await handleTelegramSettingsMenuCallbackAction("q", data, deps),
true,
data,
);
assert.deepEqual(calls, [update, "answer:"], data);
}
});
test("Settings callback action applies each mode and the inverse branch of each toggle", async () => {
const cases: Array<[string, string[]]> = [
["settings:set:voice-reply:mirror", ["voice:mirror", "update:<b>👄 Voice reply mode:</b> <code>manual</code>", "answer:Voice reply mode: mirror"]],
["settings:set:voice-reply:always", ["voice:always", "update:<b>👄 Voice reply mode:</b> <code>manual</code>", "answer:Voice reply mode: always"]],
["settings:set:time-injection:interval", ["time:interval", "update:<b>🕒 Time injection mode:</b> <code>hidden</code>", "answer:Time injection: interval"]],
["settings:set:time-injection:off", ["time:hidden", "update:<b>🕒 Time injection mode:</b> <code>hidden</code>", "answer:Time injection: hidden"]],
["settings:set:time:always", ["time:always", "update:<b>🕒 Time injection mode:</b> <code>hidden</code>", "answer:Time injection: always"]],
["settings:set:draft-previews:off", ["draft-previews:false", "update:<b>📝 Draft previews:</b> <code>off</code>", "answer:Draft previews disabled"]],
["settings:set:rich-drafts:on", ["draft-previews:true", "update:<b>📝 Draft previews:</b> <code>off</code>", "answer:Draft previews enabled"]],
["settings:set:assistant-rendering:rich", ["rendering:rich", "update:<b>🧾 Assistant rendering:</b> <code>rich</code>", "answer:Rendering: rich"]],
["settings:set:proactive:off", ["proactive:false", "update:<b>📌 Proactive push:</b> <code>off</code>", "answer:Proactive push disabled"]],
];
for (const [data, expected] of cases) {
const { calls, deps } = createSettingsCallbackHarness();
assert.equal(
await handleTelegramSettingsMenuCallbackAction("q", data, deps),
true,
data,
);
assert.deepEqual(calls, expected, data);
}
});
test("Settings callback action only acknowledges invalid or unknown settings values", async () => {
for (const data of [
"settings:set:voice-reply:bogus",
"settings:set:time:bogus",
"settings:set:assistant-rendering:bogus",
"settings:set:draft-previews:maybe",
"settings:set:proactive:maybe",
"settings:unknown",
]) {
const { calls, deps } = createSettingsCallbackHarness();
assert.equal(
await handleTelegramSettingsMenuCallbackAction("q", data, deps),
true,
data,
);
assert.deepEqual(calls, ["answer:"], data);
}
const { calls, deps } = createSettingsCallbackHarness();
assert.equal(
await handleTelegramSettingsMenuCallbackAction("q", undefined, deps),
false,
);
assert.deepEqual(calls, []);
});