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