716 lines
20 KiB
Markdown
716 lines
20 KiB
Markdown
# Test Plan: MGN-003 Roles/RBAC
|
|
|
|
## Identificacion
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **ID** | TP-MGN003 |
|
|
| **Modulo** | MGN-003 Roles/RBAC |
|
|
| **Version** | 1.0 |
|
|
| **Fecha** | 2025-12-05 |
|
|
| **Autor** | System |
|
|
| **Estado** | Ready |
|
|
|
|
---
|
|
|
|
## Alcance
|
|
|
|
Este plan de pruebas cubre todos los flujos del sistema de control de acceso basado en roles:
|
|
|
|
| RF | Nombre | Prioridad |
|
|
|----|--------|-----------|
|
|
| RF-ROLE-001 | CRUD de Roles | P0 |
|
|
| RF-ROLE-002 | Gestion de Permisos | P0 |
|
|
| RF-ROLE-003 | Asignacion Roles-Usuarios | P0 |
|
|
| RF-ROLE-004 | Guards y Middlewares RBAC | P0 |
|
|
|
|
---
|
|
|
|
## Estrategia de Pruebas
|
|
|
|
### Niveles de Testing
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ E2E Tests (10%) │
|
|
│ Cypress - Flujos completos de gestion de roles │
|
|
├─────────────────────────────────────────────────────────────────┤
|
|
│ Integration Tests (30%) │
|
|
│ Supertest - API endpoints, guards, validacion permisos │
|
|
├─────────────────────────────────────────────────────────────────┤
|
|
│ Unit Tests (60%) │
|
|
│ Jest - Services, guards, decorators, cache │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Cobertura Objetivo
|
|
|
|
| Tipo | Coverage Target |
|
|
|------|-----------------|
|
|
| Unit Tests | > 85% |
|
|
| Integration Tests | > 75% |
|
|
| E2E Tests | Flujos criticos |
|
|
|
|
---
|
|
|
|
## Test Cases
|
|
|
|
### TC-RBAC-001: Crear rol exitosamente
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **RF** | RF-ROLE-001 |
|
|
| **Tipo** | Integration |
|
|
| **Prioridad** | P0 |
|
|
|
|
**Precondiciones:**
|
|
- Admin autenticado con permiso `roles:create`
|
|
- Permisos existentes en el sistema
|
|
|
|
**Datos de entrada:**
|
|
```json
|
|
{
|
|
"name": "Vendedor",
|
|
"description": "Equipo de ventas",
|
|
"permissionIds": ["perm-uuid-1", "perm-uuid-2"]
|
|
}
|
|
```
|
|
|
|
**Pasos:**
|
|
1. POST /api/v1/roles con datos validos
|
|
2. Verificar response status 201
|
|
3. Verificar estructura del rol retornado
|
|
4. Verificar slug generado
|
|
5. Verificar isBuiltIn = false
|
|
|
|
**Implementacion Jest:**
|
|
```typescript
|
|
describe('RolesController - Create', () => {
|
|
it('should create role with permissions', async () => {
|
|
const createDto = {
|
|
name: 'Vendedor',
|
|
description: 'Equipo de ventas',
|
|
permissionIds: [testPermission1.id, testPermission2.id],
|
|
};
|
|
|
|
const response = await request(app.getHttpServer())
|
|
.post('/api/v1/roles')
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.send(createDto)
|
|
.expect(201);
|
|
|
|
expect(response.body).toMatchObject({
|
|
name: 'Vendedor',
|
|
slug: 'vendedor',
|
|
description: 'Equipo de ventas',
|
|
isBuiltIn: false,
|
|
});
|
|
expect(response.body.permissions).toHaveLength(2);
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### TC-RBAC-002: No crear rol con nombre duplicado
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **RF** | RF-ROLE-001 |
|
|
| **Tipo** | Integration |
|
|
| **Prioridad** | P0 |
|
|
|
|
**Implementacion Jest:**
|
|
```typescript
|
|
it('should not create role with duplicate name', async () => {
|
|
// Crear primer rol
|
|
await rolesService.create(tenantId, {
|
|
name: 'Vendedor',
|
|
permissionIds: [testPermission.id],
|
|
}, adminUser.id);
|
|
|
|
// Intentar crear duplicado
|
|
const response = await request(app.getHttpServer())
|
|
.post('/api/v1/roles')
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.send({ name: 'Vendedor', permissionIds: [testPermission.id] })
|
|
.expect(409);
|
|
|
|
expect(response.body.message).toBe('Ya existe un rol con este nombre');
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### TC-RBAC-003: No eliminar rol built-in
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **RF** | RF-ROLE-001 |
|
|
| **Tipo** | Integration |
|
|
| **Prioridad** | P0 |
|
|
|
|
**Implementacion Jest:**
|
|
```typescript
|
|
it('should not delete built-in role', async () => {
|
|
const adminRole = await roleRepository.findOne({
|
|
where: { slug: 'admin', tenantId },
|
|
});
|
|
|
|
const response = await request(app.getHttpServer())
|
|
.delete(`/api/v1/roles/${adminRole.id}`)
|
|
.set('Authorization', `Bearer ${superAdminToken}`)
|
|
.expect(400);
|
|
|
|
expect(response.body.message).toBe('No se pueden eliminar roles del sistema');
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### TC-RBAC-004: Listar permisos agrupados
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **RF** | RF-ROLE-002 |
|
|
| **Tipo** | Integration |
|
|
| **Prioridad** | P0 |
|
|
|
|
**Implementacion Jest:**
|
|
```typescript
|
|
describe('PermissionsController - List', () => {
|
|
it('should return permissions grouped by module', async () => {
|
|
const response = await request(app.getHttpServer())
|
|
.get('/api/v1/permissions')
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.expect(200);
|
|
|
|
expect(response.body.data).toBeInstanceOf(Array);
|
|
expect(response.body.data[0]).toHaveProperty('module');
|
|
expect(response.body.data[0]).toHaveProperty('moduleName');
|
|
expect(response.body.data[0]).toHaveProperty('permissions');
|
|
expect(response.body.data[0].permissions[0]).toHaveProperty('code');
|
|
expect(response.body.data[0].permissions[0]).toHaveProperty('name');
|
|
});
|
|
|
|
it('should filter permissions by search', async () => {
|
|
const response = await request(app.getHttpServer())
|
|
.get('/api/v1/permissions?search=users')
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.expect(200);
|
|
|
|
const allCodes = response.body.data.flatMap(g => g.permissions.map(p => p.code));
|
|
expect(allCodes.every(c => c.includes('users'))).toBe(true);
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### TC-RBAC-005: Asignar roles a usuario
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **RF** | RF-ROLE-003 |
|
|
| **Tipo** | Integration |
|
|
| **Prioridad** | P0 |
|
|
|
|
**Implementacion Jest:**
|
|
```typescript
|
|
describe('User Roles Assignment', () => {
|
|
it('should assign multiple roles to user', async () => {
|
|
const vendedorRole = await createRole('Vendedor');
|
|
const contadorRole = await createRole('Contador');
|
|
|
|
const response = await request(app.getHttpServer())
|
|
.put(`/api/v1/users/${testUser.id}/roles`)
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.send({ roleIds: [vendedorRole.id, contadorRole.id] })
|
|
.expect(200);
|
|
|
|
expect(response.body.roles).toHaveLength(2);
|
|
expect(response.body.roles.map(r => r.name)).toContain('Vendedor');
|
|
expect(response.body.roles.map(r => r.name)).toContain('Contador');
|
|
});
|
|
|
|
it('should calculate effective permissions from multiple roles', async () => {
|
|
const response = await request(app.getHttpServer())
|
|
.get(`/api/v1/users/${testUser.id}/permissions`)
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.expect(200);
|
|
|
|
expect(response.body.roles).toContain('vendedor');
|
|
expect(response.body.all).toBeInstanceOf(Array);
|
|
expect(response.body.all.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### TC-RBAC-006: Proteccion de rol super_admin
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **RF** | RF-ROLE-003 |
|
|
| **Tipo** | Integration |
|
|
| **Prioridad** | P0 |
|
|
|
|
**Implementacion Jest:**
|
|
```typescript
|
|
describe('Super Admin Protection', () => {
|
|
it('should not allow admin to assign super_admin role', async () => {
|
|
const superAdminRole = await roleRepository.findOne({
|
|
where: { slug: 'super_admin', tenantId },
|
|
});
|
|
|
|
const response = await request(app.getHttpServer())
|
|
.put(`/api/v1/users/${testUser.id}/roles`)
|
|
.set('Authorization', `Bearer ${adminToken}`) // admin, no super_admin
|
|
.send({ roleIds: [superAdminRole.id] })
|
|
.expect(403);
|
|
|
|
expect(response.body.message).toBe('Solo Super Admin puede asignar este rol');
|
|
});
|
|
|
|
it('should not allow removing last super_admin', async () => {
|
|
// Solo hay un super_admin en el tenant
|
|
const response = await request(app.getHttpServer())
|
|
.put(`/api/v1/users/${superAdminUser.id}/roles`)
|
|
.set('Authorization', `Bearer ${superAdminToken}`)
|
|
.send({ roleIds: [] }) // Quitar todos los roles
|
|
.expect(400);
|
|
|
|
expect(response.body.message).toBe('Debe existir al menos un Super Admin');
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### TC-RBAC-007: RbacGuard permite acceso con permiso
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **RF** | RF-ROLE-004 |
|
|
| **Tipo** | Integration |
|
|
| **Prioridad** | P0 |
|
|
|
|
**Implementacion Jest:**
|
|
```typescript
|
|
describe('RbacGuard', () => {
|
|
it('should allow access with required permission', async () => {
|
|
// Usuario con permiso users:read
|
|
await assignPermissionToUser(testUser.id, 'users:read');
|
|
|
|
await request(app.getHttpServer())
|
|
.get('/api/v1/users')
|
|
.set('Authorization', `Bearer ${testUserToken}`)
|
|
.expect(200);
|
|
});
|
|
|
|
it('should deny access without required permission', async () => {
|
|
// Usuario sin permiso users:delete
|
|
const response = await request(app.getHttpServer())
|
|
.delete('/api/v1/users/some-id')
|
|
.set('Authorization', `Bearer ${testUserToken}`)
|
|
.expect(403);
|
|
|
|
expect(response.body.message).toBe('No tienes permiso para realizar esta accion');
|
|
// No debe revelar que permiso falta
|
|
expect(response.body.requiredPermission).toBeUndefined();
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### TC-RBAC-008: Wildcard permite acceso
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **RF** | RF-ROLE-004 |
|
|
| **Tipo** | Unit |
|
|
| **Prioridad** | P0 |
|
|
|
|
**Implementacion Jest:**
|
|
```typescript
|
|
describe('Wildcard Permissions', () => {
|
|
it('should expand wildcard to include all module permissions', async () => {
|
|
// Asignar users:* al usuario
|
|
await assignPermissionToUser(testUser.id, 'users:*');
|
|
|
|
const effective = await permissionsService.getEffectivePermissions(testUser.id);
|
|
|
|
expect(effective.direct).toContain('users:*');
|
|
expect(effective.all).toContain('users:read');
|
|
expect(effective.all).toContain('users:create');
|
|
expect(effective.all).toContain('users:delete');
|
|
});
|
|
|
|
it('should allow access via wildcard', async () => {
|
|
await assignPermissionToUser(testUser.id, 'users:*');
|
|
|
|
// Endpoint requiere users:delete
|
|
await request(app.getHttpServer())
|
|
.delete(`/api/v1/users/${anotherUser.id}`)
|
|
.set('Authorization', `Bearer ${testUserToken}`)
|
|
.expect(200);
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### TC-RBAC-009: Super Admin bypass
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **RF** | RF-ROLE-004 |
|
|
| **Tipo** | Integration |
|
|
| **Prioridad** | P0 |
|
|
|
|
**Implementacion Jest:**
|
|
```typescript
|
|
describe('Super Admin Bypass', () => {
|
|
it('should bypass permission check for super_admin', async () => {
|
|
// Endpoint que requiere permiso especial
|
|
await request(app.getHttpServer())
|
|
.delete('/api/v1/tenants/config/dangerous')
|
|
.set('Authorization', `Bearer ${superAdminToken}`)
|
|
.expect(200);
|
|
});
|
|
|
|
it('should not bypass for regular admin', async () => {
|
|
await request(app.getHttpServer())
|
|
.delete('/api/v1/tenants/config/dangerous')
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.expect(403);
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### TC-RBAC-010: Owner puede acceder
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **RF** | RF-ROLE-004 |
|
|
| **Tipo** | Integration |
|
|
| **Prioridad** | P0 |
|
|
|
|
**Implementacion Jest:**
|
|
```typescript
|
|
describe('Owner Access', () => {
|
|
it('should allow owner to update own resource', async () => {
|
|
// Usuario sin permiso users:update
|
|
// pero es owner del recurso (su propio perfil)
|
|
await request(app.getHttpServer())
|
|
.patch(`/api/v1/users/${testUser.id}`)
|
|
.set('Authorization', `Bearer ${testUserToken}`)
|
|
.send({ firstName: 'Nuevo Nombre' })
|
|
.expect(200);
|
|
});
|
|
|
|
it('should deny non-owner without permission', async () => {
|
|
// Usuario sin permiso intentando modificar otro usuario
|
|
await request(app.getHttpServer())
|
|
.patch(`/api/v1/users/${anotherUser.id}`)
|
|
.set('Authorization', `Bearer ${testUserToken}`)
|
|
.send({ firstName: 'Hack' })
|
|
.expect(403);
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### TC-RBAC-011: Cache de permisos
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **RF** | RF-ROLE-004 |
|
|
| **Tipo** | Integration |
|
|
| **Prioridad** | P0 |
|
|
|
|
**Implementacion Jest:**
|
|
```typescript
|
|
describe('Permissions Cache', () => {
|
|
it('should cache permissions', async () => {
|
|
const spy = jest.spyOn(permissionsService, 'calculateEffective');
|
|
|
|
// Primera llamada - calcula
|
|
await permissionsService.getEffectivePermissions(testUser.id);
|
|
expect(spy).toHaveBeenCalledTimes(1);
|
|
|
|
// Segunda llamada - usa cache
|
|
await permissionsService.getEffectivePermissions(testUser.id);
|
|
expect(spy).toHaveBeenCalledTimes(1); // No incrementa
|
|
});
|
|
|
|
it('should invalidate cache on role change', async () => {
|
|
// Obtener permisos (cachea)
|
|
const before = await permissionsService.getEffectivePermissions(testUser.id);
|
|
|
|
// Cambiar roles del usuario
|
|
await rolesService.assignRoles(testUser.id, [newRole.id], adminUser.id);
|
|
|
|
// Cache invalidado, recalcula
|
|
const after = await permissionsService.getEffectivePermissions(testUser.id);
|
|
|
|
expect(after.roles).not.toEqual(before.roles);
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### TC-RBAC-012: AnyPermission (OR)
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **RF** | RF-ROLE-004 |
|
|
| **Tipo** | Integration |
|
|
| **Prioridad** | P1 |
|
|
|
|
**Implementacion Jest:**
|
|
```typescript
|
|
describe('AnyPermission Decorator', () => {
|
|
// Endpoint decorado con @AnyPermission('users:update', 'users:admin')
|
|
it('should allow access with first permission', async () => {
|
|
await assignPermissionToUser(testUser.id, 'users:update');
|
|
|
|
await request(app.getHttpServer())
|
|
.patch('/api/v1/users/special-action')
|
|
.set('Authorization', `Bearer ${testUserToken}`)
|
|
.expect(200);
|
|
});
|
|
|
|
it('should allow access with second permission', async () => {
|
|
await assignPermissionToUser(testUser.id, 'users:admin');
|
|
|
|
await request(app.getHttpServer())
|
|
.patch('/api/v1/users/special-action')
|
|
.set('Authorization', `Bearer ${testUserToken}`)
|
|
.expect(200);
|
|
});
|
|
|
|
it('should deny without any of the permissions', async () => {
|
|
// Usuario solo con users:read
|
|
await assignPermissionToUser(testUser.id, 'users:read');
|
|
|
|
await request(app.getHttpServer())
|
|
.patch('/api/v1/users/special-action')
|
|
.set('Authorization', `Bearer ${testUserToken}`)
|
|
.expect(403);
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### TC-RBAC-013: Multi-tenant isolation
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **RF** | Transversal |
|
|
| **Tipo** | Integration |
|
|
| **Prioridad** | P0 |
|
|
|
|
**Implementacion Jest:**
|
|
```typescript
|
|
describe('Multi-tenant Isolation', () => {
|
|
let tenantA, tenantB;
|
|
let roleA, roleB;
|
|
|
|
beforeAll(async () => {
|
|
tenantA = await createTenant('Tenant A');
|
|
tenantB = await createTenant('Tenant B');
|
|
|
|
roleA = await createRole('Custom Role', tenantA.id);
|
|
roleB = await createRole('Custom Role', tenantB.id);
|
|
});
|
|
|
|
it('should only list roles from own tenant', async () => {
|
|
const response = await request(app.getHttpServer())
|
|
.get('/api/v1/roles')
|
|
.set('Authorization', `Bearer ${tenantAAdminToken}`)
|
|
.expect(200);
|
|
|
|
const roleIds = response.body.data.map(r => r.id);
|
|
expect(roleIds).toContain(roleA.id);
|
|
expect(roleIds).not.toContain(roleB.id);
|
|
});
|
|
|
|
it('should not access role from another tenant', async () => {
|
|
await request(app.getHttpServer())
|
|
.get(`/api/v1/roles/${roleB.id}`)
|
|
.set('Authorization', `Bearer ${tenantAAdminToken}`)
|
|
.expect(404);
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### TC-RBAC-014: Soft delete con reasignacion
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **RF** | RF-ROLE-001 |
|
|
| **Tipo** | Integration |
|
|
| **Prioridad** | P0 |
|
|
|
|
**Implementacion Jest:**
|
|
```typescript
|
|
describe('Role Soft Delete', () => {
|
|
it('should soft delete role and reassign users', async () => {
|
|
const customRole = await createRole('ToDelete');
|
|
const userRole = await roleRepository.findOne({ where: { slug: 'user' } });
|
|
|
|
// Asignar rol a usuarios
|
|
await assignRoleToUsers(customRole.id, [user1.id, user2.id, user3.id]);
|
|
|
|
// Eliminar con reasignacion
|
|
await request(app.getHttpServer())
|
|
.delete(`/api/v1/roles/${customRole.id}?reassignTo=${userRole.id}`)
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.expect(200);
|
|
|
|
// Verificar soft delete
|
|
const deleted = await roleRepository.findOne({
|
|
where: { id: customRole.id },
|
|
withDeleted: true,
|
|
});
|
|
expect(deleted.deletedAt).not.toBeNull();
|
|
|
|
// Verificar reasignacion
|
|
const user1Roles = await userRoleRepository.find({ where: { userId: user1.id } });
|
|
expect(user1Roles.some(ur => ur.roleId === userRole.id)).toBe(true);
|
|
expect(user1Roles.some(ur => ur.roleId === customRole.id)).toBe(false);
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Datos de Prueba
|
|
|
|
### Permisos de Prueba
|
|
|
|
```typescript
|
|
const testPermissions = [
|
|
{ code: 'users:read', name: 'Leer usuarios', module: 'users' },
|
|
{ code: 'users:create', name: 'Crear usuarios', module: 'users' },
|
|
{ code: 'users:update', name: 'Actualizar usuarios', module: 'users' },
|
|
{ code: 'users:delete', name: 'Eliminar usuarios', module: 'users' },
|
|
{ code: 'users:*', name: 'Todos usuarios', module: 'users' },
|
|
{ code: 'roles:read', name: 'Leer roles', module: 'roles' },
|
|
{ code: 'roles:create', name: 'Crear roles', module: 'roles' },
|
|
];
|
|
```
|
|
|
|
### Usuarios de Prueba
|
|
|
|
```typescript
|
|
const testUsers = {
|
|
superAdmin: {
|
|
email: 'super@test.com',
|
|
roles: ['super_admin'],
|
|
},
|
|
admin: {
|
|
email: 'admin@test.com',
|
|
roles: ['admin'],
|
|
},
|
|
regular: {
|
|
email: 'user@test.com',
|
|
roles: ['user'],
|
|
},
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Setup y Teardown
|
|
|
|
```typescript
|
|
beforeAll(async () => {
|
|
// Crear tenant de prueba
|
|
testTenant = await createTestTenant();
|
|
|
|
// Seed permisos
|
|
await seedPermissions();
|
|
|
|
// Crear roles built-in
|
|
await seedBuiltInRoles(testTenant.id);
|
|
|
|
// Crear usuarios de prueba
|
|
superAdminUser = await createUser('super_admin');
|
|
adminUser = await createUser('admin');
|
|
testUser = await createUser('user');
|
|
|
|
// Generar tokens
|
|
superAdminToken = await generateToken(superAdminUser);
|
|
adminToken = await generateToken(adminUser);
|
|
testUserToken = await generateToken(testUser);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await cleanDatabase();
|
|
await closeConnections();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
// Limpiar roles custom y asignaciones entre tests
|
|
await truncateTables([
|
|
'core_rbac.user_roles',
|
|
'core_rbac.role_permissions',
|
|
]);
|
|
await roleRepository.delete({ isBuiltIn: false });
|
|
|
|
// Limpiar cache
|
|
await cacheManager.reset();
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Matriz de Trazabilidad
|
|
|
|
| Test Case | RF | User Story | Criterio |
|
|
|-----------|-----|------------|----------|
|
|
| TC-RBAC-001 | RF-ROLE-001 | US-MGN003-001 | Escenario 1 |
|
|
| TC-RBAC-002 | RF-ROLE-001 | US-MGN003-001 | Escenario 3 |
|
|
| TC-RBAC-003 | RF-ROLE-001 | US-MGN003-001 | Escenario 4 |
|
|
| TC-RBAC-004 | RF-ROLE-002 | US-MGN003-002 | Escenario 1 |
|
|
| TC-RBAC-005 | RF-ROLE-003 | US-MGN003-003 | Escenario 1 |
|
|
| TC-RBAC-006 | RF-ROLE-003 | US-MGN003-003 | Escenarios 4-5 |
|
|
| TC-RBAC-007 | RF-ROLE-004 | US-MGN003-004 | Escenarios 1-2 |
|
|
| TC-RBAC-008 | RF-ROLE-004 | US-MGN003-004 | Escenario 3 |
|
|
| TC-RBAC-009 | RF-ROLE-004 | US-MGN003-004 | Escenario 4 |
|
|
| TC-RBAC-010 | RF-ROLE-004 | US-MGN003-004 | Escenario 6 |
|
|
| TC-RBAC-011 | RF-ROLE-004 | US-MGN003-004 | Escenario 7 |
|
|
| TC-RBAC-012 | RF-ROLE-004 | US-MGN003-004 | Escenario 5 |
|
|
| TC-RBAC-013 | Transversal | - | Multi-tenant |
|
|
| TC-RBAC-014 | RF-ROLE-001 | US-MGN003-001 | Escenario 5 |
|
|
|
|
---
|
|
|
|
## Metricas de Calidad
|
|
|
|
| Metrica | Objetivo |
|
|
|---------|----------|
|
|
| Tests pasando | 100% |
|
|
| Code coverage | > 85% |
|
|
| Bugs criticos | 0 |
|
|
| Bugs mayores | < 2 |
|
|
|
|
---
|
|
|
|
## Historial
|
|
|
|
| Version | Fecha | Autor | Cambios |
|
|
|---------|-------|-------|---------|
|
|
| 1.0 | 2025-12-05 | System | Creacion inicial con 14 test cases |
|