feat: expose archive entries in fsScanner

- Añade `scanDirectory` support para listar entradas internas de ZIP/7z
- Añade test unitario que mockea `archiveReader.listArchiveEntries`
- Añade límite configurable `ARCHIVE_MAX_ENTRIES` y comprobación básica de seguridad
This commit is contained in:
2026-02-09 19:26:00 +01:00
parent ab63361e66
commit 97a7f74685
3 changed files with 96 additions and 0 deletions

View File

@@ -13,9 +13,13 @@
import path from 'path'; import path from 'path';
import { promises as fsPromises } from 'fs'; import { promises as fsPromises } from 'fs';
import { detectFormat } from '../lib/fileTypeDetector'; import { detectFormat } from '../lib/fileTypeDetector';
import { listArchiveEntries } from './archiveReader';
const ARCHIVE_MAX_ENTRIES = Number(process.env.ARCHIVE_MAX_ENTRIES) || 1000;
export async function scanDirectory(dirPath: string): Promise<any[]> { export async function scanDirectory(dirPath: string): Promise<any[]> {
const results: any[] = []; const results: any[] = [];
let archiveEntriesAdded = 0;
async function walk(dir: string) { async function walk(dir: string) {
const entries = await fsPromises.readdir(dir, { withFileTypes: true }); const entries = await fsPromises.readdir(dir, { withFileTypes: true });
@@ -43,6 +47,35 @@ export async function scanDirectory(dirPath: string): Promise<any[]> {
format, format,
isArchive, isArchive,
}); });
if (isArchive) {
try {
const entries = await listArchiveEntries(full);
for (const e of entries) {
if (archiveEntriesAdded >= ARCHIVE_MAX_ENTRIES) break;
if (!e || !e.name) continue;
// avoid path traversal or absolute paths
if (e.name.includes('..') || path.isAbsolute(e.name)) continue;
results.push({
path: `${full}::${e.name}`,
containerPath: full,
entryPath: e.name,
filename: path.basename(e.name),
name: e.name,
size: e.size,
format: detectFormat(e.name),
isArchive: false,
isArchiveEntry: true,
});
archiveEntriesAdded++;
}
} catch (err) {
// ignore archive listing errors
}
}
} }
} }
} }

View File

@@ -0,0 +1,35 @@
import path from 'path';
import os from 'os';
import { promises as fs } from 'fs';
import { afterEach, it, expect, vi } 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 vi.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 });
});

View File

@@ -0,0 +1,28 @@
## Phase 2 Complete: Exponer entradas internas en el escáner
TL;DR: `scanDirectory` ahora lista entradas internas de contenedores ZIP/7z como items virtuales codificados usando `::`. Se añadieron tests unitarios que mockean `archiveReader.listArchiveEntries` y se introdujo un límite configurable `ARCHIVE_MAX_ENTRIES`.
**Files created/changed:**
- backend/src/services/fsScanner.ts
- backend/tests/services/fsScanner.archiveEntries.spec.ts
**Functions created/changed:**
- `scanDirectory(dirPath)` — ahora, al detectar un archivo contenedor, invoca `listArchiveEntries(container)` y añade items virtuales con:
- `path: "${containerPath}::${entryPath}"`
- `containerPath`, `entryPath`, `filename`, `size`, `format`, `isArchiveEntry: true`
- Añadido `ARCHIVE_MAX_ENTRIES` (configurable via `process.env.ARCHIVE_MAX_ENTRIES`, default 1000).
**Tests created/changed:**
- `backend/tests/services/fsScanner.archiveEntries.spec.ts` — valida que `scanDirectory` incluya la entrada interna codificada y que los metadatos (`filename`, `format`, `containerPath`, `entryPath`, `isArchiveEntry`) sean correctos.
**Review Status:** APPROVED
**Git Commit Message:**
feat: expose archive entries in fsScanner
- Añade `scanDirectory` support para listar entradas internas de ZIP/7z
- Añade test unitario que mockea `archiveReader.listArchiveEntries`
- Añade límite configurable `ARCHIVE_MAX_ENTRIES` y comprobación básica de seguridad