erp-core/docs/06-test-plans/TP-tenants.md

13 KiB

Plan de Pruebas - MGN-004 Multi-Tenancy

Identificacion

Campo Valor
Modulo MGN-004
Nombre Multi-Tenancy
Version 1.0
Fecha 2025-12-05
Autor System

Alcance

En Alcance

  • CRUD de Tenants (Platform Admin)
  • Configuracion de Tenant (Settings)
  • Aislamiento de datos (RLS)
  • Guards: TenantGuard, LimitGuard, ModuleGuard
  • Subscripciones y Planes
  • Verificacion de limites

Fuera de Alcance

  • Integracion real con pasarela de pagos
  • UI de plataforma admin
  • Performance testing exhaustivo (otro documento)

Casos de Prueba

TP-TENANT-001: Crear Tenant

Campo Valor
Tipo Unitario / Integracion
Prioridad Alta
US Relacionada US-MGN004-001

Precondiciones:

  • Platform Admin autenticado
  • Schema core_tenants existe

Pasos:

  1. Enviar POST /api/v1/platform/tenants con datos validos
  2. Verificar respuesta 201
  3. Verificar tenant creado en BD
  4. Verificar settings creados por defecto

Datos de Prueba:

{
  "name": "Empresa Test",
  "slug": "empresa-test",
  "subdomain": "test"
}

Resultado Esperado:

  • Tenant creado con status "created"
  • tenant_settings creado con valores vacios
  • created_at y created_by populados

TP-TENANT-002: Slug Duplicado

Campo Valor
Tipo Unitario
Prioridad Alta
US Relacionada US-MGN004-001

Precondiciones:

  • Existe tenant con slug "empresa-abc"

Pasos:

  1. Intentar crear tenant con slug "empresa-abc"
  2. Verificar respuesta 409 Conflict

Resultado Esperado:

  • Error 409 con mensaje "Slug ya existe"
  • No se crea nuevo tenant

TP-TENANT-003: Transicion de Estados

Campo Valor
Tipo Unitario
Prioridad Alta
US Relacionada US-MGN004-001

Pasos:

  1. Crear tenant (status: created)
  2. Iniciar trial (status: trial, trial_ends_at set)
  3. Activar (status: active)
  4. Suspender (status: suspended, suspended_at set)
  5. Reactivar (status: active, suspended_at cleared)
  6. Programar eliminacion (status: pending_deletion)
  7. Restaurar (status: active, deletion_scheduled_at cleared)

Resultado Esperado:

  • Cada transicion actualiza los campos correspondientes
  • Campos de fecha se setean/limpian correctamente

TP-TENANT-004: Actualizar Settings

Campo Valor
Tipo Unitario
Prioridad Media
US Relacionada US-MGN004-002

Precondiciones:

  • Tenant existente
  • Tenant Admin autenticado

Pasos:

  1. GET /api/v1/tenant/settings (verificar defaults)
  2. PATCH con company.companyName = "Nueva Empresa"
  3. GET settings, verificar merge correcto

Datos de Prueba:

{
  "company": {
    "companyName": "Nueva Empresa S.A."
  }
}

Resultado Esperado:

  • Solo company.companyName cambia
  • Otros campos de company mantienen defaults
  • Otras secciones no se modifican

TP-TENANT-005: Merge con Defaults

Campo Valor
Tipo Unitario
Prioridad Media
US Relacionada US-MGN004-002

Precondiciones:

  • Tenant con regional.defaultTimezone = "America/New_York"
  • Default de plataforma regional.dateFormat = "DD/MM/YYYY"

Pasos:

  1. GET /api/v1/tenant/settings
  2. Verificar que timezone es "America/New_York" (override)
  3. Verificar que dateFormat es "DD/MM/YYYY" (default)

Resultado Esperado:

  • Valores del tenant sobrescriben defaults
  • Valores no seteados usan defaults de plataforma

TP-TENANT-006: RLS - Solo Ver Datos Propios

Campo Valor
Tipo Integracion / Seguridad
Prioridad Critica
US Relacionada US-MGN004-003

Precondiciones:

  • Tenant A con 100 productos
  • Tenant B con 50 productos
  • Usuario de Tenant A autenticado

Pasos:

  1. GET /api/v1/products
  2. Verificar que solo retorna productos de Tenant A

Resultado Esperado:

  • Exactamente 100 productos retornados
  • Ningun producto de Tenant B visible
  • RLS filtra automaticamente

TP-TENANT-007: RLS - Recurso de Otro Tenant No Accesible

Campo Valor
Tipo Integracion / Seguridad
Prioridad Critica
US Relacionada US-MGN004-003

Precondiciones:

  • Producto "P-001" pertenece a Tenant B
  • Usuario de Tenant A autenticado

Pasos:

  1. GET /api/v1/products/P-001
  2. Verificar respuesta 404

