style: format and lint codebase using Biome v2

This commit is contained in:
MythEclipse
2026-05-18 07:29:01 +07:00
parent 3f4e697733
commit cf79a1f195
22 changed files with 638 additions and 528 deletions
+27 -25
View File
@@ -1,43 +1,45 @@
// @ts-nocheck
import { describe, it, expect, mock, beforeEach, afterAll } from "bun:test";
import { afterAll, beforeEach, describe, expect, it, mock } from 'bun:test';
const mockServe = mock((options) => {
return {
port: options.port,
routes: options.routes,
stop: mock()
stop: mock(),
};
});
const originalServe = Bun.serve;
Bun.serve = mockServe;
const mockStartBot = mock(() => Promise.resolve({
stop: mock()
const mockStartBot = mock(() =>
Promise.resolve({
stop: mock(),
}),
);
mock.module('../src/bot', () => ({
startBot: mockStartBot,
}));
mock.module("../src/bot", () => ({
startBot: mockStartBot
mock.module('../src/routes/upload', () => ({
handleUpload: mock(),
}));
mock.module("../src/routes/upload", () => ({
handleUpload: mock()
}));
mock.module("../src/routes/files", () => ({
mock.module('../src/routes/files', () => ({
handleFileRedirect: mock(),
handleFileInfo: mock()
handleFileInfo: mock(),
}));
mock.module("../src/routes/health", () => ({
handleHealth: mock()
mock.module('../src/routes/health', () => ({
handleHealth: mock(),
}));
mock.module("../src/utils/rateLimit", () => ({
cleanupRateLimitCache: mock()
mock.module('../src/utils/rateLimit', () => ({
cleanupRateLimitCache: mock(),
}));
describe("Bootstrap Server", () => {
describe('Bootstrap Server', () => {
beforeEach(() => {
mockServe.mockClear();
mockStartBot.mockClear();
@@ -47,18 +49,18 @@ describe("Bootstrap Server", () => {
Bun.serve = originalServe;
});
it("should bootstrap the application successfully", async () => {
await import("../src/index");
it('should bootstrap the application successfully', async () => {
await import('../src/index');
expect(mockServe).toHaveBeenCalled();
expect(mockStartBot).toHaveBeenCalled();
const serveCallArgs = mockServe.mock.calls[0][0];
expect(serveCallArgs).toHaveProperty("port");
expect(serveCallArgs).toHaveProperty("routes");
expect(serveCallArgs.routes).toHaveProperty("/api/upload");
expect(serveCallArgs.routes).toHaveProperty("/f/:public_id");
expect(serveCallArgs.routes).toHaveProperty("/file/:public_id/info");
expect(serveCallArgs.routes).toHaveProperty("/health");
expect(serveCallArgs).toHaveProperty('port');
expect(serveCallArgs).toHaveProperty('routes');
expect(serveCallArgs.routes).toHaveProperty('/api/upload');
expect(serveCallArgs.routes).toHaveProperty('/f/:public_id');
expect(serveCallArgs.routes).toHaveProperty('/file/:public_id/info');
expect(serveCallArgs.routes).toHaveProperty('/health');
});
});
+60 -53
View File
@@ -1,11 +1,11 @@
// @ts-nocheck
import { describe, it, expect, mock, spyOn, beforeEach, afterAll } from "bun:test";
import logger from "../src/utils/logger";
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";
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());
@@ -13,7 +13,7 @@ const mockCommand = mock();
const mockOn = mock();
const mockUse = mock();
mock.module("telegraf", () => {
mock.module('telegraf', () => {
return {
Telegraf: class {
constructor(token) {
@@ -23,35 +23,37 @@ mock.module("telegraf", () => {
this.on = mockOn;
this.use = mockUse;
}
}
},
};
});
// Mock database
const mockInsert = mock(() => ({
values: mock(() => Promise.resolve())
values: mock(() => Promise.resolve()),
}));
mock.module("../src/db/index", () => ({
mock.module('../src/db/index', () => ({
db: {
insert: mockInsert
insert: mockInsert,
},
files: {}
files: {},
}));
// 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 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");
const infoSpy = spyOn(logger, 'info');
const errorSpy = spyOn(logger, 'error');
describe("Telegram Bot Handler", () => {
describe('Telegram Bot Handler', () => {
beforeEach(() => {
mockLaunch.mockClear();
mockCommand.mockClear();
@@ -63,36 +65,36 @@ describe("Telegram Bot Handler", () => {
errorSpy.mockClear();
});
it("should initialize and launch the bot", async () => {
const { startBot } = await import("../src/bot");
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(mockCommand).toHaveBeenCalledWith('start', expect.any(Function));
expect(mockOn).toHaveBeenCalledWith(
["document", "photo", "video", "audio", "voice", "animation"],
expect.any(Function)
['document', 'photo', 'video', 'audio', 'voice', 'animation'],
expect.any(Function),
);
expect(mockUse).toHaveBeenCalled();
expect(mockLaunch).toHaveBeenCalled();
});
it("should handle /start command", async () => {
const { startBot } = await import("../src/bot");
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 startHandler = mockCommand.mock.calls.find((call) => call[0] === 'start')[1];
const replyMock = mock(() => Promise.resolve());
const ctx = {
reply: replyMock
reply: replyMock,
};
await startHandler(ctx);
expect(replyMock).toHaveBeenCalledWith(expect.stringContaining("Halo"));
expect(replyMock).toHaveBeenCalledWith(expect.stringContaining('Halo'));
});
it("should process document uploads and save to db", async () => {
const { startBot } = await import("../src/bot");
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];
@@ -101,27 +103,30 @@ describe("Telegram Bot Handler", () => {
message: {
message_id: 42,
document: {
file_id: "doc_123",
file_unique_id: "doc_uniq_123",
file_id: 'doc_123',
file_unique_id: 'doc_uniq_123',
file_size: 1024,
mime_type: "application/pdf",
file_name: "cv.pdf"
}
mime_type: 'application/pdf',
file_name: 'cv.pdf',
},
},
from: {
id: 999
id: 999,
},
reply: replyMock
reply: replyMock,
};
await fileHandler(ctx);
expect(mockForwardToStorage).toHaveBeenCalledWith("doc_123", "cv.pdf");
expect(mockForwardToStorage).toHaveBeenCalledWith('doc_123', 'cv.pdf');
expect(mockInsert).toHaveBeenCalled();
expect(replyMock).toHaveBeenCalledWith(expect.stringContaining("File berhasil diupload"), expect.any(Object));
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");
it('should reject uploads exceeding max size limit', async () => {
const { startBot } = await import('../src/bot');
await startBot();
const fileHandler = mockOn.mock.calls[0][1];
@@ -129,22 +134,24 @@ describe("Telegram Bot Handler", () => {
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"
}]
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
id: 999,
},
reply: replyMock
reply: replyMock,
};
await fileHandler(ctx);
expect(mockForwardToStorage).not.toHaveBeenCalled();
expect(replyMock).toHaveBeenCalledWith(expect.stringContaining("exceeds"));
expect(replyMock).toHaveBeenCalledWith(expect.stringContaining('exceeds'));
});
afterAll(() => {
+7 -7
View File
@@ -1,20 +1,20 @@
// @ts-nocheck
import { describe, it, expect } from "bun:test";
import { db, files } from "../src/db/index";
import { files as schemaFiles } from "../src/db/schema";
import { describe, expect, it } from 'bun:test';
import { db, files } from '../src/db/index';
import { files as schemaFiles } from '../src/db/schema';
describe("Database Layer", () => {
it("should export db instance", () => {
describe('Database Layer', () => {
it('should export db instance', () => {
expect(db).toBeDefined();
});
it("should export files schema from both index and schema", () => {
it('should export files schema from both index and schema', () => {
expect(files).toBeDefined();
expect(schemaFiles).toBeDefined();
expect(files).toBe(schemaFiles);
});
it("should have correct schema properties", () => {
it('should have correct schema properties', () => {
expect(files.id).toBeDefined();
expect(files.publicId).toBeDefined();
expect(files.telegramFileId).toBeDefined();
+22 -22
View File
@@ -1,46 +1,46 @@
// @ts-nocheck
import { describe, it, expect, beforeAll } from "bun:test";
import { config } from "../src/env";
import { describe, expect, it } from 'bun:test';
import { config } from '../src/env';
describe("Environment Variables Validation", () => {
it("config should have all required fields", () => {
expect(config).toHaveProperty("botToken");
expect(config).toHaveProperty("storageChatId");
expect(config).toHaveProperty("baseUrl");
expect(config).toHaveProperty("databaseUrl");
expect(config).toHaveProperty("port");
expect(config).toHaveProperty("nodeEnv");
expect(config).toHaveProperty("logLevel");
expect(config).toHaveProperty("rateLimitWindowMs");
expect(config).toHaveProperty("rateLimitMaxRequests");
describe('Environment Variables Validation', () => {
it('config should have all required fields', () => {
expect(config).toHaveProperty('botToken');
expect(config).toHaveProperty('storageChatId');
expect(config).toHaveProperty('baseUrl');
expect(config).toHaveProperty('databaseUrl');
expect(config).toHaveProperty('port');
expect(config).toHaveProperty('nodeEnv');
expect(config).toHaveProperty('logLevel');
expect(config).toHaveProperty('rateLimitWindowMs');
expect(config).toHaveProperty('rateLimitMaxRequests');
});
it("config.botToken should return BOT_TOKEN from process.env", () => {
it('config.botToken should return BOT_TOKEN from process.env', () => {
expect(config.botToken).toBe(process.env.BOT_TOKEN);
});
it("config.storageChatId should be parsed as integer from STORAGE_CHANNEL_ID", () => {
expect(typeof config.storageChatId).toBe("number");
it('config.storageChatId should be parsed as integer from STORAGE_CHANNEL_ID', () => {
expect(typeof config.storageChatId).toBe('number');
expect(config.storageChatId).toBe(parseInt(process.env.STORAGE_CHANNEL_ID, 10));
});
it("config.port should default to 3000 when not specified", () => {
expect(typeof config.port).toBe("number");
it('config.port should default to 3000 when not specified', () => {
expect(typeof config.port).toBe('number');
});
it("nodeEnv should be 'test' or 'development'", () => {
expect(["test", "development"]).toContain(config.nodeEnv);
expect(['test', 'development']).toContain(config.nodeEnv);
});
it("logLevel should default to 'info'", () => {
expect(config.logLevel).toBe("info");
expect(config.logLevel).toBe('info');
});
it("rateLimitWindowMs should default to 60000 when not specified", () => {
it('rateLimitWindowMs should default to 60000 when not specified', () => {
expect(config.rateLimitWindowMs).toBe(60000);
});
it("rateLimitMaxRequests should default to 30 when not specified", () => {
it('rateLimitMaxRequests should default to 30 when not specified', () => {
expect(config.rateLimitMaxRequests).toBe(30);
});
});
+61 -55
View File
@@ -1,89 +1,95 @@
// @ts-nocheck
import { describe, it, expect } from "bun:test";
import { getFileType, checkFileSize, extractFileName, extractMimeType } from "../src/utils/file";
import { describe, expect, it } from 'bun:test';
import { checkFileSize, extractFileName, extractMimeType, getFileType } from '../src/utils/file';
describe("File Utilities", () => {
describe("getFileType", () => {
it("should classify video mime types as video", () => {
expect(getFileType("video/mp4", "")).toBe("video");
expect(getFileType("video/quicktime", "")).toBe("video");
describe('File Utilities', () => {
describe('getFileType', () => {
it('should classify video mime types as video', () => {
expect(getFileType('video/mp4', '')).toBe('video');
expect(getFileType('video/quicktime', '')).toBe('video');
});
it("should classify audio mime types as audio", () => {
expect(getFileType("audio/mpeg", "")).toBe("audio");
expect(getFileType("audio/ogg", "")).toBe("audio");
it('should classify audio mime types as audio', () => {
expect(getFileType('audio/mpeg', '')).toBe('audio');
expect(getFileType('audio/ogg', '')).toBe('audio');
});
it("should classify image mime types based on caption", () => {
expect(getFileType("image/jpeg", "my photo")).toBe("photo");
expect(getFileType("image/png", "cool image.png")).toBe("photo");
expect(getFileType("image/gif", "funny.gif")).toBe("animation");
expect(getFileType("image/png", "funny gif")).toBe("animation");
it('should classify image mime types based on caption', () => {
expect(getFileType('image/jpeg', 'my photo')).toBe('photo');
expect(getFileType('image/png', 'cool image.png')).toBe('photo');
expect(getFileType('image/gif', 'funny.gif')).toBe('animation');
expect(getFileType('image/png', 'funny gif')).toBe('animation');
});
it("should classify voice and animation based on caption", () => {
expect(getFileType("application/octet-stream", "this is a voice note")).toBe("voice");
expect(getFileType("application/octet-stream", "cool animation")).toBe("animation");
it('should classify voice and animation based on caption', () => {
expect(getFileType('application/octet-stream', 'this is a voice note')).toBe('voice');
expect(getFileType('application/octet-stream', 'cool animation')).toBe('animation');
});
it("should default to mime first segment or document", () => {
expect(getFileType("application/pdf", "")).toBe("application");
expect(getFileType(null, "")).toBe("document");
it('should default to mime first segment or document', () => {
expect(getFileType('application/pdf', '')).toBe('application');
expect(getFileType(null, '')).toBe('document');
});
});
describe("checkFileSize", () => {
it("should allow files under the size limit", () => {
expect(checkFileSize(5 * 1024 * 1024, "photo")).toBe(true); // Photo limit is 10MB
expect(checkFileSize(1 * 1024 * 1024 * 1024, "video")).toBe(true); // Video limit is 2GB
describe('checkFileSize', () => {
it('should allow files under the size limit', () => {
expect(checkFileSize(5 * 1024 * 1024, 'photo')).toBe(true); // Photo limit is 10MB
expect(checkFileSize(1 * 1024 * 1024 * 1024, 'video')).toBe(true); // Video limit is 2GB
});
it("should block files exceeding the size limit", () => {
expect(checkFileSize(15 * 1024 * 1024, "photo")).toBe(false); // Photo limit is 10MB
expect(checkFileSize(3 * 1024 * 1024 * 1024, "video")).toBe(false); // Video limit is 2GB
it('should block files exceeding the size limit', () => {
expect(checkFileSize(15 * 1024 * 1024, 'photo')).toBe(false); // Photo limit is 10MB
expect(checkFileSize(3 * 1024 * 1024 * 1024, 'video')).toBe(false); // Video limit is 2GB
});
it("should fall back to document limit if fileType is unknown", () => {
expect(checkFileSize(1 * 1024 * 1024 * 1024, "unknown")).toBe(true); // Document limit is 2GB
expect(checkFileSize(3 * 1024 * 1024 * 1024, "unknown")).toBe(false);
it('should fall back to document limit if fileType is unknown', () => {
expect(checkFileSize(1 * 1024 * 1024 * 1024, 'unknown')).toBe(true); // Document limit is 2GB
expect(checkFileSize(3 * 1024 * 1024 * 1024, 'unknown')).toBe(false);
});
});
describe("extractFileName", () => {
it("should extract file name from headers if present", () => {
const req = { headers: { "x-file-name": "custom.txt" } };
expect(extractFileName({}, req)).toBe("custom.txt");
describe('extractFileName', () => {
it('should extract file name from headers if present', () => {
const req = { headers: { 'x-file-name': 'custom.txt' } };
expect(extractFileName({}, req)).toBe('custom.txt');
});
it("should extract file name from various message attachment types", () => {
expect(extractFileName({ document: { fileName: "doc.pdf" } }, null)).toBe("doc.pdf");
expect(extractFileName({ photo: [{ fileName: "low.jpg" }, { fileName: "high.jpg" }] }, null)).toBe("high.jpg");
expect(extractFileName({ audio: { fileName: "song.mp3" } }, null)).toBe("song.mp3");
expect(extractFileName({ voice: { fileName: "voice.ogg" } }, null)).toBe("voice.ogg");
expect(extractFileName({ animation: { fileName: "anim.gif" } }, null)).toBe("anim.gif");
it('should extract file name from various message attachment types', () => {
expect(extractFileName({ document: { fileName: 'doc.pdf' } }, null)).toBe('doc.pdf');
expect(
extractFileName({ photo: [{ fileName: 'low.jpg' }, { fileName: 'high.jpg' }] }, null),
).toBe('high.jpg');
expect(extractFileName({ audio: { fileName: 'song.mp3' } }, null)).toBe('song.mp3');
expect(extractFileName({ voice: { fileName: 'voice.ogg' } }, null)).toBe('voice.ogg');
expect(extractFileName({ animation: { fileName: 'anim.gif' } }, null)).toBe('anim.gif');
});
it("should return default filename if not found", () => {
expect(extractFileName({}, null)).toBe("file");
it('should return default filename if not found', () => {
expect(extractFileName({}, null)).toBe('file');
});
});
describe("extractMimeType", () => {
it("should extract mime type from headers if present", () => {
const req = { headers: { "x-mime-type": "text/plain" } };
expect(extractMimeType({}, req)).toBe("text/plain");
describe('extractMimeType', () => {
it('should extract mime type from headers if present', () => {
const req = { headers: { 'x-mime-type': 'text/plain' } };
expect(extractMimeType({}, req)).toBe('text/plain');
});
it("should extract mime type from various message attachment types", () => {
expect(extractMimeType({ document: { mimeType: "application/pdf" } }, null)).toBe("application/pdf");
expect(extractMimeType({ photo: [{ mimeType: "image/jpeg" }, { mimeType: "image/png" }] }, null)).toBe("image/png");
expect(extractMimeType({ audio: { mimeType: "audio/mpeg" } }, null)).toBe("audio/mpeg");
expect(extractMimeType({ voice: { mimeType: "audio/ogg" } }, null)).toBe("audio/ogg");
expect(extractMimeType({ animation: { mimeType: "video/mp4" } }, null)).toBe("video/mp4");
it('should extract mime type from various message attachment types', () => {
expect(extractMimeType({ document: { mimeType: 'application/pdf' } }, null)).toBe(
'application/pdf',
);
expect(
extractMimeType({ photo: [{ mimeType: 'image/jpeg' }, { mimeType: 'image/png' }] }, null),
).toBe('image/png');
expect(extractMimeType({ audio: { mimeType: 'audio/mpeg' } }, null)).toBe('audio/mpeg');
expect(extractMimeType({ voice: { mimeType: 'audio/ogg' } }, null)).toBe('audio/ogg');
expect(extractMimeType({ animation: { mimeType: 'video/mp4' } }, null)).toBe('video/mp4');
});
it("should return default mime type if not found", () => {
expect(extractMimeType({}, null)).toBe("application/octet-stream");
it('should return default mime type if not found', () => {
expect(extractMimeType({}, null)).toBe('application/octet-stream');
});
});
});
+83 -78
View File
@@ -1,44 +1,44 @@
// @ts-nocheck
import { describe, it, expect, mock, beforeEach, afterAll } from "bun:test";
import { afterAll, beforeEach, describe, expect, it, mock } from 'bun:test';
// Mock database layer
const mockSelect = mock(() => ({
from: mock(() => ({
where: mock(() => ({
limit: mock(() => Promise.resolve([]))
}))
}))
limit: mock(() => Promise.resolve([])),
})),
})),
}));
mock.module("../src/db/index", () => ({
mock.module('../src/db/index', () => ({
db: {
select: mockSelect
select: mockSelect,
},
files: {
publicId: {
equals: (val) => ({ type: "equals", value: val })
}
}
equals: (val) => ({ type: 'equals', value: val }),
},
},
}));
// Mock telegram utils
const mockGetFile = mock(() => Promise.resolve({ file_path: "photos/file_0.jpg" }));
mock.module("../src/utils/telegram", () => ({
const mockGetFile = mock(() => Promise.resolve({ file_path: 'photos/file_0.jpg' }));
mock.module('../src/utils/telegram', () => ({
getBot: () => ({
telegram: {
getFile: mockGetFile
}
})
getFile: mockGetFile,
},
}),
}));
// Mock rateLimit
const mockCheckRateLimit = mock(() => true);
mock.module("../src/utils/rateLimit", () => ({
checkRateLimit: mockCheckRateLimit
mock.module('../src/utils/rateLimit', () => ({
checkRateLimit: mockCheckRateLimit,
}));
describe("File Route Handlers", () => {
let handleFileRedirect, handleFileInfo;
describe('File Route Handlers', () => {
let handleFileRedirect: any, handleFileInfo: any;
beforeEach(async () => {
mockSelect.mockClear();
@@ -46,141 +46,146 @@ describe("File Route Handlers", () => {
mockCheckRateLimit.mockClear();
// Set up mock token
process.env.BOT_TOKEN = "123456:ABC-DEF";
process.env.BOT_TOKEN = '123456:ABC-DEF';
const filesRoute = await import("../src/routes/files");
const filesRoute = await import('../src/routes/files');
handleFileRedirect = filesRoute.handleFileRedirect;
handleFileInfo = filesRoute.handleFileInfo;
});
describe("handleFileRedirect", () => {
it("should return 429 if rate limit is exceeded", async () => {
describe('handleFileRedirect', () => {
it('should return 429 if rate limit is exceeded', async () => {
mockCheckRateLimit.mockImplementationOnce(() => false);
const req = new Request("http://localhost:3000/f/test-id");
req.params = { public_id: "test-id" };
const req = new Request('http://localhost:3000/f/test-id');
req.params = { public_id: 'test-id' };
const res = await handleFileRedirect(req);
expect(res.status).toBe(429);
const body = await res.json();
expect(body.error).toBe("Rate limit exceeded");
expect(body.error).toBe('Rate limit exceeded');
});
it("should return 404 if file is not found in database", async () => {
it('should return 404 if file is not found in database', async () => {
mockSelect.mockImplementationOnce(() => ({
from: () => ({
where: () => ({
limit: () => Promise.resolve([])
})
})
limit: () => Promise.resolve([]),
}),
}),
}));
const req = new Request("http://localhost:3000/f/missing-id");
req.params = { public_id: "missing-id" };
const req = new Request('http://localhost:3000/f/missing-id');
req.params = { public_id: 'missing-id' };
const res = await handleFileRedirect(req);
expect(res.status).toBe(404);
const body = await res.json();
expect(body.error).toBe("File not found");
expect(body.error).toBe('File not found');
});
it("should redirect to telegram file url if file is found", async () => {
it('should redirect to telegram file url if file is found', async () => {
mockSelect.mockImplementationOnce(() => ({
from: () => ({
where: () => ({
limit: () => Promise.resolve([{
id: "uuid-123",
publicId: "test-id",
telegramFileId: "tg-file-id",
fileName: "test.jpg"
}])
})
})
limit: () =>
Promise.resolve([
{
id: 'uuid-123',
publicId: 'test-id',
telegramFileId: 'tg-file-id',
fileName: 'test.jpg',
},
]),
}),
}),
}));
const req = new Request("http://localhost:3000/f/test-id");
req.params = { public_id: "test-id" };
const req = new Request('http://localhost:3000/f/test-id');
req.params = { public_id: 'test-id' };
const res = await handleFileRedirect(req);
expect(res.status).toBe(302);
expect(res.headers.get("Location")).toBe("https://api.telegram.org/file/bot123456:ABC-DEF/photos/file_0.jpg");
expect(res.headers.get('Location')).toBe(
'https://api.telegram.org/file/bot123456:ABC-DEF/photos/file_0.jpg',
);
});
it("should return 500 on database or external errors", async () => {
it('should return 500 on database or external errors', async () => {
mockSelect.mockImplementationOnce(() => {
throw new Error("DB Connection Error");
throw new Error('DB Connection Error');
});
const req = new Request("http://localhost:3000/f/test-id");
req.params = { public_id: "test-id" };
const req = new Request('http://localhost:3000/f/test-id');
req.params = { public_id: 'test-id' };
const res = await handleFileRedirect(req);
expect(res.status).toBe(500);
const body = await res.json();
expect(body.error).toBe("Server error");
expect(body.error).toBe('Server error');
});
});
describe("handleFileInfo", () => {
it("should return 404 if file is not found in database", async () => {
describe('handleFileInfo', () => {
it('should return 404 if file is not found in database', async () => {
mockSelect.mockImplementationOnce(() => ({
from: () => ({
where: () => ({
limit: () => Promise.resolve([])
})
})
limit: () => Promise.resolve([]),
}),
}),
}));
const req = new Request("http://localhost:3000/file/missing-id/info");
req.params = { public_id: "missing-id" };
const req = new Request('http://localhost:3000/file/missing-id/info');
req.params = { public_id: 'missing-id' };
const res = await handleFileInfo(req);
expect(res.status).toBe(404);
const body = await res.json();
expect(body.error).toBe("File not found");
expect(body.error).toBe('File not found');
});
it("should return file info JSON if file is found", async () => {
it('should return file info JSON if file is found', async () => {
const dbFile = {
publicId: "test-id",
fileName: "image.png",
mimeType: "image/png",
publicId: 'test-id',
fileName: 'image.png',
mimeType: 'image/png',
sizeBytes: 2048,
fileType: "photo",
fileType: 'photo',
uploaderId: 99999,
createdAt: new Date("2026-05-18T00:00:00.000Z")
createdAt: new Date('2026-05-18T00:00:00.000Z'),
};
mockSelect.mockImplementationOnce(() => ({
from: () => ({
where: () => ({
limit: () => Promise.resolve([dbFile])
})
})
limit: () => Promise.resolve([dbFile]),
}),
}),
}));
const req = new Request("http://localhost:3000/file/test-id/info");
req.params = { public_id: "test-id" };
const req = new Request('http://localhost:3000/file/test-id/info');
req.params = { public_id: 'test-id' };
const res = await handleFileInfo(req);
expect(res.status).toBe(200);
const body = await res.json();
expect(body).toEqual({
public_id: "test-id",
file_name: "image.png",
mime_type: "image/png",
public_id: 'test-id',
file_name: 'image.png',
mime_type: 'image/png',
size_bytes: 2048,
file_type: "photo",
file_type: 'photo',
uploader_id: 99999,
created_at: "2026-05-18T00:00:00.000Z"
created_at: '2026-05-18T00:00:00.000Z',
});
});
it("should return 500 on database or external errors", async () => {
it('should return 500 on database or external errors', async () => {
mockSelect.mockImplementationOnce(() => {
throw new Error("DB Connection Error");
throw new Error('DB Connection Error');
});
const req = new Request("http://localhost:3000/file/test-id/info");
req.params = { public_id: "test-id" };
const req = new Request('http://localhost:3000/file/test-id/info');
req.params = { public_id: 'test-id' };
const res = await handleFileInfo(req);
expect(res.status).toBe(500);
const body = await res.json();
expect(body.error).toBe("Server error");
expect(body.error).toBe('Server error');
});
});
+15 -15
View File
@@ -1,42 +1,42 @@
// @ts-nocheck
import { describe, it, expect, mock, beforeEach } from "bun:test";
import { beforeEach, describe, expect, it, mock } from 'bun:test';
// Mock database layer
const mockExecute = mock(() => Promise.resolve());
mock.module("../src/db/index", () => ({
mock.module('../src/db/index', () => ({
db: {
execute: mockExecute
}
execute: mockExecute,
},
}));
describe("Health Route Handler", () => {
let handleHealth;
describe('Health Route Handler', () => {
let handleHealth: any;
beforeEach(async () => {
mockExecute.mockClear();
const healthRoute = await import("../src/routes/health");
const healthRoute = await import('../src/routes/health');
handleHealth = healthRoute.handleHealth;
});
it("should return status 200 and ok when DB is healthy", async () => {
const req = new Request("http://localhost:3000/health");
it('should return status 200 and ok when DB is healthy', async () => {
const req = new Request('http://localhost:3000/health');
const res = await handleHealth(req);
expect(res.status).toBe(200);
const body = await res.json();
expect(body).toEqual({ status: "ok" });
expect(body).toEqual({ status: 'ok' });
expect(mockExecute).toHaveBeenCalled();
});
it("should return status 500 and error details when DB health check fails", async () => {
mockExecute.mockImplementationOnce(() => Promise.reject(new Error("DB Connection Failed")));
const req = new Request("http://localhost:3000/health");
it('should return status 500 and error details when DB health check fails', async () => {
mockExecute.mockImplementationOnce(() => Promise.reject(new Error('DB Connection Failed')));
const req = new Request('http://localhost:3000/health');
const res = await handleHealth(req);
expect(res.status).toBe(500);
const body = await res.json();
expect(body.status).toBe("error");
expect(body.error).toBe("DB Connection Failed");
expect(body.status).toBe('error');
expect(body.error).toBe('DB Connection Failed');
});
});
+17 -17
View File
@@ -1,17 +1,17 @@
// @ts-nocheck
import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test";
import { checkRateLimit, cleanupRateLimitCache } from "../src/utils/rateLimit";
import logger from "../src/utils/logger";
import { afterEach, beforeEach, describe, expect, it, spyOn } from 'bun:test';
import logger from '../src/utils/logger';
import { checkRateLimit, cleanupRateLimitCache } from '../src/utils/rateLimit';
// Spy on logger.warn
const warnSpy = spyOn(logger, "warn");
const warnSpy = spyOn(logger, 'warn');
describe("Rate Limiter", () => {
describe('Rate Limiter', () => {
beforeEach(() => {
warnSpy.mockClear();
// Set custom env variables for predictable tests
process.env.RATE_LIMIT_WINDOW_MS = "100"; // 100ms window
process.env.RATE_LIMIT_MAX_REQUESTS = "3"; // max 3 requests
process.env.RATE_LIMIT_WINDOW_MS = '100'; // 100ms window
process.env.RATE_LIMIT_MAX_REQUESTS = '3'; // max 3 requests
});
afterEach(() => {
@@ -19,16 +19,16 @@ describe("Rate Limiter", () => {
delete process.env.RATE_LIMIT_MAX_REQUESTS;
});
it("should allow requests under the limit", () => {
const key = "user-1";
it('should allow requests under the limit', () => {
const key = 'user-1';
expect(checkRateLimit(key)).toBe(true);
expect(checkRateLimit(key)).toBe(true);
expect(checkRateLimit(key)).toBe(true);
expect(warnSpy).not.toHaveBeenCalled();
});
it("should block requests exceeding the limit and log a warning", () => {
const key = "user-2";
it('should block requests exceeding the limit and log a warning', () => {
const key = 'user-2';
expect(checkRateLimit(key)).toBe(true);
expect(checkRateLimit(key)).toBe(true);
expect(checkRateLimit(key)).toBe(true);
@@ -37,12 +37,12 @@ describe("Rate Limiter", () => {
expect(checkRateLimit(key)).toBe(false);
expect(warnSpy).toHaveBeenCalled();
const callArgs = warnSpy.mock.calls[0];
expect(callArgs[0]).toBe("Rate limit exceeded");
expect(callArgs[0]).toBe('Rate limit exceeded');
expect(callArgs[1].key).toBe(key);
});
it("should reset request count after the window passes", async () => {
const key = "user-3";
it('should reset request count after the window passes', async () => {
const key = 'user-3';
expect(checkRateLimit(key)).toBe(true);
expect(checkRateLimit(key)).toBe(true);
expect(checkRateLimit(key)).toBe(true);
@@ -55,9 +55,9 @@ describe("Rate Limiter", () => {
expect(checkRateLimit(key)).toBe(true);
});
it("should cleanup rate limit cache of expired keys", async () => {
const key1 = "cleanup-1";
const key2 = "cleanup-2";
it('should cleanup rate limit cache of expired keys', async () => {
const key1 = 'cleanup-1';
const key2 = 'cleanup-2';
// Populate keys
expect(checkRateLimit(key1)).toBe(true);
+81 -62
View File
@@ -1,33 +1,34 @@
// @ts-nocheck
import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test";
import logger from "../src/utils/logger";
import { config } from "../src/env";
import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from 'bun:test';
import logger from '../src/utils/logger';
// Mock Telegraf and fetch
mock.module("telegraf", () => {
mock.module('telegraf', () => {
return {
Telegraf: class {
constructor(token) {
this.token = token;
this.telegram = {
sendPhoto: mock(() => Promise.resolve({
message_id: 12345,
photo: [
{ file_id: "photo_id_low", file_unique_id: "unique_id_low" },
{ file_id: "photo_id_high", file_unique_id: "unique_id_high" }
]
}))
sendPhoto: mock(() =>
Promise.resolve({
message_id: 12345,
photo: [
{ file_id: 'photo_id_low', file_unique_id: 'unique_id_low' },
{ file_id: 'photo_id_high', file_unique_id: 'unique_id_high' },
],
}),
),
};
}
}
},
};
});
const infoSpy = spyOn(logger, "info");
const errorSpy = spyOn(logger, "error");
const infoSpy = spyOn(logger, 'info');
const errorSpy = spyOn(logger, 'error');
describe("Telegram API Utilities", () => {
let forwardToStorage, getFileInfo, getBot;
describe('Telegram API Utilities', () => {
let forwardToStorage: any, getFileInfo: any, getBot: any;
beforeEach(async () => {
infoSpy.mockClear();
@@ -35,7 +36,7 @@ describe("Telegram API Utilities", () => {
global.fetch = mock(() => Promise.resolve(new Response(JSON.stringify({ ok: true }))));
// Import dynamically so mocking is applied first
const telegramUtils = await import("../src/utils/telegram");
const telegramUtils = await import('../src/utils/telegram');
forwardToStorage = telegramUtils.forwardToStorage;
getFileInfo = telegramUtils.getFileInfo;
getBot = telegramUtils.getBot;
@@ -45,83 +46,101 @@ describe("Telegram API Utilities", () => {
delete global.fetch;
});
describe("getBot", () => {
it("should return the telegraf bot instance", () => {
describe('getBot', () => {
it('should return the telegraf bot instance', () => {
const bot = getBot();
expect(bot).toBeDefined();
expect(bot.telegram).toBeDefined();
});
});
describe("forwardToStorage", () => {
it("should forward photo to storage chat and return file details", async () => {
const chunk = Buffer.from("fake photo data");
const fileName = "test_photo.jpg";
describe('forwardToStorage', () => {
it('should forward photo to storage chat and return file details', async () => {
const chunk = Buffer.from('fake photo data');
const fileName = 'test_photo.jpg';
const result = await forwardToStorage(chunk, fileName, false);
expect(result).toEqual({
telegramFileId: "photo_id_high",
telegramFileUniqueId: "unique_id_high",
storageMessageId: 12345
telegramFileId: 'photo_id_high',
telegramFileUniqueId: 'unique_id_high',
storageMessageId: 12345,
});
expect(infoSpy).toHaveBeenCalledWith("File forwarded to storage", {
expect(infoSpy).toHaveBeenCalledWith('File forwarded to storage', {
fileName,
message: 12345
message: 12345,
});
});
it("should handle error when forwarding fails", async () => {
it('should handle error when forwarding fails', async () => {
const bot = getBot();
bot.telegram.sendPhoto = mock(() => Promise.reject(new Error("Telegram send failed")));
bot.telegram.sendPhoto = mock(() => Promise.reject(new Error('Telegram send failed')));
const chunk = Buffer.from("fake photo data");
const fileName = "test_photo.jpg";
const chunk = Buffer.from('fake photo data');
const fileName = 'test_photo.jpg';
await expect(forwardToStorage(chunk, fileName, false)).rejects.toThrow("Telegram send failed");
expect(errorSpy).toHaveBeenCalledWith("Failed to forward file to storage", {
await expect(forwardToStorage(chunk, fileName, false)).rejects.toThrow(
'Telegram send failed',
);
expect(errorSpy).toHaveBeenCalledWith('Failed to forward file to storage', {
fileName,
error: "Telegram send failed"
error: 'Telegram send failed',
});
});
});
describe("getFileInfo", () => {
it("should fetch file details successfully", async () => {
global.fetch = mock((url, init) => {
if (url.endsWith("getFile")) {
return Promise.resolve(new Response(JSON.stringify({
ok: true,
result: { file_id: "some_file_id" }
})));
} else if (url.endsWith("getInfo")) {
return Promise.resolve(new Response(JSON.stringify({
ok: true,
result: {
file_size: 98765,
mime_type: "image/jpeg",
file_path: "photos/file_0.jpg"
}
})));
describe('getFileInfo', () => {
it('should fetch file details successfully', async () => {
global.fetch = mock((url, _init) => {
if (url.endsWith('getFile')) {
return Promise.resolve(
new Response(
JSON.stringify({
ok: true,
result: { file_id: 'some_file_id' },
}),
),
);
} else if (url.endsWith('getInfo')) {
return Promise.resolve(
new Response(
JSON.stringify({
ok: true,
result: {
file_size: 98765,
mime_type: 'image/jpeg',
file_path: 'photos/file_0.jpg',
},
}),
),
);
}
return Promise.reject(new Error("Unknown URL"));
return Promise.reject(new Error('Unknown URL'));
});
const result = await getFileInfo("some_file_id", "some_unique_id");
const result = await getFileInfo('some_file_id', 'some_unique_id');
expect(result).toEqual({
file_size: 98765,
mime_type: "image/jpeg",
file_path: "photos/file_0.jpg"
mime_type: 'image/jpeg',
file_path: 'photos/file_0.jpg',
});
});
it("should handle error when getFile fails", async () => {
global.fetch = mock(() => Promise.resolve(new Response(JSON.stringify({
ok: false,
description: "Bad Request: file_id invalid"
}))));
it('should handle error when getFile fails', async () => {
global.fetch = mock(() =>
Promise.resolve(
new Response(
JSON.stringify({
ok: false,
description: 'Bad Request: file_id invalid',
}),
),
),
);
await expect(getFileInfo("invalid_file_id", "invalid_unique_id")).rejects.toThrow("Bad Request: file_id invalid");
await expect(getFileInfo('invalid_file_id', 'invalid_unique_id')).rejects.toThrow(
'Bad Request: file_id invalid',
);
expect(errorSpy).toHaveBeenCalled();
});
});
+60 -56
View File
@@ -1,120 +1,124 @@
// @ts-nocheck
import { describe, it, expect, mock, beforeEach, afterAll } from "bun:test";
import { afterAll, beforeEach, describe, expect, it, mock } from 'bun:test';
// Mock db
const mockInsert = mock(() => ({
values: mock(() => Promise.resolve())
values: mock(() => Promise.resolve()),
}));
mock.module("../src/db/index", () => ({
mock.module('../src/db/index', () => ({
db: {
insert: mockInsert
insert: mockInsert,
},
files: {}
files: {},
}));
// Mock nanoid
mock.module("nanoid", () => ({
nanoid: () => "mocked-nanoid-id"
mock.module('nanoid', () => ({
nanoid: () => 'mocked-nanoid-id',
}));
// Mock telegram utils
mock.module("../src/utils/telegram", () => ({
forwardToStorage: mock(() => Promise.resolve({
telegramFileId: "tg-file-id-123",
telegramFileUniqueId: "tg-unique-id-abc",
storageMessageId: 98765
})),
mock.module('../src/utils/telegram', () => ({
forwardToStorage: mock(() =>
Promise.resolve({
telegramFileId: 'tg-file-id-123',
telegramFileUniqueId: 'tg-unique-id-abc',
storageMessageId: 98765,
}),
),
getBot: () => ({
telegram: {
getFile: mock(() => Promise.resolve({
file_id: "tg-file-id-123",
file_size: 1000,
mime_type: "image/jpeg"
}))
}
})
getFile: mock(() =>
Promise.resolve({
file_id: 'tg-file-id-123',
file_size: 1000,
mime_type: 'image/jpeg',
}),
),
},
}),
}));
describe("Upload Route Handler", () => {
let handleUpload;
describe('Upload Route Handler', () => {
let handleUpload: any;
beforeEach(async () => {
mockInsert.mockClear();
const uploadRoute = await import("../src/routes/upload");
const uploadRoute = await import('../src/routes/upload');
handleUpload = uploadRoute.handleUpload;
});
it("should reject unsupported content types with 400 status", async () => {
const req = new Request("http://localhost:3000/api/upload", {
method: "POST",
it('should reject unsupported content types with 400 status', async () => {
const req = new Request('http://localhost:3000/api/upload', {
method: 'POST',
headers: {
"content-type": "text/plain"
'content-type': 'text/plain',
},
body: "plain text data"
body: 'plain text data',
});
const res = await handleUpload(req);
expect(res.status).toBe(400);
const body = await res.json();
expect(body.error).toContain("Unsupported content type");
expect(body.error).toContain('Unsupported content type');
});
it("should process JSON upload (base64) successfully", async () => {
const req = new Request("http://localhost:3000/api/upload", {
method: "POST",
it('should process JSON upload (base64) successfully', async () => {
const req = new Request('http://localhost:3000/api/upload', {
method: 'POST',
headers: {
"content-type": "application/json"
'content-type': 'application/json',
},
body: JSON.stringify({
file: Buffer.from("hello world").toString("base64"),
fileName: "test.txt"
})
file: Buffer.from('hello world').toString('base64'),
fileName: 'test.txt',
}),
});
const res = await handleUpload(req);
expect(res.status).toBe(200);
const body = await res.json();
expect(body.public_id).toBe("mocked-nanoid-id");
expect(body.telegram_file_id).toBe("tg-file-id-123");
expect(body.telegram_file_unique_id).toBe("tg-unique-id-abc");
expect(body.file_name).toBe("test.txt");
expect(body.file_type).toBe("document");
expect(body.public_id).toBe('mocked-nanoid-id');
expect(body.telegram_file_id).toBe('tg-file-id-123');
expect(body.telegram_file_unique_id).toBe('tg-unique-id-abc');
expect(body.file_name).toBe('test.txt');
expect(body.file_type).toBe('document');
});
it("should reject JSON upload without file key", async () => {
const req = new Request("http://localhost:3000/api/upload", {
method: "POST",
it('should reject JSON upload without file key', async () => {
const req = new Request('http://localhost:3000/api/upload', {
method: 'POST',
headers: {
"content-type": "application/json"
'content-type': 'application/json',
},
body: JSON.stringify({
fileName: "test.txt"
})
fileName: 'test.txt',
}),
});
const res = await handleUpload(req);
expect(res.status).toBe(400);
const body = await res.json();
expect(body.error).toContain("Invalid JSON");
expect(body.error).toContain('Invalid JSON');
});
it("should process multipart upload successfully", async () => {
it('should process multipart upload successfully', async () => {
const formData = new FormData();
const fileBlob = new Blob([Buffer.from("multipart hello")], { type: "text/plain" });
formData.append("file", fileBlob, "test_multi.txt");
const fileBlob = new Blob([Buffer.from('multipart hello')], { type: 'text/plain' });
formData.append('file', fileBlob, 'test_multi.txt');
const req = new Request("http://localhost:3000/api/upload", {
method: "POST",
body: formData
const req = new Request('http://localhost:3000/api/upload', {
method: 'POST',
body: formData,
});
const res = await handleUpload(req);
expect(res.status).toBe(200);
const body = await res.json();
expect(body.public_id).toBe("mocked-nanoid-id");
expect(body.file_name).toBe("test_multi.txt");
expect(body.public_id).toBe('mocked-nanoid-id');
expect(body.file_name).toBe('test_multi.txt');
});
afterAll(() => {