import { describe, it, expect, vi, afterEach } from 'vitest'; import { PassThrough } from 'stream'; import { EventEmitter } from 'events'; vi.mock('child_process', () => ({ spawn: vi.fn() })); import * as child_process from 'child_process'; import { streamArchiveEntry } from '../../src/services/archiveReader'; afterEach(() => { vi.restoreAllMocks(); }); describe('services/archiveReader streamArchiveEntry', () => { it('streams entry using 7z stdout', async () => { const pass = new PassThrough(); const proc = new EventEmitter() as any; proc.stdout = pass; (child_process.spawn as any).mockImplementation(() => proc as any); // Emular producción de datos de forma asíncrona setImmediate(() => { pass.write(Buffer.from('content-from-7z')); pass.end(); }); const stream = await streamArchiveEntry('/roms/archive.7z', 'path/file.txt'); expect(stream).not.toBeNull(); const chunks: Buffer[] = []; for await (const chunk of stream as any) { chunks.push(Buffer.from(chunk)); } expect(Buffer.concat(chunks).toString()).toBe('content-from-7z'); }); it('falls back to unzip -p when 7z throws', async () => { const pass = new PassThrough(); const proc2 = new EventEmitter() as any; proc2.stdout = pass; (child_process.spawn as any) .mockImplementationOnce(() => { throw new Error('spawn ENOENT'); }) .mockImplementationOnce(() => proc2 as any); setImmediate(() => { pass.write(Buffer.from('fallback-content')); pass.end(); }); const stream = await streamArchiveEntry('/roms/archive.zip', 'file.dat'); expect(stream).not.toBeNull(); const chunks: Buffer[] = []; for await (const chunk of stream as any) { chunks.push(Buffer.from(chunk)); } expect(Buffer.concat(chunks).toString()).toBe('fallback-content'); }); it('returns null for unsupported formats', async () => { const res = await streamArchiveEntry('/roms/archive.bin', 'entry'); expect(res).toBeNull(); }); });