- Replace forceDocument boolean with fileType string in forwardToStorage - Dynamically call matching sendPhoto, sendAudio, sendVideo, sendVoice, sendAnimation, sendSticker, or sendDocument Telegraf API method - Correctly extract uploaded file details based on what Telegram returned - Fixes IMAGE_PROCESS_FAILED 500 error when uploading ogg audio, video, sticker, or voice notes via HTTP - Update upload.ts, bot.ts, and test suites to match the new dynamic signature Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
278 lines
7.2 KiB
TypeScript
278 lines
7.2 KiB
TypeScript
// @ts-nocheck
|
|
import { afterAll, beforeEach, describe, expect, it, mock, spyOn } from 'bun:test';
|
|
import logger from '../src/utils/logger';
|
|
|
|
// Mock environment
|
|
process.env.BOT_TOKEN = '8605908810:AAFpUzlIBktfd_7wpEj7zMJob2CFxvG-ZGY';
|
|
process.env.STORAGE_CHANNEL_ID = '-1003996572954';
|
|
process.env.BASE_URL = 'https://tele.asepharyana.tech';
|
|
|
|
// Mock Telegraf
|
|
const mockLaunch = mock(() => Promise.resolve());
|
|
const mockCommand = mock();
|
|
const mockOn = mock();
|
|
const mockUse = mock();
|
|
|
|
mock.module('telegraf', () => {
|
|
return {
|
|
Telegraf: class {
|
|
constructor(token) {
|
|
this.token = token;
|
|
this.launch = mockLaunch;
|
|
this.command = mockCommand;
|
|
this.on = mockOn;
|
|
this.use = mockUse;
|
|
}
|
|
},
|
|
};
|
|
});
|
|
|
|
// Mock database
|
|
const mockInsert = mock(() => ({
|
|
values: mock(() => Promise.resolve()),
|
|
}));
|
|
const mockLimit = mock(() => Promise.resolve([]));
|
|
const mockWhere = mock(() => ({
|
|
limit: mockLimit,
|
|
}));
|
|
const mockFrom = mock(() => ({
|
|
where: mockWhere,
|
|
}));
|
|
const mockSelect = mock(() => ({
|
|
from: mockFrom,
|
|
}));
|
|
mock.module('../src/db/index', () => ({
|
|
db: {
|
|
insert: mockInsert,
|
|
select: mockSelect,
|
|
},
|
|
files: {
|
|
telegramFileUniqueId: 'telegram_file_unique_id',
|
|
},
|
|
}));
|
|
|
|
// Mock forwardToStorage
|
|
const mockForwardToStorage = mock(() =>
|
|
Promise.resolve({
|
|
telegramFileId: 'stored_file_id',
|
|
telegramFileUniqueId: 'stored_unique_id',
|
|
storageMessageId: 9999,
|
|
}),
|
|
);
|
|
mock.module('../src/utils/telegram', () => ({
|
|
forwardToStorage: mockForwardToStorage,
|
|
}));
|
|
|
|
const infoSpy = spyOn(logger, 'info');
|
|
const errorSpy = spyOn(logger, 'error');
|
|
|
|
describe('Telegram Bot Handler', () => {
|
|
beforeEach(() => {
|
|
mockLaunch.mockClear();
|
|
mockCommand.mockClear();
|
|
mockOn.mockClear();
|
|
mockUse.mockClear();
|
|
mockInsert.mockClear();
|
|
mockLimit.mockClear();
|
|
mockLimit.mockResolvedValue([]);
|
|
mockForwardToStorage.mockClear();
|
|
infoSpy.mockClear();
|
|
errorSpy.mockClear();
|
|
});
|
|
|
|
it('should initialize and launch the bot', async () => {
|
|
const { startBot } = await import('../src/bot');
|
|
const bot = await startBot();
|
|
|
|
expect(bot).toBeDefined();
|
|
expect(mockCommand).toHaveBeenCalledWith('start', expect.any(Function));
|
|
expect(mockOn).toHaveBeenCalledWith(
|
|
['document', 'photo', 'video', 'audio', 'voice', 'animation', 'sticker', 'video_note'],
|
|
expect.any(Function),
|
|
);
|
|
expect(mockUse).toHaveBeenCalled();
|
|
expect(mockLaunch).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should handle /start command', async () => {
|
|
const { startBot } = await import('../src/bot');
|
|
await startBot();
|
|
|
|
const startHandler = mockCommand.mock.calls.find((call) => call[0] === 'start')[1];
|
|
const replyMock = mock(() => Promise.resolve());
|
|
const ctx = {
|
|
reply: replyMock,
|
|
};
|
|
|
|
await startHandler(ctx);
|
|
expect(replyMock).toHaveBeenCalledWith(expect.stringContaining('Halo'));
|
|
});
|
|
|
|
it('should process document uploads and save to db', async () => {
|
|
const { startBot } = await import('../src/bot');
|
|
await startBot();
|
|
|
|
const fileHandler = mockOn.mock.calls[0][1];
|
|
const replyMock = mock(() => Promise.resolve());
|
|
const ctx = {
|
|
message: {
|
|
message_id: 42,
|
|
document: {
|
|
file_id: 'doc_123',
|
|
file_unique_id: 'doc_uniq_123',
|
|
file_size: 1024,
|
|
mime_type: 'application/pdf',
|
|
file_name: 'cv.pdf',
|
|
},
|
|
},
|
|
from: {
|
|
id: 999,
|
|
},
|
|
reply: replyMock,
|
|
};
|
|
|
|
await fileHandler(ctx);
|
|
expect(mockForwardToStorage).toHaveBeenCalledWith('doc_123', 'cv.pdf', 'document');
|
|
expect(mockInsert).toHaveBeenCalled();
|
|
expect(replyMock).toHaveBeenCalledWith(
|
|
expect.stringContaining('File berhasil diupload'),
|
|
expect.any(Object),
|
|
);
|
|
});
|
|
|
|
it('should reject uploads exceeding max size limit', async () => {
|
|
const { startBot } = await import('../src/bot');
|
|
await startBot();
|
|
|
|
const fileHandler = mockOn.mock.calls[0][1];
|
|
const replyMock = mock(() => Promise.resolve());
|
|
const ctx = {
|
|
message: {
|
|
message_id: 42,
|
|
photo: [
|
|
{
|
|
file_id: 'photo_123',
|
|
file_unique_id: 'photo_uniq_123',
|
|
file_size: 20 * 1024 * 1024, // 20MB exceeds 10MB limit
|
|
mime_type: 'image/jpeg',
|
|
},
|
|
],
|
|
},
|
|
from: {
|
|
id: 999,
|
|
},
|
|
reply: replyMock,
|
|
};
|
|
|
|
await fileHandler(ctx);
|
|
expect(mockForwardToStorage).not.toHaveBeenCalled();
|
|
expect(replyMock).toHaveBeenCalledWith(expect.stringContaining('exceeds'));
|
|
});
|
|
|
|
it('should return existing download link for duplicates without uploading again', async () => {
|
|
const { startBot } = await import('../src/bot');
|
|
await startBot();
|
|
|
|
const fileHandler = mockOn.mock.calls[0][1];
|
|
const replyMock = mock(() => Promise.resolve());
|
|
|
|
// Mock DB to return an existing match
|
|
mockLimit.mockResolvedValueOnce([
|
|
{
|
|
publicId: 'already_exists_abc',
|
|
telegramFileId: 'stored_file_id',
|
|
telegramFileUniqueId: 'doc_uniq_123',
|
|
},
|
|
]);
|
|
|
|
const ctx = {
|
|
message: {
|
|
message_id: 42,
|
|
document: {
|
|
file_id: 'doc_123',
|
|
file_unique_id: 'doc_uniq_123',
|
|
file_size: 1024,
|
|
mime_type: 'application/pdf',
|
|
file_name: 'cv.pdf',
|
|
},
|
|
},
|
|
from: {
|
|
id: 999,
|
|
},
|
|
reply: replyMock,
|
|
};
|
|
|
|
await fileHandler(ctx);
|
|
expect(mockForwardToStorage).not.toHaveBeenCalled();
|
|
expect(mockInsert).not.toHaveBeenCalled();
|
|
expect(replyMock).toHaveBeenCalledWith(
|
|
expect.stringContaining('already_exists_abc'),
|
|
expect.any(Object),
|
|
);
|
|
});
|
|
|
|
it('should process sticker uploads', async () => {
|
|
const { startBot } = await import('../src/bot');
|
|
await startBot();
|
|
|
|
const fileHandler = mockOn.mock.calls[0][1];
|
|
const replyMock = mock(() => Promise.resolve());
|
|
const ctx = {
|
|
message: {
|
|
message_id: 43,
|
|
sticker: {
|
|
file_id: 'sticker_123',
|
|
file_unique_id: 'sticker_uniq_123',
|
|
file_size: 1024,
|
|
},
|
|
},
|
|
from: {
|
|
id: 999,
|
|
},
|
|
reply: replyMock,
|
|
};
|
|
|
|
await fileHandler(ctx);
|
|
expect(mockForwardToStorage).toHaveBeenCalledWith('sticker_123', 'file', 'sticker');
|
|
expect(mockInsert).toHaveBeenCalled();
|
|
expect(replyMock).toHaveBeenCalledWith(
|
|
expect.stringContaining('File berhasil diupload'),
|
|
expect.any(Object),
|
|
);
|
|
});
|
|
|
|
it('should process video note uploads', async () => {
|
|
const { startBot } = await import('../src/bot');
|
|
await startBot();
|
|
|
|
const fileHandler = mockOn.mock.calls[0][1];
|
|
const replyMock = mock(() => Promise.resolve());
|
|
const ctx = {
|
|
message: {
|
|
message_id: 44,
|
|
video_note: {
|
|
file_id: 'video_note_123',
|
|
file_unique_id: 'video_note_uniq_123',
|
|
file_size: 1024,
|
|
},
|
|
},
|
|
from: {
|
|
id: 999,
|
|
},
|
|
reply: replyMock,
|
|
};
|
|
|
|
await fileHandler(ctx);
|
|
expect(mockForwardToStorage).toHaveBeenCalledWith('video_note_123', 'file', 'video_note');
|
|
expect(mockInsert).toHaveBeenCalled();
|
|
expect(replyMock).toHaveBeenCalledWith(
|
|
expect.stringContaining('File berhasil diupload'),
|
|
expect.any(Object),
|
|
);
|
|
});
|
|
|
|
afterAll(() => {
|
|
mock.restore();
|
|
});
|
|
});
|