Files
service_manager/backend/app/tests/test_tenant_isolation.py
2026-03-03 14:02:18 -07:00

124 lines
4.3 KiB
Python

"""
Pruebas de aislamiento multi-tenant para tickets y usuarios.
Usa solo los fixtures definidos en conftest.py.
Roles en juego:
client_tenant_a → SUPPORT_MANAGER (aduanasoft) — restringido a su tenant
client_tenant_b → CLIENT_ADMIN (empresa-test) — restringido a su tenant
client_admin → ADMIN global (aduanasoft) — acceso a todos los tenants
"""
import pytest
@pytest.mark.asyncio
async def test_tenant_a_cannot_see_tenant_b_tickets(
client_tenant_a, create_ticket_tenant_b
):
"""
El usuario del tenant B crea un ticket.
El usuario del tenant A (SUPPORT_MANAGER) lista sus tickets.
El ticket de tenant B NO debe aparecer en la respuesta.
"""
# El usuario del tenant B crea un ticket
ticket_b = await create_ticket_tenant_b()
ticket_b_id = ticket_b["id"]
# El usuario del tenant A lista sus tickets
response = await client_tenant_a.get("/v1/tickets/")
assert response.status_code == 200
ids_visibles = {t["id"] for t in response.json()}
# El ticket de tenant B no debe ser visible para tenant A
assert ticket_b_id not in ids_visibles, (
f"Fallo de aislamiento: ticket de tenant B ({ticket_b_id}) "
f"visible para usuario de tenant A"
)
@pytest.mark.asyncio
async def test_tenant_b_cannot_edit_tenant_a_ticket(
create_ticket_tenant_a, client_tenant_b
):
"""
El usuario del tenant A crea un ticket.
El usuario del tenant B intenta editar ese ticket vía PATCH.
Debe recibir 403 (prohibido) o 404 (no encontrado).
"""
# El usuario del tenant A crea un ticket
ticket_a = await create_ticket_tenant_a()
ticket_a_id = ticket_a["id"]
# El usuario del tenant B intenta editar el ticket de tenant A
response = await client_tenant_b.patch(
f"/v1/tickets/{ticket_a_id}",
json={"status": "CLOSED"},
)
# Debe recibir 403 o 404 — nunca 200
assert response.status_code in (403, 404), (
f"Fallo de aislamiento: tenant B pudo editar ticket de tenant A "
f"(HTTP {response.status_code})"
)
@pytest.mark.asyncio
async def test_tenant_a_cannot_see_tenant_b_users(
client_tenant_a, create_user_tenant_b
):
"""
El usuario del tenant B crea un usuario nuevo.
El usuario del tenant A (SUPPORT_MANAGER) lista los usuarios.
El usuario de tenant B NO debe aparecer en la respuesta.
"""
# El usuario del tenant B crea un usuario
user_b = await create_user_tenant_b()
user_b_id = user_b["id"]
# El usuario del tenant A lista los usuarios de su tenant
response = await client_tenant_a.get("/v1/users/")
assert response.status_code == 200
ids_visibles = {u["id"] for u in response.json()}
# El usuario de tenant B no debe ser visible para tenant A
assert user_b_id not in ids_visibles, (
f"Fallo de aislamiento: usuario de tenant B ({user_b_id}) "
f"visible para usuario de tenant A"
)
@pytest.mark.asyncio
async def test_admin_sees_all_tenant_data(
client_admin,
create_ticket_tenant_a,
create_ticket_tenant_b,
create_user_tenant_a,
create_user_tenant_b,
):
"""
El ADMIN global debe poder ver tickets y usuarios de TODOS los tenants.
- Tickets: vía /v1/tickets/admin/all (endpoint multi-tenant).
- Usuarios: vía /v1/users/ (ADMIN bypasa el filtro de tenant).
"""
# Crear datos en ambos tenants
ticket_a = await create_ticket_tenant_a()
ticket_b = await create_ticket_tenant_b()
user_a = await create_user_tenant_a()
user_b = await create_user_tenant_b()
# El admin lista todos los tickets (endpoint multi-tenant)
resp_tickets = await client_admin.get("/v1/tickets/admin/all")
assert resp_tickets.status_code == 200
ids_tickets = {t["id"] for t in resp_tickets.json()}
assert ticket_a["id"] in ids_tickets, "El ADMIN no ve el ticket de tenant A"
assert ticket_b["id"] in ids_tickets, "El ADMIN no ve el ticket de tenant B"
# El admin lista todos los usuarios (ADMIN bypasa filtro de tenant)
resp_users = await client_admin.get("/v1/users/")
assert resp_users.status_code == 200
ids_users = {u["id"] for u in resp_users.json()}
assert user_a["id"] in ids_users, "El ADMIN no ve el usuario de tenant A"
assert user_b["id"] in ids_users, "El ADMIN no ve el usuario de tenant B"