feat: stream hashing y entradas en archivos
- Añade `computeHashesFromStream` para hashing desde streams - Añade `streamArchiveEntry` e integra en `importDirectory` (path codificado con ::) - Extiende `scanDirectory` para exponer entradas internas, normaliza rutas POSIX y evita traversal; `ARCHIVE_MAX_ENTRIES` configurable - Limpia listeners en hashing y mejora robustez/logging - Añade tests unitarios e integración; actualiza mocks a `Mock` types - CI: instala `unzip` junto a `p7zip` para soportar tests de integración
This commit is contained in:
@@ -2,6 +2,7 @@ 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() }));
|
||||
|
||||
@@ -15,7 +16,7 @@ it('expone entradas internas de archivos como items virtuales', async () => {
|
||||
const collectionFile = path.join(tmpDir, 'collection.zip');
|
||||
await fs.writeFile(collectionFile, '');
|
||||
|
||||
(listArchiveEntries as unknown as vi.Mock).mockResolvedValue([
|
||||
(listArchiveEntries as unknown as Mock).mockResolvedValue([
|
||||
{ name: 'inner/rom1.bin', size: 1234 },
|
||||
]);
|
||||
|
||||
@@ -33,3 +34,49 @@ it('expone entradas internas de archivos como items virtuales', async () => {
|
||||
|
||||
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 });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user