# 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:**
```json
{
  "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:**
```json
{
  "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:**
```json
{
  "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

```typescript
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

```typescript
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 |
