import { expect,test } from 'bun:test'; import { mkdtempSync,mkdirSync,readFileSync,statSync } from 'node:fs'; import { join } from 'node:path'; import { sanitizeInboundFileName,fetchInboundFile } from './files.js';
const tmp=()=>{const base=join(process.env.XDG_CACHE_HOME??join(process.env.HOME??'/tmp','.cache'),'overdeck/tests');mkdirSync(base,{recursive:true});return mkdtempSync(join(base,'botmaster-files-'))};
const response=(body:unknown)=>new Response(JSON.stringify(body),{status:200});
test('sanitizes traversal, empty names, and dotfiles to a basename',()=>{expect(sanitizeInboundFileName('../secret.txt')).toBe('secret.txt');expect(sanitizeInboundFileName('..\\secret.txt')).toBe('secret.txt');expect(sanitizeInboundFileName('...')).toBe('attachment.bin');expect(sanitizeInboundFileName('.env')).toBe('env')});
test('saves Telegram files below the supplied state root with restricted modes',async()=>{const root=tmp(),fetchImpl=(async(url:string)=>url.includes('getFile')?response({ok:true,result:{file_path:'docs/a.txt',file_size:2}}):new Response('ok')) as typeof fetch;const path=await fetchInboundFile('secret-token','file','../a.txt','42',{fetchImpl,root});expect(path).toBe(join(root,'42','a.txt'));expect(readFileSync(path,'utf8')).toBe('ok');expect(statSync(root).mode&0o777).toBe(0o700);expect(statSync(join(root,'42')).mode&0o777).toBe(0o700);expect(statSync(path).mode&0o777).toBe(0o600)});
test('refuses declared and actual files over 45 MB',async()=>{const root=tmp(),declared=(async()=>response({ok:true,result:{file_path:'a',file_size:45*1024*1024+1}})) as typeof fetch;await expect(fetchInboundFile('secret','file','a','1',{fetchImpl:declared,root})).rejects.toThrow(/45 MB/);const actual=(async(url:string)=>url.includes('getFile')?response({ok:true,result:{file_path:'a',file_size:1}}):new Response(new Uint8Array(45*1024*1024+1))) as typeof fetch;await expect(fetchInboundFile('secret','file','a','2',{fetchImpl:actual,root})).rejects.toThrow(/45 MB/)});
test('never exposes a token when Telegram fetch fails',async()=>{const token='123:never-leak';const failed=(async()=>{throw new Error(`fetch https://api.telegram.org/bot${token}/getFile failed`)}) as typeof fetch;try{await fetchInboundFile(token,'file','a','1',{fetchImpl:failed,root:tmp()});throw new Error('expected failure')}catch(error){expect(String(error)).not.toContain(token);expect(String(error)).not.toContain('api.telegram.org')}});
