Files
Evandro Fonseca Junior d1d04001d0
Release / release (push) Failing after 5s
Validate / validate (push) Failing after 5m21s
chore: fork @llblab/[email protected] as @evandrodevbr/oh-my-pi-telegram
Fork of upstream llblab/[email protected].0 (SHA afe09c5) repackaged as a
drop-in extension for can1357/oh-my-pi (omp >= 16.5.2).

Changes vs upstream:
- Rename package: @llblab/pi-telegram -> @evandrodevbr/oh-my-pi-telegram
- Bump version: 0.22.0 -> 0.22.0-evandro.1
- Update repository/homepage/bugs URLs to evandrodevbr
- Add omp field in package.json (displayName + compatibleWith)
- README: prepend 'Oh My Pi (omp) Compatibility' section with install
  commands, verified-components table, upstream lineage, and rationale
- CHANGELOG: add '0.22.0-evandro.1' entry under Unreleased
- docs/: rename @llblab/pi-telegram imports to @evandrodevbr/oh-my-pi-telegram
- tests/public-api.test.ts: rename package import paths

Gates re-run on the fork:
- npm run typecheck  -> tsc --noEmit (success)
- npm test           -> 1229 pass / 0 fail / 1 platform-only skip
- npm run audit      -> found 0 vulnerabilities
- npm run pack:check -> evandrodevbr-oh-my-pi-telegram-0.22.0-evandro.1.tgz

Verified:
- omp install --link ./pi-telegram -> Linked OK
- omp plugin list -> @evandrodevbr/[email protected].0-evandro.1

Upstream lineage: badlogic/pi-telegram -> llblab/pi-telegram (0.22.0) -> evandrodevbr/oh-my-pi-telegram (this fork)
2026-07-15 14:18:16 -03:00

398 lines
11 KiB
TypeScript

