const { makeTorrent, extractWebseed, extractInfoHash } = require('../src/torrent'); describe('makeTorrent + extractWebseed round-trip', () => { it('extracts the webseed URL back from a generated torrent', () => { const url = 'http://example.com/stream/abc123'; const buf = makeTorrent({ name: 'test.mkv', size: 1024 * 1024, webseedUrl: url }); expect(extractWebseed(buf)).toBe(url); }); it('works with size 0', () => { const url = 'http://example.com/stream/xyz'; const buf = makeTorrent({ name: 'empty.mkv', size: 0, webseedUrl: url }); expect(extractWebseed(buf)).toBe(url); }); it('produces a Buffer', () => { const buf = makeTorrent({ name: 'f.mkv', size: 100, webseedUrl: 'http://x.com/s/y' }); expect(Buffer.isBuffer(buf)).toBe(true); expect(buf.length).toBeGreaterThan(0); }); it('encodes name into the torrent', () => { const buf = makeTorrent({ name: 'movie.mkv', size: 500, webseedUrl: 'http://x.com/s/z' }); expect(buf.toString()).toContain('movie.mkv'); }); it('encodes multi-piece torrents for large sizes', () => { const buf = makeTorrent({ name: 'big.mkv', size: 512 * 1024 * 3, webseedUrl: 'http://x.com/s/big', }); expect(Buffer.isBuffer(buf)).toBe(true); expect(buf.length).toBeGreaterThan(60); }); }); describe('extractWebseed', () => { it('returns null for empty buffer', () => { expect(extractWebseed(Buffer.alloc(0))).toBeNull(); }); it('returns null for non-torrent data', () => { expect(extractWebseed(Buffer.from('not a torrent'))).toBeNull(); }); it('returns null for truncated dict (no busy-loop)', () => { // Previously: dict key "pieces" without value → bdecode infinite loop const truncated = Buffer.from( 'd4:infod6:lengthi0e4:name4:teste12:piece lengthi524288e6:piecese', 'utf8', ); const t0 = Date.now(); expect(extractWebseed(truncated)).toBeNull(); expect(Date.now() - t0).toBeLessThan(100); }); it('returns null when url-list is missing', () => { // Bencode a dict without url-list const buf = makeTorrent({ name: 'f.mkv', size: 0, webseedUrl: 'http://x.com/s/a' }); // Corrupt the url-list key to make it missing const str = buf.toString('binary').replace('url-list', 'url-xxxx'); const corrupted = Buffer.from(str, 'binary'); expect(extractWebseed(corrupted)).toBeNull(); }); it('handles url-list as array', () => { const url = 'http://cdn.example.com/stream/abcdef'; // d8:url-list l : e e const buf = Buffer.from(`d8:url-listl${url.length}:${url}ee`); expect(extractWebseed(buf)).toBe(url); }); it('returns null for empty url-list array', () => { const buf = Buffer.from('d8:url-listlee'); expect(extractWebseed(buf)).toBeNull(); }); }); describe('extractInfoHash', () => { it('returns stable sha1 hex for a generated torrent', () => { const buf = makeTorrent({ name: 't.mkv', size: 50, webseedUrl: 'http://x/s/a' }); const h = extractInfoHash(buf); expect(h).toMatch(/^[0-9a-f]{40}$/); expect(extractInfoHash(buf)).toBe(h); }); it('returns null without info dict or on garbage', () => { expect(extractInfoHash(Buffer.from('d3:fooi1ee'))).toBeNull(); expect(extractInfoHash(Buffer.from('not-bencode'))).toBeNull(); }); }); describe('bdecode edge cases via extractWebseed', () => { it('decodes lists and rejects unclosed list', () => { const url = 'http://a/b'; expect(extractWebseed(Buffer.from(`d8:url-listl${url.length}:${url}ee`))).toBe(url); // unclosed list → catch → null expect(extractWebseed(Buffer.from('d8:url-listl5:hello'))).toBeNull(); }); it('rejects bad string lengths and unclosed dicts', () => { expect(extractWebseed(Buffer.from('d3:fo'))).toBeNull(); expect(extractWebseed(Buffer.from('d4:name5:hello'))).toBeNull(); }); });