Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions app/bot/webhook.ensureBotInit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { Bot } from 'grammy';

test('ensureBotInit resets after init failure (no stuck rejected promise)', async (t) => {
const originalInit = Bot.prototype.init;
const originalHandleUpdate = Bot.prototype.handleUpdate;

let initCalls = 0;
let handleUpdateCalls = 0;

Bot.prototype.init = async function initMock(): Promise<void> {
initCalls += 1;
if (initCalls === 1) throw new Error('init failed (simulated)');
};

Bot.prototype.handleUpdate = async function handleUpdateMock(): Promise<void> {
handleUpdateCalls += 1;
};

t.after(() => {
Bot.prototype.init = originalInit;
Bot.prototype.handleUpdate = originalHandleUpdate;
delete process.env.BOT_TOKEN;
delete process.env.TELEGRAM_BOT_TOKEN;
});

process.env.BOT_TOKEN = 'test-token';

// Import after env + prototype mocks so the module creates a bot using the patched methods.
const mod = await import('./webhook.ts');
const handler = mod.default as (
req: { method: string; body?: unknown },
res: {
setHeader(name: string, value: string): void;
status(code: number): { json(data: unknown): void; end(): void };
end(): void;
},
) => Promise<void>;

function createRes() {
let statusCode: number | null = null;
let jsonBody: unknown = undefined;
return {
get statusCode() {
return statusCode;
},
get jsonBody() {
return jsonBody;
},
res: {
setHeader() {},
status(code: number) {
statusCode = code;
return {
json(data: unknown) {
jsonBody = data;
},
end() {},
};
},
end() {},
},
};
}

const update = JSON.stringify({
update_id: 1,
message: { chat: { id: 123 }, text: 'hi' },
});

const first = createRes();
await handler({ method: 'POST', body: update }, first.res);
assert.equal(first.statusCode, 500);

const second = createRes();
await handler({ method: 'POST', body: update }, second.res);
assert.equal(second.statusCode, 200);

assert.equal(initCalls, 2);
assert.equal(handleUpdateCalls, 1);
});

14 changes: 13 additions & 1 deletion app/bot/webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,19 @@ const bot = BOT_TOKEN ? createBot(BOT_TOKEN) : null;
let botInitPromise: Promise<void> | null = null;
function ensureBotInit(): Promise<void> {
if (!bot) return Promise.resolve();
if (!botInitPromise) botInitPromise = bot.init();
if (!botInitPromise) {
try {
botInitPromise = bot.init().catch((err) => {
console.error('[bot] init failed', err);
botInitPromise = null;
throw err;
});
} catch (err) {
console.error('[bot] init failed', err);
botInitPromise = null;
throw err;
}
}
return botInitPromise;
}

Expand Down