# Smoke Test — UDT-002: Logout + Refresh Token **Branch**: feature/UDT-002 **Fecha**: 2026-04-14 **Prerequisito**: backend corriendo en `http://localhost:5212`, BD `SIGCM2` con migración V002 aplicada. --- ## Escenario 1 — Login y persistencia de tokens - [ ] Abrir la app en `http://localhost:5173` - [ ] Ingresar con credenciales válidas (admin / password) - [ ] Verificar que el login redirige al home - [ ] Abrir DevTools → Application → Local Storage → `auth-storage` - [ ] Confirmar que el objeto contiene: `accessToken`, `refreshToken`, `expiresAt`, `user` - [ ] Verificar que `expiresAt` es aproximadamente `Date.now() + 3600000` (1 hora) --- ## Escenario 2 — Refresh transparente en 401 **Opción A (esperar expiración natural — requiere token con TTL corto):** - [ ] Modificar `Jwt:AccessTokenMinutes` a `1` en `appsettings.Development.json` y reiniciar el backend - [ ] Hacer login - [ ] Esperar 1 minuto para que el access token expire - [ ] Realizar cualquier request autenticado (ej: navegar a una sección que llame a la API) - [ ] Verificar que el request se completa sin error visible para el usuario - [ ] Verificar en DevTools → Network que hubo una llamada a `POST /api/v1/auth/refresh` seguida del request original reenviado con un nuevo Bearer **Opción B (manipulación manual del token):** - [ ] Después del login, abrir DevTools → Application → Local Storage → `auth-storage` - [ ] Editar el JSON y reemplazar `accessToken` con un valor inválido (ej: `"expired"`) - [ ] Realizar cualquier request autenticado - [ ] El interceptor de axiosClient recibe 401, llama a `/refresh` con el `refreshToken` real - [ ] El request original se reintenta automáticamente con el nuevo `accessToken` - [ ] El usuario no ve ningún error --- ## Escenario 3 — Refresh de 3 requests paralelos (singleton promise) - [ ] Con el access token vencido (opción B del escenario 2) - [ ] Abrir una página que dispare múltiples llamadas API simultáneas - [ ] Verificar en DevTools → Network que hay exactamente **1** llamada a `POST /api/v1/auth/refresh` - [ ] Verificar que todos los requests subsiguientes retornan con éxito --- ## Escenario 4 — Logout - [ ] Con sesión activa, hacer click en el botón de logout - [ ] Verificar que redirige a `/login` - [ ] Verificar en DevTools → Network que se llamó a `POST /api/v1/auth/logout` - [ ] Verificar en Local Storage que `auth-storage` tiene `user: null`, `accessToken: null`, `refreshToken: null` - [ ] Intentar navegar a una ruta protegida — debería redirigir a login --- ## Escenario 5 — Reuso de refresh token después del logout (reuse detection) - [ ] Hacer login y copiar el valor de `refreshToken` del Local Storage - [ ] Hacer logout - [ ] Intentar llamar manualmente al endpoint de refresh con el token anterior: ```bash curl -X POST http://localhost:5212/api/v1/auth/refresh \ -H "Content-Type: application/json" \ -d '{"accessToken": "", "refreshToken": ""}' ``` - [ ] Verificar que el backend responde `401` con `{ "error": "invalid_token" }` - [ ] Verificar en la BD que todos los tokens de la familia fueron revocados: ```sql SELECT * FROM dbo.RefreshToken WHERE RevokedAt IS NOT NULL ORDER BY Id DESC; ``` --- ## Escenario 6 — Refresh token expirado (7 días) - [ ] Modificar `ExpiresAt` de un token en la BD `SIGCM2_Test` a una fecha pasada - [ ] Intentar refresh con ese token — debería responder `401` - [ ] Verificar que el frontend redirige a `/login` y limpia el Local Storage --- ## Escenario 7 — Refresh con access token de otro usuario (mismatch) - [ ] Crear dos usuarios en la BD (o usar admin + otro) - [ ] Hacer login con usuario A, guardar el `accessToken` - [ ] Hacer login con usuario B, guardar el `refreshToken` - [ ] Intentar refresh con accessToken de A + refreshToken de B: ```bash curl -X POST http://localhost:5212/api/v1/auth/refresh \ -H "Content-Type: application/json" \ -d '{"accessToken": "", "refreshToken": ""}' ``` - [ ] Verificar que el backend responde `401` --- ## Notas de verificación | Check | Comando | |-------|---------| | Tokens en BD | `SELECT Id, UsuarioId, FamilyId, IssuedAt, ExpiresAt, RevokedAt FROM dbo.RefreshToken ORDER BY Id DESC` | | Familias revocadas | `SELECT FamilyId, COUNT(*) as Total, SUM(CASE WHEN RevokedAt IS NOT NULL THEN 1 ELSE 0 END) as Revoked FROM dbo.RefreshToken GROUP BY FamilyId` | | Usuario activo | `SELECT Id, Username, Activo FROM dbo.Usuario` |