- Añade `streamArchiveEntry` que devuelve un stream para entradas internas de ZIP/7z - Añade tests unitarios que mockean `child_process.spawn` (7z + unzip fallback) - Mantiene `listArchiveEntries` y documenta dependencia de binarios en CI
6.8 KiB
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
-
Phase 1: Contracto de ArchiveReader (list + stream)
- Objective: Definir y probar la API de
archiveReadercon dos funciones públicas:listArchiveEntries(containerPath): Promise<Entry[]>ystreamArchiveEntry(containerPath, entryPath): Readable. - Files/Functions to Modify/Create:
backend/src/services/archiveReader.ts(añadirstreamArchiveEntry, documentar comportamiento y fallback a librería JS para ZIP si falta7z). - Tests to Write:
backend/tests/services/archiveReader.list.spec.ts— unit: mockearchild_process.execpara simular salida de7z -sltyunzip -l.backend/tests/services/archiveReader.stream.spec.ts— unit: mockearchild_processy stream; integration opcional (ver Fase 4).
- Steps:
- Escribir tests (fallando) que describan la API y el formato de
Entry({ name, size }). - Implementar
streamArchiveEntryusando7z x -soounzip -py devolver unReadable. - Añadir fallback para ZIP mediante librería JS si
7zno está disponible. - Ejecutar y hacer pasar tests unitarios.
- Escribir tests (fallando) que describan la API y el formato de
- Acceptance: Tests unitarios pasan;
streamArchiveEntryes mockeable y devuelve stream.
- Objective: Definir y probar la API de
-
Phase 2: Extender
fsScannerpara exponer entradas (virtual files)- Objective:
scanDirectory(dir)debe incluir entradas internas de archivos contenedor como items virtuales conpathcodificado (/abs/archive.zip::inner/path.rom),filename= basename(inner),isArchiveEntry = true. - Files/Functions to Modify/Create:
backend/src/services/fsScanner.ts(usararchiveReader.listArchiveEntries). - Tests to Write:
backend/tests/services/fsScanner.archiveEntries.spec.ts— unit: mockeararchiveReader.listArchiveEntriesy verificar formato.
- Steps:
- Escribir test unitario (fallando) que verifica que
scanDirectoryinvocaarchiveReadery añade entradas codificadas. - Implementar la integración mínima en
fsScanner(sin extracción, solo listar entradas). - Ejecutar tests y ajustar.
- Escribir test unitario (fallando) que verifica que
- Acceptance:
scanDirectorydevuelve objetos virtuales estandarizados; tests unitarios pasan.
- Objective:
-
Phase 3: Hashing por stream y soporte en
importService(unit)- Objective: Añadir
computeHashesFromStream(stream)y hacer queimportDirectorypueda procesar entradas internas usandoarchiveReader.streamArchiveEntrypara obtener hashes sin escribir ficheros temporales. - Files/Functions to Modify/Create:
backend/src/services/checksumService.ts(añadircomputeHashesFromStream),backend/src/services/importService.ts(aceptarisArchiveEntryy usararchiveReader.streamArchiveEntry). - Tests to Write:
backend/tests/services/checksumService.stream.spec.ts— unit: hashing desde unReadablecreado desde un fixture (backend/tests/fixtures/simple-rom.bin).backend/tests/services/importService.archiveEntry.spec.ts— unit: mockearscanDirectorypara devolver entry codificada, mockeararchiveReader.streamArchiveEntrypara devolver stream desde fixture, mockear Prisma y verificarupsertconpathcodificado.
- Steps:
- Escribir tests (fallando) que describan el comportamiento.
- Implementar
computeHashesFromStream(stream)(MD5/SHA1/CRC32) y refactorizarcomputeHashespara delegar cuando se dispone de stream. - Hacer
importDirectorysoportar entries internas: obtener stream, calcular hashes, persistir conpathcodificado. - Ejecutar y pasar tests unitarios.
- Acceptance: Unit tests pasan;
importDirectoryhace upsert conpathcodificado y hashes correctos.
- Objective: Añadir
-
Phase 4: Integración real y CI opt-in
- Objective: Validar flujo end-to-end con binarios nativos (
7zyunzip) usando fixtures reales enbackend/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 enarchiveReaderpara robustez. - Tests to Write:
backend/tests/services/archiveReader.stream.spec.ts(integration) — usasimple.zipfixture y verifica hashes.backend/tests/services/integration/archive-to-import.spec.ts— E2E:importDirectorysobre carpeta consimple.zip, verificar DB upsert.
- Steps:
- Añadir fixtures de archive en
backend/tests/fixtures/archives/(simple.zip,nested.zip,traversal.zip). - Marcar tests de integración opt-in mediante
INTEGRATION=1o detectando binarios con helper (tests/helpers/requireBinaries.ts). - Ejecutar integraciones en local con
INTEGRATION=1y en CI asegurando que7z/unzipse instalen.
- Añadir fixtures de archive en
- Acceptance: Integration tests pasan en entornos con binarios; fallback JS para ZIP pasa cuando faltan binarios.
- Objective: Validar flujo end-to-end con binarios nativos (
Decisiones concretas ya tomadas
- Representación en DB: usar
pathcodificado 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) yunzipen runners para ejecutar tests de integración.
Riesgos y mitigaciones
- Path traversal: sanitizar
entryPathy rechazar entradas que suban fuera del contenedor. - Zip bombs / entradas gigantes: respetar
ARCHIVE_MAX_ENTRY_SIZEy 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
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)
- Usar encoding
::parapath— Confirmado. - Soporte de archives anidados — Dejar fuera por ahora.
- Límite por defecto por entrada — Configurable; por defecto 200 MB.
- CI debe instalar
7zyunzip— 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?