## Plan: Integrar entradas de archivo en el escáner TL;DR: Añadir soporte para listar y procesar entradas dentro de contenedores (ZIP/7z) en el pipeline de importación. Empezamos sin migración de base de datos (usando `::` para codificar `path`), no soportamos archives anidados por ahora, y añadimos límites configurables de tamaño y entradas por archivo. CI instalará `7z` y `unzip` para tests de integración. **Phases 4** 1. **Phase 1: Contracto de ArchiveReader (list + stream)** - **Objective:** Definir y probar la API de `archiveReader` con dos funciones públicas: `listArchiveEntries(containerPath): Promise` y `streamArchiveEntry(containerPath, entryPath): Readable`. - **Files/Functions to Modify/Create:** `backend/src/services/archiveReader.ts` (añadir `streamArchiveEntry`, documentar comportamiento y fallback a librería JS para ZIP si falta `7z`). - **Tests to Write:** - `backend/tests/services/archiveReader.list.spec.ts` — unit: mockear `child_process.exec` para simular salida de `7z -slt` y `unzip -l`. - `backend/tests/services/archiveReader.stream.spec.ts` — unit: mockear `child_process` y stream; integration opcional (ver Fase 4). - **Steps:** 1. Escribir tests (fallando) que describan la API y el formato de `Entry` (`{ name, size }`). 2. Implementar `streamArchiveEntry` usando `7z x -so` o `unzip -p` y devolver un `Readable`. 3. Añadir fallback para ZIP mediante librería JS si `7z` no está disponible. 4. Ejecutar y hacer pasar tests unitarios. - **Acceptance:** Tests unitarios pasan; `streamArchiveEntry` es mockeable y devuelve stream. 2. **Phase 2: Extender `fsScanner` para exponer entradas (virtual files)** - **Objective:** `scanDirectory(dir)` debe incluir entradas internas de archivos contenedor como items virtuales con `path` codificado (`/abs/archive.zip::inner/path.rom`), `filename` = basename(inner), `isArchiveEntry = true`. - **Files/Functions to Modify/Create:** `backend/src/services/fsScanner.ts` (usar `archiveReader.listArchiveEntries`). - **Tests to Write:** - `backend/tests/services/fsScanner.archiveEntries.spec.ts` — unit: mockear `archiveReader.listArchiveEntries` y verificar formato. - **Steps:** 1. Escribir test unitario (fallando) que verifica que `scanDirectory` invoca `archiveReader` y añade entradas codificadas. 2. Implementar la integración mínima en `fsScanner` (sin extracción, solo listar entradas). 3. Ejecutar tests y ajustar. - **Acceptance:** `scanDirectory` devuelve objetos virtuales estandarizados; tests unitarios pasan. 3. **Phase 3: Hashing por stream y soporte en `importService` (unit)** - **Objective:** Añadir `computeHashesFromStream(stream)` y hacer que `importDirectory` pueda procesar entradas internas usando `archiveReader.streamArchiveEntry` para obtener hashes sin escribir ficheros temporales. - **Files/Functions to Modify/Create:** `backend/src/services/checksumService.ts` (añadir `computeHashesFromStream`), `backend/src/services/importService.ts` (aceptar `isArchiveEntry` y usar `archiveReader.streamArchiveEntry`). - **Tests to Write:** - `backend/tests/services/checksumService.stream.spec.ts` — unit: hashing desde un `Readable` creado desde un fixture (`backend/tests/fixtures/simple-rom.bin`). - `backend/tests/services/importService.archiveEntry.spec.ts` — unit: mockear `scanDirectory` para devolver entry codificada, mockear `archiveReader.streamArchiveEntry` para devolver stream desde fixture, mockear Prisma y verificar `upsert` con `path` codificado. - **Steps:** 1. Escribir tests (fallando) que describan el comportamiento. 2. Implementar `computeHashesFromStream(stream)` (MD5/SHA1/CRC32) y refactorizar `computeHashes` para delegar cuando se dispone de stream. 3. Hacer `importDirectory` soportar entries internas: obtener stream, calcular hashes, persistir con `path` codificado. 4. Ejecutar y pasar tests unitarios. - **Acceptance:** Unit tests pasan; `importDirectory` hace upsert con `path` codificado y hashes correctos. 4. **Phase 4: Integración real y CI opt-in** - **Objective:** Validar flujo end-to-end con binarios nativos (`7z` y `unzip`) usando fixtures reales en `backend/tests/fixtures/archives/`. CI instalará estos binarios para ejecutar integration tests. - **Files/Functions to Modify/Create:** tests de integración (ej. `backend/tests/services/integration/archive-to-import.spec.ts`), posibles ajustes en `archiveReader` para robustez. - **Tests to Write:** - `backend/tests/services/archiveReader.stream.spec.ts` (integration) — usa `simple.zip` fixture y verifica hashes. - `backend/tests/services/integration/archive-to-import.spec.ts` — E2E: `importDirectory` sobre carpeta con `simple.zip`, verificar DB upsert. - **Steps:** 1. Añadir fixtures de archive en `backend/tests/fixtures/archives/` (`simple.zip`, `nested.zip`, `traversal.zip`). 2. Marcar tests de integración opt-in mediante `INTEGRATION=1` o detectando binarios con helper (`tests/helpers/requireBinaries.ts`). 3. Ejecutar integraciones en local con `INTEGRATION=1` y en CI asegurando que `7z`/`unzip` se instalen. - **Acceptance:** Integration tests pasan en entornos con binarios; fallback JS para ZIP pasa cuando faltan binarios. **Decisiones concretas ya tomadas** - Representación en DB: usar `path` codificado con `::` (ej. `/abs/archive.zip::dir/inner.rom`) y no tocar Prisma inicialmente. - No soportar archives anidados por ahora (configurable en futuro). - Límites configurables (con valores por defecto razonables): - `ARCHIVE_MAX_ENTRY_SIZE` — tamaño máximo por entrada (por defecto: 200 MB). - `ARCHIVE_MAX_ENTRIES` — máximo de entradas a listar por archive (por defecto: 1000). - CI: instalar `7z` (`p7zip-full`) y `unzip` en runners para ejecutar tests de integración. **Riesgos y mitigaciones** - Path traversal: sanitizar `entryPath` y rechazar entradas que suban fuera del contenedor. - Zip bombs / entradas gigantes: respetar `ARCHIVE_MAX_ENTRY_SIZE` y abortar hashing si se excede. - Recursos por spawn: imponer timeouts y límites, cerrar streams correctamente. - Archivos cifrados/password: detectar y registrar como `status: 'encrypted'` o saltar. **Comandos recomendados para pruebas** ```bash yarn --cwd backend test yarn --cwd backend test tests/services/archiveReader.list.spec.ts INTEGRATION=1 yarn --cwd backend test tests/services/integration/archive-to-import.spec.ts ``` **Open Questions (resueltas por ti)** 1. Usar encoding `::` para `path` — Confirmado. 2. Soporte de archives anidados — Dejar fuera por ahora. 3. Límite por defecto por entrada — Configurable; por defecto 200 MB. 4. CI debe instalar `7z` y `unzip` — Confirmado. --- Si apruebas este plan, empezaré la Phase 1: escribiré los tests unitarios para `archiveReader` y delegaré la implementación al subagente implementador siguiendo TDD. ¿Procedo?