13 KiB
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:
- Enviar POST /api/v1/platform/tenants con datos validos
- Verificar respuesta 201
- Verificar tenant creado en BD
- 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:
- Intentar crear tenant con slug "empresa-abc"
- 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:
- Crear tenant (status: created)
- Iniciar trial (status: trial, trial_ends_at set)
- Activar (status: active)
- Suspender (status: suspended, suspended_at set)
- Reactivar (status: active, suspended_at cleared)
- Programar eliminacion (status: pending_deletion)
- 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:
- GET /api/v1/tenant/settings (verificar defaults)
- PATCH con company.companyName = "Nueva Empresa"
- 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:
- GET /api/v1/tenant/settings
- Verificar que timezone es "America/New_York" (override)
- 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:
- GET /api/v1/products
- 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:
- GET /api/v1/products/P-001
- 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:
- Ejecutar query directa sin SET app.current_tenant_id
- 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:
- Hacer cualquier request autenticado
- 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:
- Hacer cualquier request autenticado
- 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:
- POST /api/v1/users (crear usuario #11)
- 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:
- POST /api/v1/users (crear usuario #6)
- 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:
- GET /api/v1/crm/contacts
- 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:
- GET /api/v1/tenant/subscription/check-limit?type=users
- 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:
- POST /api/v1/tenant/subscription/upgrade con plan Professional
- Verificar prorrateo calculado
- 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:
- POST /api/v1/tenant/subscription/cancel
- 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:
- POST /api/v1/platform/switch-tenant/tenant-a-id
- GET /api/v1/users (ve usuarios de Tenant A)
- POST /api/v1/platform/switch-tenant/tenant-b-id
- 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:
- POST /api/v1/products sin tenant_id en body
- 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 |