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

607 lines
13 KiB
Markdown

# 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 |