Resultado Esperado:

  • Status 404 Not Found
  • Mensaje generico "Recurso no encontrado"
  • NO revelar que existe en otro tenant

TP-TENANT-008: RLS - Query Sin Contexto

Campo Valor
Tipo Unitario / Seguridad
Prioridad Critica
US Relacionada US-MGN004-003

Precondiciones:

  • Datos en tabla users de multiples tenants

Pasos:

  1. Ejecutar query directa sin SET app.current_tenant_id
  2. SELECT * FROM core_users.users

Resultado Esperado:

  • Query retorna 0 registros
  • RLS previene acceso sin contexto

TP-TENANT-009: TenantGuard - Tenant Suspendido

Campo Valor
Tipo Unitario
Prioridad Alta
US Relacionada US-MGN004-003

Precondiciones:

  • Tenant en status "suspended"
  • Usuario con token valido de ese tenant

Pasos:

  1. Hacer cualquier request autenticado
  2. Verificar respuesta 403

Resultado Esperado:

  • Status 403 Forbidden
  • Mensaje "Tenant suspendido: acceso denegado"

TP-TENANT-010: TenantGuard - Trial Expirado

Campo Valor
Tipo Unitario
Prioridad Alta
US Relacionada US-MGN004-003

Precondiciones:

  • Tenant en status "trial"
  • trial_ends_at = hace 2 dias

Pasos:

  1. Hacer cualquier request autenticado
  2. Verificar respuesta 403

Resultado Esperado:

  • Status 403 Forbidden
  • Mensaje "Periodo de prueba expirado"

TP-TENANT-011: LimitGuard - Limite de Usuarios

Campo Valor
Tipo Unitario
Prioridad Alta
US Relacionada US-MGN004-004

Precondiciones:

  • Tenant con plan Starter (max 10 usuarios)
  • 10 usuarios existentes

