Casos de prueba: módulo File
Documenta los tests unitarios del módulo file (subida, almacenamiento y lectura de archivos con URL firmada).
Specs fuente (src/app/modules/file/):
controllers/file.controller.spec.tsservices/file.service.spec.ts
Resumen: 2 archivos de spec, 2 suites raíz, 38 tests it() (8 de controlador y 30 de servicio; uno de ellos usa it.each con 2 variantes, por lo que se ejecutan 31 casos en el servicio).
file.controller.spec.ts
Suite raíz: FileController. El servicio se reemplaza por un mock; el tenant se toma del usuario autenticado.
upload
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should delegate to service with user.tenantId and return translated message | Un usuario sube un archivo | Llama a service.upload con el archivo y el tenant del usuario y responde con mensaje traducido y el documento | mockService.upload |
should propagate errors thrown by the service | El servicio rechaza con storage error | El error se propaga | mockService.upload |
findAll
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should return all files scoped to user tenant | Listado de archivos | Llama a findAll(tenantId) y responde con la lista | mockService.findAll |
should return empty results when no files exist | Sin archivos | Respuesta con lista vacía | mockService.findAll |
findOne
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should return a single file scoped to user tenant | Archivo existente | Delegación con id y tenant y respuesta con el archivo | mockService.findOne |
should propagate NotFoundException when file is not found | No existe | La NotFoundException se propaga | mockService.findOne |
remove
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should delete file scoped to user tenant and return the document | Eliminación válida | Delegación con id y tenant y respuesta con el documento eliminado | mockService.remove |
should propagate NotFoundException when file is not found | No existe | La NotFoundException se propaga | mockService.remove |
file.service.spec.ts
Suite raíz: FileService. Mocks: StorageService (subida, borrado y URL firmada), repositorio de archivos, procesador de media (processAudio, processVideo, analyzeAudio, getAudioDuration) y traducción. Las imágenes se convierten a WebP al subir.
upload
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should upload to storage and persist the file document | Subida de una imagen | Sube al storage bajo tenantId/ con mimetype image/webp, persiste con tipo image y devuelve el documento | StorageService.upload, fileRepository.create |
should resolve type "audio" for audio mimetypes | Mimetype de audio | Se persiste con tipo audio | upload, create |
converts audio to OGG/Opus and stores it with that extension | Audio audio/webm (el navegador graba WebM y WhatsApp no lo reproduce) | Llama a processAudio, guarda mimetype audio/ogg y storageKey terminada en .ogg | processAudio, upload, create |
persists the duration and waveform of an audio | Subida de audio | Se persisten durationSeconds (6) y waveform para no decodificar en cada envío | processAudio, create |
does not attach audio metadata to a non-audio file | Subida de un PDF | No se agregan duración ni onda | create |
validates the duration before converting | Audio de 500 s | Rechaza y no llama a processAudio (no se paga la conversión de un audio rechazado) | getAudioDuration |
should resolve type "video" for video mimetypes | Mimetype de video | Se persiste con tipo video | processVideo, create |
persists the video thumbnail | Subida de video | Se persiste el thumbnail extraído | processVideo, create |
stores the video even when the thumbnail could not be extracted | processVideo no obtiene thumbnail | El video se guarda igualmente, sin preview | processVideo, create |
should resolve type "document" for non-media mimetypes | Mimetype no multimedia | Se persiste con tipo document | upload, create |
should use empty extension when filename has no dot | Nombre de archivo sin punto | La extensión de la clave de storage queda vacía | upload, create |
should resolve a signed url for the just-uploaded file instead of the stored (permanent) one | Subida exitosa | La URL devuelta es la firmada (getSignedUrl), no la permanente | getSignedUrl, create |
should accept XLSX documents (%s) | it.each con application/vnd.ms-excel (sheet.xls) y con el mimetype OOXML (sheet.xlsx) | Se aceptan y se persisten como document con su mimetype | upload, create |
should reject an image larger than 5 MB | Imagen mayor a 5 MB | Lanza BadRequestException y no persiste | fileRepository.create |
should accept an image at or under 5 MB | Imagen de 5 MB o menos | La subida se resuelve sin error | upload, create |
should reject audio shorter than 1 second | Audio de 0,5 s | Lanza BadRequestException y no persiste | getAudioDuration |
should reject audio longer than 120 seconds | Audio de 121 s | Lanza BadRequestException y no persiste | getAudioDuration |
should accept audio within the 1s–120s range | Audio de 60 s | La subida se resuelve sin error | getAudioDuration, upload, create |
should reject an audio file larger than the 15 MB defense-in-depth cap | Audio de más de 15 MB | Lanza BadRequestException; no mide duración ni persiste | getAudioDuration, create |
storeIncoming
Almacena archivos recibidos de canales externos (no validados como subida de usuario).
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
stores the file without validating its mime type | Archivo entrante de documento | Se guarda sin validar el mimetype | upload, create |
reads duration and waveform of an incoming audio without re-encoding it | Audio entrante | Llama a analyzeAudio, no a processAudio, y persiste duración y onda | analyzeAudio, processAudio, create |
stores the audio anyway when it cannot be analyzed | analyzeAudio rechaza con corrupt | El audio se guarda igualmente | analyzeAudio rechaza, create |
does not analyze a non-audio file | Imagen entrante | No se llama a analyzeAudio | analyzeAudio, create |
findAll
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should return all files for the tenant | Tenant con archivos | Llama a findAll(tenantId) y devuelve los mismos documentos (mutados con URL firmada) | fileRepository.findAll |
should resolve a signed url for every file instead of reusing the stored one | Varios archivos | Firma la URL de cada archivo desde su storageKey y la asigna a url | findAll, getSignedUrl |
findOne
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should return the file when found | Archivo del tenant | Busca por id y tenant y lo devuelve | findByIdAndTenant |
should resolve a signed url from storageKey instead of the stored (permanent) url | Archivo encontrado | url es la firmada y distinta de la permanente | findByIdAndTenant, getSignedUrl |
should throw NotFoundException when file is not found | findByIdAndTenant devuelve null | Lanza NotFoundException con mensaje traducido | findByIdAndTenant, traducción |
remove
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should delete from storage and repository, then return the document | Archivo existente | Borra del storage por storageKey, elimina del repositorio y devuelve el documento | findByIdAndTenant, storage.delete, remove |
should throw NotFoundException when file is not found | Archivo inexistente | Lanza NotFoundException y no borra del storage | findByIdAndTenant (null) |