refactor: complete bootstrap of ARNES agent harness framework
- Add complete agent harness structure with 8 roles (leader, triager, architect, implementer, reviewer, security, qa, documenter) - Implement strict workflow with 9 stages and mandatory gates - Add comprehensive verification script and runtime status tracking - Create artifact-based evidence system with contracts and schemas - Add agent policy matrix with permissions and anti-cheat rules - Include test suite (44 tests passing) and CI-ready structure - Add documentation: README, HOWTO, CHECKPOINTS, templates - Configure model routing policies and token-aware task assignment - Add BDD/SDD specification guides and feature templates - Include starter pack for quick project onboarding All verification checks pass. Framework ready for production use.
This commit is contained in:
58
spec/bdd/features/README.md
Normal file
58
spec/bdd/features/README.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Features BDD
|
||||
|
||||
Este directorio contiene los archivos `.feature` organizados por dominio.
|
||||
|
||||
## Estructura
|
||||
|
||||
```
|
||||
features/
|
||||
├── auth/
|
||||
│ ├── login.feature
|
||||
│ └── registration.feature
|
||||
├── dashboard/
|
||||
│ └── dashboard.feature
|
||||
├── common/
|
||||
│ ├── navigation.feature
|
||||
│ └── error-handling.feature
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Tags comunes
|
||||
|
||||
Usar estos tags en todos los features:
|
||||
|
||||
| Tag | Descripción |
|
||||
|-----|-------------|
|
||||
| `@F-XXX` | Link a feature ID del backlog |
|
||||
| `@smoke` | Test crítico |
|
||||
| `@regression` | Regresión |
|
||||
|
||||
## Example
|
||||
|
||||
```gherkin
|
||||
@F-001 @auth @smoke
|
||||
Feature: Inicio de sesión
|
||||
|
||||
Como usuario registrado
|
||||
Quiero iniciar sesión con mis credenciales
|
||||
Para acceder a mi cuenta personal
|
||||
|
||||
@positive
|
||||
Scenario: Login exitoso con credenciales válidas
|
||||
Given un usuario con email "user@example.com" y password "Password123"
|
||||
And el usuario no tiene sesión activa
|
||||
When el usuario ingresa email "user@example.com"
|
||||
And ingresa password "Password123"
|
||||
And presiona el botón "Iniciar sesión"
|
||||
Then el sistema redirige al dashboard
|
||||
And muestra mensaje de bienvenida
|
||||
|
||||
@negative
|
||||
Scenario: Login fallido con password incorrecto
|
||||
Given un usuario con email "user@example.com" y password "Password123"
|
||||
When el usuario ingresa email "user@example.com"
|
||||
And ingresa password "WrongPassword"
|
||||
And presiona el botón "Iniciar sesión"
|
||||
Then el sistema muestra mensaje de error "Credenciales inválidas"
|
||||
And permanece en la página de login
|
||||
```
|
||||
70
spec/bdd/features/auth/login.feature
Normal file
70
spec/bdd/features/auth/login.feature
Normal file
@@ -0,0 +1,70 @@
|
||||
@F-004 @auth @login
|
||||
Feature: User Login
|
||||
|
||||
Background:
|
||||
Given the user "alice@example.com" exists with password "SecurePass123!"
|
||||
|
||||
@positive
|
||||
Scenario: Successful login with valid credentials
|
||||
Given I have valid email "alice@example.com" and password "SecurePass123!"
|
||||
When I attempt to login
|
||||
Then I should receive an access token
|
||||
And the access token should contain user_id claim
|
||||
And the access token should contain email claim
|
||||
And the access token should not be expired
|
||||
|
||||
@positive
|
||||
Scenario: Login returns refresh token
|
||||
Given I have valid credentials for "alice@example.com"
|
||||
When I login successfully
|
||||
Then I should receive a refresh token
|
||||
And the refresh token should be different from access token
|
||||
And the refresh token should have longer expiration
|
||||
|
||||
@positive
|
||||
Scenario: Login email is case-insensitive
|
||||
Given a user exists with email "bob@test.com" and password "TestPass99!"
|
||||
When I login with email "BOB@TEST.COM" and password "TestPass99!"
|
||||
Then login should be successful
|
||||
|
||||
@negative
|
||||
Scenario: Login with wrong password
|
||||
Given I have email "alice@example.com" and password "WrongPassword123!"
|
||||
When I attempt to login
|
||||
Then I should receive error "Credenciales inválidas"
|
||||
And I should not receive any token
|
||||
|
||||
@negative
|
||||
Scenario: Login with nonexistent user
|
||||
Given I have email "nonexistent@test.com" and password "AnyPass123!"
|
||||
When I attempt to login
|
||||
Then I should receive error "Credenciales inválidas"
|
||||
And I should not receive any token
|
||||
|
||||
@negative
|
||||
Scenario: Login with empty password
|
||||
Given I have email "alice@example.com" and empty password
|
||||
When I attempt to login
|
||||
Then I should receive validation error
|
||||
And I should not receive any token
|
||||
|
||||
@negative
|
||||
Scenario: Login with invalid email format
|
||||
Given I have email "not-an-email" and password "ValidPass123!"
|
||||
When I attempt to login
|
||||
Then I should receive validation error
|
||||
And I should not receive any token
|
||||
|
||||
@security @rate-limit
|
||||
Scenario: Login blocked after 10 failed attempts
|
||||
Given I have email "alice@example.com" and password "WrongPassword!"
|
||||
When I attempt to login 10 times with wrong password
|
||||
Then account should be temporarily locked
|
||||
And next login attempt should return error "Cuenta bloqueada"
|
||||
|
||||
@smoke
|
||||
Scenario: Login endpoint responds with JSON
|
||||
Given I have valid credentials for "alice@example.com"
|
||||
When I send a POST request to "/api/v1/auth/login"
|
||||
Then response should be JSON format
|
||||
And response should have correct content-type header
|
||||
58
spec/bdd/features/auth/logout.feature
Normal file
58
spec/bdd/features/auth/logout.feature
Normal file
@@ -0,0 +1,58 @@
|
||||
@F-004 @auth @logout
|
||||
Feature: User Logout
|
||||
|
||||
Background:
|
||||
Given the user "alice@example.com" exists with password "SecurePass123!"
|
||||
And I am authenticated as "alice@example.com"
|
||||
|
||||
@positive
|
||||
Scenario: Successful logout invalidates current session
|
||||
Given my current access token is valid
|
||||
When I logout
|
||||
Then I should receive confirmation
|
||||
And my session should be marked as revoked
|
||||
And my access token should no longer be valid
|
||||
|
||||
@positive
|
||||
Scenario: Logout with refresh token also invalidates access
|
||||
Given I have a valid refresh token
|
||||
When I logout
|
||||
Then both access and refresh tokens should be invalid
|
||||
And I should not be able to get new access token with refresh
|
||||
|
||||
@positive
|
||||
Scenario: Logout all sessions for user
|
||||
Given I am logged in from device "desktop"
|
||||
And I am logged in from device "mobile"
|
||||
When I logout from all devices
|
||||
Then all my sessions should be invalidated
|
||||
And I should not be able to use any previous token
|
||||
|
||||
@negative
|
||||
Scenario: Using token after logout returns unauthorized
|
||||
Given I previously logged in successfully
|
||||
And I have logged out
|
||||
When I try to use my old access token
|
||||
Then I should receive 401 Unauthorized
|
||||
And I should not have access to protected resources
|
||||
|
||||
@negative
|
||||
Scenario: Logout with invalid token does nothing
|
||||
Given I have an invalid/expired token
|
||||
When I attempt to logout
|
||||
Then logout should not fail
|
||||
But no session should be affected
|
||||
|
||||
@security
|
||||
Scenario: Concurrent logout requests are handled correctly
|
||||
Given my session is valid
|
||||
When I send multiple logout requests simultaneously
|
||||
Then only one logout operation should occur
|
||||
And token should be invalidated only once
|
||||
|
||||
@smoke
|
||||
Scenario: Logout endpoint returns 200 on success
|
||||
Given I am authenticated as "alice@example.com"
|
||||
When I send POST request to "/api/v1/auth/logout"
|
||||
Then response should be 200 OK
|
||||
And response should indicate success
|
||||
36
spec/bdd/features/common/README.md
Normal file
36
spec/bdd/features/common/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Common Features
|
||||
|
||||
Features que se reutilizan en múltiples dominios.
|
||||
|
||||
## Navigation
|
||||
|
||||
```gherkin
|
||||
@common @navigation
|
||||
Feature: Navegación entre páginas
|
||||
|
||||
Scenario: Navegar a través del menú
|
||||
Given el usuario está en la página principal
|
||||
When hace clic en el elemento de menú "Dashboard"
|
||||
Then la URL cambia a "/dashboard"
|
||||
And el título de la página muestra "Dashboard"
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```gherkin
|
||||
@common @error-handling
|
||||
Feature: Manejo de errores
|
||||
|
||||
Scenario: Mostrar error de red
|
||||
Given la conexión a internet está disponible
|
||||
And el servidor no responde
|
||||
When el usuario realiza una acción que requiere red
|
||||
Then el sistema muestra toast "Error de conexión"
|
||||
And ofrece opción de reintentar
|
||||
|
||||
Scenario: Timeout de solicitud
|
||||
Given el usuario tiene sesión activa
|
||||
When realiza una solicitud que excede 30 segundos
|
||||
Then el sistema muestra indicador de carga
|
||||
And después de timeout muestra error "Solicitud expirada"
|
||||
```
|
||||
171
spec/bdd/features/password/change-password.feature
Normal file
171
spec/bdd/features/password/change-password.feature
Normal file
@@ -0,0 +1,171 @@
|
||||
@F-003 @password
|
||||
Feature: Cambio de Contraseña
|
||||
|
||||
Como usuario autenticado
|
||||
Quiero cambiar mi contraseña
|
||||
Para mantener mi cuenta segura con credenciales actualizadas
|
||||
|
||||
# ====================
|
||||
# HAPPY PATH
|
||||
# ====================
|
||||
|
||||
@smoke @positive
|
||||
Scenario: Cambiar contraseña exitosamente
|
||||
Given un usuario autenticado con email "user@example.com"
|
||||
And su contraseña actual es "OldPass123!"
|
||||
When el usuario solicita cambiar contraseña
|
||||
And ingresa contraseña actual "OldPass123!"
|
||||
And ingresa nueva contraseña "NewPass456@"
|
||||
And confirma nueva contraseña "NewPass456@"
|
||||
Then el sistema valida la contraseña actual correctamente
|
||||
And guarda la nueva contraseña hasheada
|
||||
And invalida todas las sesiones existentes
|
||||
And muestra mensaje de confirmación "Contraseña actualizada exitosamente"
|
||||
|
||||
@positive
|
||||
Scenario: Contraseña con todos los caracteres especiales permitidos
|
||||
Given un usuario autenticado
|
||||
When cambia contraseña a "!@#$%^&*()_+-=[]{}|;':\",./<>?abc123ABC"
|
||||
Then el sistema acepta la contraseña
|
||||
And la guarda correctamente
|
||||
|
||||
# ====================
|
||||
# PASSWORD VALIDATION
|
||||
# ====================
|
||||
|
||||
@negative
|
||||
Scenario: Nueva contraseña muy corta (menos de 8 caracteres)
|
||||
Given un usuario autenticado
|
||||
When intenta cambiar contraseña a "Ab1!"
|
||||
Then el sistema muestra error "La contraseña debe tener al menos 8 caracteres"
|
||||
And la contraseña no es cambiada
|
||||
|
||||
@negative
|
||||
Scenario: Nueva contraseña muy larga (más de 128 caracteres)
|
||||
Given un usuario autenticado
|
||||
When intenta cambiar contraseña a "A" repetido 129 veces más "a1!"
|
||||
Then el sistema muestra error "La contraseña debe tener máximo 128 caracteres"
|
||||
And la contraseña no es cambiada
|
||||
|
||||
@negative
|
||||
Scenario: Nueva contraseña sin mayúscula
|
||||
Given un usuario autenticado
|
||||
When intenta cambiar contraseña a "password123!"
|
||||
Then el sistema muestra error "La contraseña debe contener al menos una mayúscula"
|
||||
And la contraseña no es cambiada
|
||||
|
||||
@negative
|
||||
Scenario: Nueva contraseña sin minúscula
|
||||
Given un usuario autenticado
|
||||
When intenta cambiar contraseña a "PASSWORD123!"
|
||||
Then el sistema muestra error "La contraseña debe contener al menos una minúscula"
|
||||
And la contraseña no es cambiada
|
||||
|
||||
@negative
|
||||
Scenario: Nueva contraseña sin número
|
||||
Given un usuario autenticado
|
||||
When intenta cambiar contraseña a "PasswordABC!"
|
||||
Then el sistema muestra error "La contraseña debe contener al menos un número"
|
||||
And la contraseña no es cambiada
|
||||
|
||||
@negative
|
||||
Scenario: Nueva contraseña sin carácter especial
|
||||
Given un usuario autenticado
|
||||
When intenta cambiar contraseña a "Password123"
|
||||
Then el sistema muestra error "La contraseña debe contener al menos un carácter especial (!@#$%^&*...)"
|
||||
And la contraseña no es cambiada
|
||||
|
||||
# ====================
|
||||
# CURRENT PASSWORD
|
||||
# ====================
|
||||
|
||||
@negative
|
||||
Scenario: Contraseña actual incorrecta
|
||||
Given un usuario autenticado con contraseña actual "CorrectPass123!"
|
||||
When intenta cambiar contraseña con actual "WrongPass456!"
|
||||
And nueva contraseña "NewPass789@"
|
||||
Then el sistema muestra error "La contraseña actual es incorrecta"
|
||||
And la contraseña no es cambiada
|
||||
And no se invalidan sesiones
|
||||
|
||||
@negative
|
||||
Scenario: Contraseña actual vacía
|
||||
Given un usuario autenticado
|
||||
When intenta cambiar contraseña con actual ""
|
||||
And nueva contraseña "NewPass123@"
|
||||
Then el sistema muestra error "La contraseña actual es requerida"
|
||||
And la contraseña no es cambiada
|
||||
|
||||
# ====================
|
||||
# PASSWORD MISMATCH
|
||||
# ====================
|
||||
|
||||
@negative
|
||||
Scenario: Nueva contraseña y confirmación no coinciden
|
||||
Given un usuario autenticado
|
||||
When ingresa contraseña actual correcta
|
||||
And ingresa nueva contraseña "NewPass123@"
|
||||
But confirma con "DifferentPass456!"
|
||||
Then el sistema muestra error "Las contraseñas no coinciden"
|
||||
And la contraseña no es cambiada
|
||||
|
||||
# ====================
|
||||
# REUSE DETECTION
|
||||
# ====================
|
||||
|
||||
@negative @security
|
||||
Scenario: Reutilizar contraseña anterior
|
||||
Given un usuario autenticado con contraseña actual "MyPass123!"
|
||||
And historial de contraseñas incluye "MyPass123!"
|
||||
When intenta cambiar contraseña a "MyPass123!"
|
||||
Then el sistema muestra error "La nueva contraseña no puede ser igual a la anterior"
|
||||
And la contraseña no es cambiada
|
||||
|
||||
# ====================
|
||||
# AUTHORIZATION
|
||||
# ====================
|
||||
|
||||
@negative @security
|
||||
Scenario: Usuario no autenticado intenta cambiar contraseña
|
||||
Given un usuario no autenticado
|
||||
When intenta cambiar contraseña
|
||||
Then el sistema retorna error 401 "No autorizado"
|
||||
And la contraseña no es cambiada
|
||||
|
||||
@negative @security
|
||||
Scenario: Token expirado al cambiar contraseña
|
||||
Given un usuario con sesión expirada
|
||||
When intenta cambiar contraseña
|
||||
Then el sistema retorna error 401 "Sesión expirada"
|
||||
And la contraseña no es cambiada
|
||||
|
||||
@negative @security
|
||||
Scenario: Intentar cambiar contraseña de otro usuario
|
||||
Given un usuario autenticado con ID "user-123"
|
||||
When intenta cambiar contraseña del usuario "user-456"
|
||||
Then el sistema retorna error 403 "No tienes permiso para modificar esta cuenta"
|
||||
And la contraseña no es cambiada
|
||||
|
||||
# ====================
|
||||
# RATE LIMITING
|
||||
# ====================
|
||||
|
||||
@negative @security
|
||||
Scenario: Superar límite de intentos (rate limit)
|
||||
Given un usuario autenticado
|
||||
And ya realizó 5 intentos fallidos en la última hora
|
||||
When intenta cambiar contraseña una vez más
|
||||
Then el sistema retorna error 429 "Demasiados intentos. Intenta de nuevo en 1 hora"
|
||||
And todas las solicitudes son bloqueadas hasta que pase el tiempo
|
||||
|
||||
# ====================
|
||||
# SUCCESSFUL REAUTHENTICATION
|
||||
# ====================
|
||||
|
||||
@positive
|
||||
Scenario: Cambio de contraseña seguido de login exitoso
|
||||
Given un usuario con contraseña "OldPass123!"
|
||||
When cambia su contraseña a "NewPass456@"
|
||||
And luego intenta iniciar sesión con "NewPass456@"
|
||||
Then el login es exitoso
|
||||
And el usuario accede a su cuenta
|
||||
159
spec/bdd/features/profile/user-profile.feature
Normal file
159
spec/bdd/features/profile/user-profile.feature
Normal file
@@ -0,0 +1,159 @@
|
||||
@F-002 @profile
|
||||
Feature: Gestión de Perfil de Usuario
|
||||
|
||||
Como usuario autenticado
|
||||
Quiero gestionar mi perfil
|
||||
Para mantener mis datos personales actualizados y personalizar mi experiencia
|
||||
|
||||
# ====================
|
||||
# VIEW PROFILE
|
||||
# ====================
|
||||
|
||||
@smoke @positive
|
||||
Scenario: Ver perfil de usuario exitosamente
|
||||
Given un usuario autenticado con ID "user-123" y nombre "Juan Pérez"
|
||||
And el usuario tiene avatar "https://cdn.example.com/avatar-123.jpg"
|
||||
And el idioma configurado es "es"
|
||||
When el usuario solicita ver su perfil
|
||||
Then el sistema retorna los datos completos del perfil
|
||||
And incluye id "user-123", nombre "Juan Pérez"
|
||||
And incluye avatar_url y language "es"
|
||||
|
||||
@negative
|
||||
Scenario: Ver perfil sin autenticación
|
||||
Given un usuario no autenticado
|
||||
When el usuario solicita ver su perfil
|
||||
Then el sistema retorna error 401 "No autorizado"
|
||||
And no retorna datos del perfil
|
||||
|
||||
@negative
|
||||
Scenario: Ver perfil de usuario inexistente
|
||||
Given un usuario autenticado
|
||||
When solicita ver perfil de ID "nonexistent-user"
|
||||
Then el sistema retorna error 404 "Usuario no encontrado"
|
||||
|
||||
# ====================
|
||||
# UPDATE NAME
|
||||
# ====================
|
||||
|
||||
@smoke @positive
|
||||
Scenario: Editar nombre del perfil exitosamente
|
||||
Given un usuario autenticado con ID "user-123"
|
||||
And el perfil tiene nombre "Juan"
|
||||
When el usuario actualiza su nombre a "Pedro"
|
||||
Then el perfil muestra nombre "Pedro"
|
||||
And la fecha de updated_at se actualiza
|
||||
|
||||
@positive
|
||||
Scenario: Editar nombre con caracteres unicode válidos
|
||||
Given un usuario autenticado
|
||||
When cambia su nombre a "José García"
|
||||
Then el sistema acepta el cambio
|
||||
And el nombre se guarda como "José García"
|
||||
|
||||
@negative
|
||||
Scenario: Editar nombre con caracteres inválidos
|
||||
Given un usuario autenticado
|
||||
When intenta cambiar nombre a "Juan@123!"
|
||||
Then el sistema muestra error de validación "Nombre inválido: solo letras y espacios"
|
||||
And el nombre permanece sin cambios
|
||||
|
||||
@negative
|
||||
Scenario: Editar nombre con menos de 2 caracteres
|
||||
Given un usuario autenticado
|
||||
When intenta cambiar nombre a "J"
|
||||
Then el sistema muestra error "Nombre debe tener al menos 2 caracteres"
|
||||
|
||||
@negative
|
||||
Scenario: Editar nombre con más de 50 caracteres
|
||||
Given un usuario autenticado
|
||||
When intenta cambiar nombre a "A" repetido 51 veces
|
||||
Then el sistema muestra error "Nombre debe tener máximo 50 caracteres"
|
||||
|
||||
# ====================
|
||||
# UPDATE AVATAR
|
||||
# ====================
|
||||
|
||||
@smoke @positive
|
||||
Scenario: Cambiar avatar exitosamente
|
||||
Given un usuario autenticado con avatar actual "https://cdn.example.com/old.jpg"
|
||||
When el usuario sube un nuevo avatar "https://cdn.example.com/new.jpg"
|
||||
Then el perfil muestra avatar_url "https://cdn.example.com/new.jpg"
|
||||
|
||||
@negative
|
||||
Scenario: Cambiar avatar con URL inválida
|
||||
Given un usuario autenticado
|
||||
When intenta cambiar avatar a "not-a-valid-url"
|
||||
Then el sistema muestra error "URL de avatar inválida"
|
||||
And el avatar permanece sin cambios
|
||||
|
||||
@negative
|
||||
Scenario: Cambiar avatar con URL de protocolo no permitido
|
||||
Given un usuario autenticado
|
||||
When intenta cambiar avatar a "ftp://malicious.com/file.jpg"
|
||||
Then el sistema muestra error "Solo se permiten URLs http o https"
|
||||
And el avatar permanece sin cambios
|
||||
|
||||
# ====================
|
||||
# UPDATE LANGUAGE
|
||||
# ====================
|
||||
|
||||
@smoke @positive
|
||||
Scenario: Cambiar idioma a español exitosamente
|
||||
Given un usuario autenticado con idioma "en"
|
||||
When el usuario cambia idioma a "es"
|
||||
Then el idioma se guarda como "es"
|
||||
And el sistema confirma el cambio
|
||||
|
||||
@positive
|
||||
Scenario: Cambiar idioma a francés
|
||||
Given un usuario autenticado
|
||||
When cambia idioma a "fr"
|
||||
Then el sistema acepta "fr" como idioma válido
|
||||
|
||||
@positive
|
||||
Scenario: Cambiar idioma a alemán
|
||||
Given un usuario autenticado
|
||||
When cambia idioma a "de"
|
||||
Then el sistema acepta "de" como idioma válido
|
||||
|
||||
@negative
|
||||
Scenario: Cambiar idioma a idioma no soportado
|
||||
Given un usuario autenticado
|
||||
When intenta cambiar idioma a "zh"
|
||||
Then el sistema muestra error "Idioma no soportado"
|
||||
And el idioma permanece sin cambios
|
||||
|
||||
# ====================
|
||||
# PARTIAL UPDATE
|
||||
# ====================
|
||||
|
||||
@positive
|
||||
Scenario: Actualizar solo nombre sin cambiar avatar
|
||||
Given un usuario autenticado con nombre "Juan" y avatar "https://cdn.com/img.jpg"
|
||||
When el usuario solo actualiza nombre a "Pedro"
|
||||
Then el nombre cambia a "Pedro"
|
||||
And el avatar_url permanece "https://cdn.com/img.jpg"
|
||||
|
||||
@positive
|
||||
Scenario: Actualizar múltiples campos en una petición
|
||||
Given un usuario autenticado
|
||||
When envía actualización con nombre "María", avatar "https://cdn.com/maria.jpg", idioma "es"
|
||||
Then todos los campos se actualizan correctamente
|
||||
And el perfil refleja todos los cambios
|
||||
|
||||
# ====================
|
||||
# AUTHORIZATION
|
||||
# ====================
|
||||
|
||||
@negative @security
|
||||
Scenario: Usuario intenta editar perfil de otro usuario
|
||||
Given un usuario autenticado con ID "user-123"
|
||||
When intenta actualizar perfil de usuario "user-456"
|
||||
Then el sistema retorna error 403 "No tienes permiso para editar este perfil"
|
||||
|
||||
@negative @security
|
||||
Scenario: Token expirado al editar perfil
|
||||
Given un usuario con token expirado
|
||||
When intenta actualizar su perfil
|
||||
Then el sistema retorna error 401 "Sesión expirada"
|
||||
Reference in New Issue
Block a user