import path from 'path'; import os from 'os'; import { promises as fs } from 'fs'; import { afterEach, it, expect, vi } from 'vitest'; import type { Mock } from 'vitest'; vi.mock('../../src/services/archiveReader', () => ({ listArchiveEntries: vi.fn() })); import scanDirectory from '../../src/services/fsScanner'; import { listArchiveEntries } from '../../src/services/archiveReader'; afterEach(() => vi.restoreAllMocks()); it('expone entradas internas de archivos como items virtuales', async () => { const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'fsScanner-test-')); const collectionFile = path.join(tmpDir, 'collection.zip'); await fs.writeFile(collectionFile, ''); (listArchiveEntries as unknown as Mock).mockResolvedValue([ { name: 'inner/rom1.bin', size: 1234 }, ]); const results = await scanDirectory(tmpDir); const expectedPath = `${collectionFile}::inner/rom1.bin`; const found = results.find((r: any) => r.path === expectedPath); expect(found).toBeDefined(); expect(found.isArchiveEntry).toBe(true); expect(found.containerPath).toBe(collectionFile); expect(found.entryPath).toBe('inner/rom1.bin'); expect(found.filename).toBe('rom1.bin'); expect(found.format).toBe('bin'); await fs.rm(tmpDir, { recursive: true, force: true }); }); it('ignora entradas con traversal o paths absolutos', async () => { const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'fsScanner-test-')); const collectionFile = path.join(tmpDir, 'collection.zip'); await fs.writeFile(collectionFile, ''); (listArchiveEntries as unknown as Mock).mockResolvedValue([ { name: '../evil.rom', size: 10 }, { name: '/abs/evil.rom', size: 20 }, { name: 'good/rom.bin', size: 30 }, ]); const results = await scanDirectory(tmpDir); const safePath = `${collectionFile}::good/rom.bin`; expect(results.find((r: any) => r.path === safePath)).toBeDefined(); expect(results.find((r: any) => r.path === `${collectionFile}::../evil.rom`)).toBeUndefined(); expect(results.find((r: any) => r.path === `${collectionFile}::/abs/evil.rom`)).toBeUndefined(); await fs.rm(tmpDir, { recursive: true, force: true }); }); it('respeta ARCHIVE_MAX_ENTRIES', async () => { const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'fsScanner-test-')); const collectionFile = path.join(tmpDir, 'collection.zip'); await fs.writeFile(collectionFile, ''); // Set env var temporarily const prev = process.env.ARCHIVE_MAX_ENTRIES; process.env.ARCHIVE_MAX_ENTRIES = '1'; (listArchiveEntries as unknown as Mock).mockResolvedValue([ { name: 'one.bin', size: 1 }, { name: 'two.bin', size: 2 }, { name: 'three.bin', size: 3 }, ]); const results = await scanDirectory(tmpDir); const matches = results.filter((r: any) => String(r.path).startsWith(collectionFile + '::')); expect(matches.length).toBe(1); // restore if (prev === undefined) delete process.env.ARCHIVE_MAX_ENTRIES; else process.env.ARCHIVE_MAX_ENTRIES = prev; await fs.rm(tmpDir, { recursive: true, force: true }); });