feat: Funcion de sistema tenants

This commit is contained in:
2026-02-23 13:01:24 -07:00
parent ceea67eb2b
commit 1ccc39732b
58 changed files with 1889 additions and 315 deletions

View File

@@ -0,0 +1,80 @@
"""Unit Tests - FileHandler - ServiceManagerWeb
Tests para app.core.file_handler.FileHandler.
"""
import io
import uuid
import tempfile
import pytest
from fastapi import UploadFile
from fastapi import HTTPException
@pytest.mark.asyncio
async def test_save_upload_pdf_valid_streaming():
from app.core.file_handler import FileHandler, settings
with tempfile.TemporaryDirectory() as tmp:
settings.UPLOAD_PATH = tmp
handler = FileHandler()
tenant_id = uuid.uuid4()
ticket_id = uuid.uuid4()
content = b"%PDF-1.7\n%\xe2\xe3\xcf\xd3\n1 0 obj\n<<>>\nendobj\n"
up = UploadFile(filename="test.pdf", file=io.BytesIO(content))
meta = await handler.save_upload(up, tenant_id=tenant_id, ticket_id=ticket_id)
assert meta["file_size"] == len(content)
assert meta["original_filename"] == "test.pdf"
assert meta["filename"].endswith(".pdf")
assert meta["md5_hash"]
assert meta["sha256_hash"]
@pytest.mark.asyncio
async def test_save_upload_pdf_invalid_magic_bytes_rejected():
from app.core.file_handler import FileHandler, settings
with tempfile.TemporaryDirectory() as tmp:
settings.UPLOAD_PATH = tmp
handler = FileHandler()
up = UploadFile(filename="bad.pdf", file=io.BytesIO(b"NOTPDF"))
with pytest.raises(HTTPException) as exc:
await handler.save_upload(up, tenant_id=uuid.uuid4(), ticket_id=uuid.uuid4())
assert exc.value.status_code == 400
@pytest.mark.asyncio
async def test_save_upload_oversize_rejected_and_file_removed():
from app.core.file_handler import FileHandler, settings
with tempfile.TemporaryDirectory() as tmp:
settings.UPLOAD_PATH = tmp
settings.MAX_UPLOAD_SIZE_MB = 0 # 0MB => max 0 bytes
handler = FileHandler()
up = UploadFile(filename="a.txt", file=io.BytesIO(b"x"))
with pytest.raises(HTTPException) as exc:
await handler.save_upload(up, tenant_id=uuid.uuid4(), ticket_id=uuid.uuid4())
assert exc.value.status_code == 413
def test_get_file_path_prevents_path_traversal():
from app.core.file_handler import FileHandler, settings
with tempfile.TemporaryDirectory() as tmp:
settings.UPLOAD_PATH = tmp
handler = FileHandler()
with pytest.raises(HTTPException) as exc:
handler.get_file_path("../../etc/passwd")
assert exc.value.status_code == 403

View File

@@ -124,8 +124,8 @@ class TestMiddlewareNoTenantHeaders:
"""Tests para requests sin headers de tenant."""
@pytest.mark.asyncio
async def test_missing_tenant_headers_in_dev_continues(self):
"""En entorno de desarrollo, sin tenant headers continúa con advertencia."""
async def test_missing_tenant_headers_returns_400(self):
"""Sin tenant headers debe retornar 400 (requerido para aislamiento multi-tenant)."""
from app.middleware.tenant import TenantMiddleware
mock_app = AsyncMock()
@@ -139,18 +139,15 @@ class TestMiddlewareNoTenantHeaders:
call_next = AsyncMock(return_value=MagicMock(status_code=200))
# En modo testing (que hereda de development), debe continuar
response = await middleware.dispatch(request, call_next)
# El request continúa (call_next fue llamado)
call_next.assert_called_once()
assert response.status_code == 400
call_next.assert_not_called()
@pytest.mark.asyncio
async def test_missing_tenant_headers_in_production_returns_400(self):
"""En producción, sin tenant headers retorna 400."""
async def test_missing_tenant_headers_does_not_call_next(self):
"""Sin tenant headers no debe llegar al handler (call_next)."""
from app.middleware.tenant import TenantMiddleware
from app.core.config import get_settings
from starlette.responses import JSONResponse
mock_app = AsyncMock()
middleware = TenantMiddleware(mock_app)
@@ -163,13 +160,10 @@ class TestMiddlewareNoTenantHeaders:
call_next = AsyncMock(return_value=MagicMock(status_code=200))
with patch.object(get_settings(), "ENVIRONMENT", "production"):
response = await middleware.dispatch(request, call_next)
response = await middleware.dispatch(request, call_next)
# En producción sin tenant debe retornar error
# (si la response es JSONResponse con status 400, el test pasa)
if hasattr(response, "status_code"):
assert response.status_code in [400, 200] # depende del env
assert response.status_code == 400
call_next.assert_not_called()
# ============================================================