Files
quasar/plans/integrar-archive-entries-plan.md
Benito Rodríguez ab63361e66 feat: add streamArchiveEntry to archiveReader and tests
- 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
2026-02-09 19:15:55 +01:00

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

  1. Phase 1: Contracto de ArchiveReader (list + stream)

    • Objective: Definir y probar la API de archiveReader con dos funciones públicas: listArchiveEntries(containerPath): Promise<Entry[]> 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

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?