Pasos:

  1. POST /api/v1/users (crear usuario #11)
  2. Verificar respuesta 402

Resultado Esperado:

  • Status 402 Payment Required
  • Mensaje indica limite alcanzado
  • Incluye upgradeOptions con planes disponibles

TP-TENANT-012: LimitGuard - Bajo el Limite

Campo Valor
Tipo Unitario
Prioridad Media
US Relacionada US-MGN004-004

Precondiciones:

  • Tenant con plan Starter (max 10 usuarios)
  • 5 usuarios existentes

Pasos:

  1. POST /api/v1/users (crear usuario #6)
  2. Verificar respuesta 201

Resultado Esperado:

  • Usuario creado exitosamente
  • LimitGuard permite la operacion

TP-TENANT-013: ModuleGuard - Modulo No Incluido

Campo Valor
Tipo Unitario
Prioridad Alta
US Relacionada US-MGN004-004

Precondiciones:

  • Tenant con plan Starter (sin CRM)
  • Endpoint de CRM con @CheckModule('crm')

Pasos:

  1. GET /api/v1/crm/contacts
  2. Verificar respuesta 402

Resultado Esperado:

  • Status 402 Payment Required
  • Mensaje "Modulo CRM no incluido en tu plan"

TP-TENANT-014: Verificar Limite

Campo Valor
Tipo Unitario
Prioridad Media
US Relacionada US-MGN004-004

Precondiciones:

  • Tenant con plan Professional (50 usuarios)
  • 35 usuarios existentes

Pasos:

  1. GET /api/v1/tenant/subscription/check-limit?type=users
  2. Verificar respuesta

Resultado Esperado:

{
  "type": "users",
  "current": 35,
  "limit": 50,
  "canAdd": true,
  "remaining": 15
}

TP-TENANT-015: Upgrade de Plan

Campo Valor
Tipo Integracion
Prioridad Alta
US Relacionada US-MGN004-004

Precondiciones:

  • Tenant con plan Starter ($29/mes)
  • Dia 15 del mes

Pasos:

  1. POST /api/v1/tenant/subscription/upgrade con plan Professional
  2. Verificar prorrateo calculado
  3. Verificar plan actualizado

Resultado Esperado:

  • Prorrateo: ($99 - $29) / 2 = $35
  • Plan cambia a Professional
  • Nuevos limites aplican inmediatamente

TP-TENANT-016: Cancelar Subscripcion

Campo Valor
Tipo Unitario
Prioridad Media
US Relacionada US-MGN004-004

Precondiciones:

  • Tenant con subscripcion activa

Pasos:

  1. POST /api/v1/tenant/subscription/cancel
  2. Verificar subscripcion marcada para cancelar

Resultado Esperado:

  • cancel_at_period_end = true
  • canceled_at = timestamp actual
  • Status sigue "active" hasta fin de periodo

TP-TENANT-017: Platform Admin Switch Tenant

Campo Valor
Tipo Integracion
Prioridad Media
US Relacionada US-MGN004-001

Precondiciones:

  • Platform Admin autenticado
  • Tenant A y Tenant B existen

Pasos:

  1. POST /api/v1/platform/switch-tenant/tenant-a-id
  2. GET /api/v1/users (ve usuarios de Tenant A)
  3. POST /api/v1/platform/switch-tenant/tenant-b-id
  4. GET /api/v1/users (ve usuarios de Tenant B)

Resultado Esperado:

  • Contexto cambia correctamente
  • Datos de tenant correcto visibles
  • Accion queda auditada

TP-TENANT-018: Tenant Auto-Asignado en Creacion

Campo Valor
Tipo Unitario
Prioridad Media
US Relacionada US-MGN004-003

Precondiciones:

  • Usuario de Tenant A autenticado

Pasos:

  1. POST /api/v1/products sin tenant_id en body
  2. Verificar producto creado

Resultado Esperado:

  • Producto creado con tenant_id = Tenant A
  • No se permite especificar tenant_id diferente

Resumen de Casos de Prueba

Categoria Cantidad Prioridad Critica Prioridad Alta Prioridad Media
CRUD Tenants 3 0 3 0
Settings 2 0 0 2
RLS/Aislamiento 5 3 2 0
Guards 3 0 2 1
Subscripciones 4 0 2 2
Platform Admin 1 0 0 1
Total 18 3 9 6

Tests de Seguridad Adicionales

SEC-001: Cross-Tenant Data Access

describe('Cross-Tenant Security', () => {
  it('should NOT allow access to other tenant resources', async () => {
    // Crear datos en Tenant B
    const productB = await createProduct(tenantBId);

    // Autenticar como usuario de Tenant A
    const tokenA = await login(userTenantA);

    // Intentar acceder a recurso de Tenant B
    const response = await request(app)
      .get(`/api/v1/products/${productB.id}`)
      .set('Authorization', `Bearer ${tokenA}`);

    expect(response.status).toBe(404);
  });

  it('should NOT allow UPDATE of other tenant resources', async () => {
    const productB = await createProduct(tenantBId);
    const tokenA = await login(userTenantA);

    const response = await request(app)
      .patch(`/api/v1/products/${productB.id}`)
      .set('Authorization', `Bearer ${tokenA}`)
      .send({ name: 'Hacked' });

    expect(response.status).toBe(404);

    // Verificar que no se modifico
    const product = await getProduct(productB.id, tenantBId);
    expect(product.name).not.toBe('Hacked');
  });

  it('should NOT allow DELETE of other tenant resources', async () => {
    const productB = await createProduct(tenantBId);
    const tokenA = await login(userTenantA);

    const response = await request(app)
      .delete(`/api/v1/products/${productB.id}`)
      .set('Authorization', `Bearer ${tokenA}`);

    expect(response.status).toBe(404);

    // Verificar que no se elimino
    const product = await getProduct(productB.id, tenantBId);
    expect(product).toBeDefined();
  });
});

SEC-002: Tenant ID Injection

describe('Tenant ID Injection Prevention', () => {
  it('should NOT allow tenant_id in request body', async () => {
    const tokenA = await login(userTenantA);

    const response = await request(app)
      .post('/api/v1/products')
      .set('Authorization', `Bearer ${tokenA}`)
      .send({
        name: 'Product',
        tenant_id: tenantBId, // Intento de inyeccion
      });

    expect(response.status).toBe(201);
    // Verificar que se uso tenant del token, no del body
    expect(response.body.tenant_id).toBe(tenantAId);
  });

  it('should NOT allow tenant_id modification via UPDATE', async () => {
    const product = await createProduct(tenantAId);
    const tokenA = await login(userTenantA);

    const response = await request(app)
      .patch(`/api/v1/products/${product.id}`)
      .set('Authorization', `Bearer ${tokenA}`)
      .send({
        tenant_id: tenantBId, // Intento de cambiar tenant
      });

    expect(response.status).toBe(400);
  });
});

Cobertura de Codigo

Componente Objetivo
TenantsService > 85%
TenantSettingsService > 80%
SubscriptionsService > 85%
TenantGuard > 90%
LimitGuard > 90%
ModuleGuard > 90%
TenantContextMiddleware > 80%
Total Modulo > 85%

Matriz de Trazabilidad

User Story Casos de Prueba
US-MGN004-001 TP-001, TP-002, TP-003, TP-017
US-MGN004-002 TP-004, TP-005
US-MGN004-003 TP-006, TP-007, TP-008, TP-009, TP-010, TP-018
US-MGN004-004 TP-011, TP-012, TP-013, TP-014, TP-015, TP-016

Historial

Version Fecha Autor Cambios
1.0 2025-12-05 System Creacion inicial con 18 casos