/**
* Regression tests for Telegram setup prompt helpers
* Exercises bot-token prompt defaults, setup success/failure, and prompt-runtime guard cleanup
*/
import assert from "node:assert/strict";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { createTelegramConfigStore } from "../lib/config.ts";
import {
TELEGRAM_BOT_TOKEN_INPUT_PLACEHOLDER,
createTelegramSetupPromptRuntime,
getTelegramBotTokenInputDefault,
getTelegramBotTokenPromptSpec,
runTelegramSetup,
type TelegramSetupConfig,
} from "../lib/setup.ts";
test("Setup token defaults prefer config, then env aliases, then placeholder", () => {
assert.equal(
getTelegramBotTokenInputDefault(
{ TELEGRAM_BOT_TOKEN: " env-token " },
" config-token ",
),
"config-token",
);
assert.equal(
getTelegramBotTokenInputDefault({ TELEGRAM_KEY: " env-key " }),
"env-key",
);
assert.equal(
getTelegramBotTokenInputDefault({}),
TELEGRAM_BOT_TOKEN_INPUT_PLACEHOLDER,
);
});
test("Setup prompt spec uses editor for real tokens and input for placeholder", () => {
assert.deepEqual(getTelegramBotTokenPromptSpec({}, "123:abc"), {
method: "editor",
value: "123:abc",
});
assert.deepEqual(getTelegramBotTokenPromptSpec({}), {
method: "input",
value: TELEGRAM_BOT_TOKEN_INPUT_PLACEHOLDER,
});
});
test("Setup runner validates token, persists config, starts polling, and updates status", async () => {
const calls: string[] = [];
let persisted: unknown;
const nextConfig = await runTelegramSetup({
hasUI: true,
env: {},
config: { allowedUserId: 42 },
promptInput: async (_label, value) => {
calls.push(`input:${value}`);
return "token";
},
promptEditor: async () => {
throw new Error("must not use editor for placeholder");
},
getMe: async (botToken) => {
calls.push(`getMe:${botToken}`);
return { ok: true, result: { id: 7, username: "demo_bot" } };
},
persistConfig: async (config) => {
persisted = config;
calls.push("persist");
},
notify: (message, level) => calls.push(`${level}:${message}`),
startPolling: () => ({ ok: true, message: "Polling started" }),
updateStatus: () => calls.push("status"),
});
assert.deepEqual(nextConfig, {
status: "success",
config: {
allowedUserId: 42,
botToken: "token",
botId: 7,
botUsername: "demo_bot",
},
});
assert.deepEqual(persisted, nextConfig.config);
assert.deepEqual(calls, [
`input:${TELEGRAM_BOT_TOKEN_INPUT_PLACEHOLDER}`,
"getMe:token",
"persist",
"info:Telegram bot connected: @demo_bot",
"info:Send /start to your bot in Telegram to pair this extension with your account.",
"info:Polling started",
"status",
]);
});
test("Setup runner reports invalid tokens without persisting or starting polling", async () => {
const calls: string[] = [];
const nextConfig = await runTelegramSetup({
hasUI: true,
env: {},
config: {},
promptInput: async () => "bad-token",
promptEditor: async () => "bad-token",
getMe: async () => ({ ok: false, description: "Unauthorized" }),
persistConfig: async () => {
calls.push("persist");
},
notify: (message, level) => calls.push(`${level}:${message}`),
startPolling: () => calls.push("start"),
updateStatus: () => calls.push("status"),
});
assert.deepEqual(nextConfig, { status: "validation-failed" });
assert.deepEqual(calls, ["error:Unauthorized"]);
});
test("Setup runner distinguishes cancellation and polling startup failure", async () => {
const baseDeps = {
hasUI: true,
env: {},
config: {},
promptEditor: async () => undefined,
getMe: async () => ({
ok: true,
result: { id: 7, username: "demo_bot" },
}),
persistConfig: async () => undefined,
notify: () => undefined,
updateStatus: () => undefined,
};
assert.deepEqual(
await runTelegramSetup({
...baseDeps,
hasUI: false,
promptInput: async () => "token",
startPolling: () => ({ ok: true }),
}),
{ status: "unavailable" },
);
assert.deepEqual(
await runTelegramSetup({
...baseDeps,
promptInput: async () => undefined,
startPolling: () => ({ ok: true }),
}),
{ status: "cancelled" },
);
const failed = await runTelegramSetup({
...baseDeps,
promptInput: async () => "token",
startPolling: () => ({ ok: false, message: "Polling unavailable" }),
});
assert.equal(failed.status, "polling-failed");
assert.equal(
failed.status === "polling-failed" && failed.config.botToken,
"token",
);
});
test("Setup prompt runtime stores config before starting polling", async () => {
const calls: string[] = [];
let currentToken: string | undefined;
const runtime = createTelegramSetupPromptRuntime({
env: {},
getConfig: () => ({}),
setConfig: (config) => {
currentToken = config.botToken;
calls.push(`set:${config.botToken}`);
},
setupGuard: {
start: () => true,
finish: () => calls.push("finish"),
},
getMe: async () => ({ ok: true, result: { id: 7, username: "demo_bot" } }),
persistConfig: async (config) => {
calls.push(`persist:${config.botToken}`);
},
startPolling: () => calls.push(`start:${currentToken ?? "missing"}`),
updateStatus: () => calls.push("status"),
});
await runtime({
hasUI: true,
ui: {
input: async () => "token",
editor: async () => "token",
notify: (message) => calls.push(`notify:${message}`),
},
});
assert.deepEqual(calls, [
"set:token",
"persist:token",
"notify:Telegram bot connected: @demo_bot",
"notify:Send /start to your bot in Telegram to pair this extension with your account.",
"start:token",
"status",
"finish",
]);
});
test("Setup prompt runtime rolls memory back when persistence fails", async () => {
const calls: string[] = [];
let config: TelegramSetupConfig = { botToken: "previous" };
const runtime = createTelegramSetupPromptRuntime({
env: {},
getConfig: () => config,
setConfig: (next) => {
config = { ...next };
calls.push(`set:${next.botToken}`);
},
setupGuard: {
start: () => true,
finish: () => calls.push("finish"),
},
getMe: async () => ({ ok: true, result: { id: 7, username: "demo_bot" } }),
persistConfig: async () => {
calls.push("persist");
throw new Error("disk full");
},
startPolling: () => calls.push("unexpected:start"),
updateStatus: () => calls.push("unexpected:status"),
recordRuntimeEvent: () => calls.push("record"),
});
await assert.rejects(
runtime({
hasUI: true,
ui: {
input: async () => "token",
editor: async () => "token",
notify: () => calls.push("unexpected:notify"),
},
}),
/disk full/,
);
assert.deepEqual(config, { botToken: "previous" });
assert.deepEqual(calls, [
"set:token",
"persist",
"set:previous",
"record",
"finish",
]);
});
test("Setup prompt runtime persists the first validated config to missing or empty files", async () => {
for (const initialFile of [undefined, "{}\n"]) {
const dir = await mkdtemp(join(tmpdir(), "pi-telegram-setup-first-run-"));
const configPath = join(dir, "telegram.json");
try {
if (initialFile !== undefined) await writeFile(configPath, initialFile);
const store = createTelegramConfigStore({ agentDir: dir, configPath });
await store.load();
const runtime = createTelegramSetupPromptRuntime({
env: { TELEGRAM_BOT_TOKEN: "env-token" },
getConfig: store.get,
setConfig: store.set,
setupGuard: { start: () => true, finish: () => {} },
getMe: async () => ({
ok: true,
result: { id: 77, username: "first_run_bot" },
}),
persistConfig: async () => store.persist(),
startPolling: () => ({ ok: true }),
updateStatus: () => {},
});
const result = await runtime({
hasUI: true,
ui: {
input: async () => "env-token",
editor: async () => "env-token",
notify: () => {},
},
});
assert.equal(result.status, "success");
assert.deepEqual(JSON.parse(await readFile(configPath, "utf8")), {
botToken: "env-token",
botUsername: "first_run_bot",
botId: 77,
});
} finally {
await rm(dir, { recursive: true, force: true });
}
}
});
test("Setup prompt runtime persists a first named profile without changing siblings", async () => {
const dir = await mkdtemp(join(tmpdir(), "pi-telegram-setup-profile-"));
const configPath = join(dir, "telegram.json");
try {
const store = createTelegramConfigStore({
agentDir: dir,
configPath,
initialConfig: {
botToken: "default-token",
botId: 1,
profiles: {
existing: { botToken: "existing-token", botId: 2 },
fresh: { botToken: "" },
},
},
});
assert.equal(store.activateProfile("fresh"), true);
const runtime = createTelegramSetupPromptRuntime({
env: { TELEGRAM_BOT_TOKEN: "fresh-token" },
getConfig: store.get,
setConfig: store.set,
setupGuard: { start: () => true, finish: () => {} },
getMe: async () => ({
ok: true,
result: { id: 3, username: "fresh_bot" },
}),
persistConfig: async () => store.persist(),
startPolling: () => ({ ok: true }),
updateStatus: () => {},
});
const result = await runtime({
hasUI: true,
ui: {
input: async () => "fresh-token",
editor: async () => "fresh-token",
notify: () => {},
},
});
assert.equal(result.status, "success");
assert.deepEqual(JSON.parse(await readFile(configPath, "utf8")), {
botToken: "default-token",
botId: 1,
profiles: {
existing: { botToken: "existing-token", botId: 2 },
fresh: {
botToken: "fresh-token",
botUsername: "fresh_bot",
botId: 3,
},
},
});
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test("Setup prompt runtime reports token check errors and always finishes", async () => {
const calls: string[] = [];
let locked = false;
const runtime = createTelegramSetupPromptRuntime({
env: {},
getConfig: () => ({}),
setConfig: () => calls.push("set-config"),
setupGuard: {
start: () => {
if (locked) return false;
locked = true;
calls.push("guard-start");
return true;
},
finish: () => {
locked = false;
calls.push("guard-finish");
},
},
getMe: async () => {
throw new Error("network down");
},
persistConfig: async () => {
calls.push("persist");
},
startPolling: () => calls.push("start"),
updateStatus: () => calls.push("status"),
recordRuntimeEvent: (category, error) =>
calls.push(`${category}:${(error as Error).message}`),
});
await runtime({
hasUI: true,
ui: {
input: async () => "token",
editor: async () => "token",
notify: (message) => calls.push(`notify:${message}`),
},
});
assert.deepEqual(calls, [
"guard-start",
"notify:Telegram API check failed: network down",
"guard-finish",
]);
assert.equal(locked, false);
});