fix: fail-fast guard TELEGRAM_CHUNK_SIZE_BYTES <= 19MB (Telegram getFile limit 20MB)
Build & Deploy (Nix) / build-and-deploy (push) Successful in 56s
Build & Deploy (Nix) / build-and-deploy (push) Successful in 56s
Chunk parts > 19MB are stored to Telegram but getFile cannot resolve files over 20MB ('Bad Request: file is too big'), making every part undownloadable (prod bug 2026-08-01: 48MB chunk -> download 500).
- src/env.ts: reject TELEGRAM_CHUNK_SIZE_BYTES > 19922944 at startup (log error + throw), default changed 20MB -> 19MB
- src/shared/utils/validation.ts: TELEGRAM_CHUNK_SIZE_MAX_BYTES constant; asSafeChunkSize now enforces the max at runtime (covers S3 multipart parts too)
- test/env.test.ts: unit tests + subprocess fail-fast tests (48MB rejected, 19MB accepted)
- test/helpers/setup-env.ts: pin safe chunk size so a stale .env can't break the suite
- .env.example + CLAUDE.md: document the 20MB getFile limit
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import { config } from '../src/env';
|
||||
import { asSafeChunkSize, TELEGRAM_CHUNK_SIZE_MAX_BYTES } from '../src/shared/utils/validation';
|
||||
|
||||
describe('Environment Variables Validation', () => {
|
||||
it('config should have all required fields', () => {
|
||||
@@ -67,3 +68,77 @@ describe('Environment Variables Validation', () => {
|
||||
expect(config.s3SecretKey).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Telegram chunk size validation', () => {
|
||||
it('config.telegramChunkSizeBytes should default to the safe 19 MB value when unset', () => {
|
||||
// setup-env.ts does not set TELEGRAM_CHUNK_SIZE_BYTES, so the default must
|
||||
// be the safe 19 MB value — never the raw 20 MB getFile limit.
|
||||
expect(config.telegramChunkSizeBytes).toBe(TELEGRAM_CHUNK_SIZE_MAX_BYTES);
|
||||
expect(config.telegramChunkSizeBytes).toBeLessThan(20 * 1024 * 1024);
|
||||
});
|
||||
|
||||
it('asSafeChunkSize accepts sizes at or below the maximum (19 MB)', () => {
|
||||
expect(asSafeChunkSize(TELEGRAM_CHUNK_SIZE_MAX_BYTES)).toBe(TELEGRAM_CHUNK_SIZE_MAX_BYTES);
|
||||
expect(asSafeChunkSize(1024)).toBe(1024);
|
||||
expect(asSafeChunkSize(19 * 1024 * 1024)).toBe(19 * 1024 * 1024);
|
||||
});
|
||||
|
||||
it('asSafeChunkSize rejects sizes above the Telegram getFile limit (incl. the 48 MB production bug)', () => {
|
||||
// The production bug value (48 MB / 50331648) must be rejected.
|
||||
expect(() => asSafeChunkSize(48 * 1024 * 1024)).toThrow(/exceeds/);
|
||||
// Even exactly 20 MB is at the raw Telegram limit — rejected by the margin.
|
||||
expect(() => asSafeChunkSize(20 * 1024 * 1024)).toThrow(/exceeds/);
|
||||
expect(() => asSafeChunkSize(TELEGRAM_CHUNK_SIZE_MAX_BYTES + 1)).toThrow(/exceeds/);
|
||||
});
|
||||
|
||||
it('asSafeChunkSize rejects non-positive or non-integer sizes', () => {
|
||||
expect(() => asSafeChunkSize(0)).toThrow('Invalid Telegram chunk size');
|
||||
expect(() => asSafeChunkSize(-1)).toThrow('Invalid Telegram chunk size');
|
||||
expect(() => asSafeChunkSize(1.5)).toThrow('Invalid Telegram chunk size');
|
||||
});
|
||||
|
||||
it('startup fails fast when TELEGRAM_CHUNK_SIZE_BYTES exceeds the limit', async () => {
|
||||
// Spawn a real process that imports src/env with an oversized chunk size;
|
||||
// it must exit non-zero with a clear error instead of starting silently.
|
||||
const proc = Bun.spawn({
|
||||
cmd: ['bun', '-e', "import('./src/env')"],
|
||||
cwd: `${import.meta.dir}/..`,
|
||||
env: {
|
||||
...process.env,
|
||||
BOT_TOKENS: '123456:ABC-DEF',
|
||||
STORAGE_CHANNEL_ID: '-1001234567890',
|
||||
BASE_URL: 'https://example.com',
|
||||
DATABASE_URL: 'postgresql://user:***@localhost:5432/test',
|
||||
PORT: '3000',
|
||||
TELEGRAM_CHUNK_SIZE_BYTES: String(48 * 1024 * 1024),
|
||||
},
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
});
|
||||
const exitCode = await proc.exited;
|
||||
const stderr = await new Response(proc.stderr).text();
|
||||
expect(exitCode).not.toBe(0);
|
||||
expect(stderr).toContain('TELEGRAM_CHUNK_SIZE_BYTES');
|
||||
expect(stderr).toContain('exceeds');
|
||||
});
|
||||
|
||||
it('startup succeeds when TELEGRAM_CHUNK_SIZE_BYTES is at the safe limit', async () => {
|
||||
const proc = Bun.spawn({
|
||||
cmd: ['bun', '-e', "import('./src/env')"],
|
||||
cwd: `${import.meta.dir}/..`,
|
||||
env: {
|
||||
...process.env,
|
||||
BOT_TOKENS: '123456:ABC-DEF',
|
||||
STORAGE_CHANNEL_ID: '-1001234567890',
|
||||
BASE_URL: 'https://example.com',
|
||||
DATABASE_URL: 'postgresql://user:***@localhost:5432/test',
|
||||
PORT: '3000',
|
||||
TELEGRAM_CHUNK_SIZE_BYTES: String(TELEGRAM_CHUNK_SIZE_MAX_BYTES),
|
||||
},
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
});
|
||||
const exitCode = await proc.exited;
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user