From aa9bc585fd28c79976df3c3fad23a9f9b7a99770 Mon Sep 17 00:00:00 2001 From: Laucha1312 Date: Thu, 4 Jun 2026 15:22:45 -0300 Subject: [PATCH] 4 --- tests/Feature/AdminTest.php | 79 ++++++++++++++++ tests/Feature/AuthTest.php | 115 +++++++++++++++++++++++ tests/Feature/CleanupTest.php | 45 +++++++++ tests/Feature/EventoQRTest.php | 129 ++++++++++++++++++++++++++ tests/Feature/ExampleTest.php | 19 ++++ tests/Feature/JugadorCreationTest.php | 80 ++++++++++++++++ tests/Feature/PaseTest.php | 64 +++++++++++++ tests/Feature/SoftDeleteTest.php | 52 +++++++++++ tests/Feature/TFICorrectionsTest.php | 108 +++++++++++++++++++++ tests/GUIA_TESTS.md | 65 +++++++++++++ tests/TestCase.php | 10 ++ tests/Unit/ExampleTest.php | 16 ++++ 12 files changed, 782 insertions(+) create mode 100644 tests/Feature/AdminTest.php create mode 100644 tests/Feature/AuthTest.php create mode 100644 tests/Feature/CleanupTest.php create mode 100644 tests/Feature/EventoQRTest.php create mode 100644 tests/Feature/ExampleTest.php create mode 100644 tests/Feature/JugadorCreationTest.php create mode 100644 tests/Feature/PaseTest.php create mode 100644 tests/Feature/SoftDeleteTest.php create mode 100644 tests/Feature/TFICorrectionsTest.php create mode 100644 tests/GUIA_TESTS.md create mode 100644 tests/TestCase.php create mode 100644 tests/Unit/ExampleTest.php diff --git a/tests/Feature/AdminTest.php b/tests/Feature/AdminTest.php new file mode 100644 index 0000000..63e62f9 --- /dev/null +++ b/tests/Feature/AdminTest.php @@ -0,0 +1,79 @@ +get('/admin'); + + $response->assertStatus(403); + } + + public function test_player_cannot_access_admin_dashboard() + { + // Simular que un jugador está logueado + $this->withSession(['user_logged_in' => true, 'user_tipo' => 'jugador']); + + $response = $this->get('/admin'); + + // El middleware/controlador debería redirigirlo por no ser admin + $response->assertStatus(403); + } + + public function test_superadmin_can_access_admin_dashboard() + { + AdminUser::create([ + 'username' => 'supertest', + 'password' => Hash::make('123456'), + 'role' => 1 + ]); + + $this->withSession([ + 'admin_logged_in' => true, + 'admin_role' => 1, + 'admin_username' => 'supertest' + ]); + + $response = $this->get('/admin'); + + $response->assertStatus(200); + $response->assertSee('Resumen General'); + } + + public function test_superadmin_can_create_a_club() + { + Storage::fake('public'); + + $this->withSession([ + 'admin_logged_in' => true, + 'admin_role' => 1, + 'admin_username' => 'supertest' + ]); + + // Evitamos enviar una imagen real para no depender del File System + $response = $this->post('/admin/clubes', [ + 'id_club' => 999, + 'nombre' => 'Club Atlético Test', + 'localidad' => 'Testville', + 'direccion' => 'Calle Falsa 123', + 'activo' => 1 + ]); + + $response->assertRedirect(route('admin.clubes.index')); + $this->assertDatabaseHas('clubes', [ + 'nombre' => 'Club Atlético Test' + ]); + } +} diff --git a/tests/Feature/AuthTest.php b/tests/Feature/AuthTest.php new file mode 100644 index 0000000..c1d5862 --- /dev/null +++ b/tests/Feature/AuthTest.php @@ -0,0 +1,115 @@ + 'superadmin', + 'password' => Hash::make('password123'), + 'role' => 1 + ]); + + $response = $this->post('/login', [ + 'tipo' => 'admin', + 'username' => 'superadmin', + 'password' => 'password123', + 'cf-turnstile-response' => self::TURNSTILE_BYPASS + ]); + + $response->assertRedirect('/'); + $response->assertSessionHas('admin_logged_in', true); + $response->assertSessionHas('admin_username', 'superadmin'); + $response->assertSessionHas('admin_role', 1); + } + + public function test_admin_login_failure() + { + AdminUser::create([ + 'username' => 'superadmin', + 'password' => Hash::make('password123'), + 'role' => 1 + ]); + + $response = $this->post('/login', [ + 'tipo' => 'admin', + 'username' => 'superadmin', + 'password' => 'wrongpassword', + 'cf-turnstile-response' => self::TURNSTILE_BYPASS + ]); + + $response->assertSessionHas('login_error', 'Usuario o contraseña incorrectos'); + } + + public function test_jugador_login_success() + { + Jugador::create([ + 'id_jugador' => 'TESTJ01', + 'nombre' => 'Emanuel', + 'apellido' => 'Ginobili', + 'documento' => '25123456', + 'fecha_nacimiento' => '1977-07-28', + 'activo' => true, + 'password' => Hash::make('secreta123') + ]); + + $response = $this->post('/login', [ + 'tipo' => 'player', + 'dni' => '25123456', + 'password' => 'secreta123', + 'cf-turnstile-response' => self::TURNSTILE_BYPASS + ]); + + $response->assertRedirect('/'); + $response->assertSessionHas('user_logged_in', true); + $response->assertSessionHas('user_tipo', 'jugador'); + $response->assertSessionHas('user_documento', '25123456'); + } + + public function test_aficionado_login_success() + { + Aficionado::create([ + 'nombre' => 'Juan', + 'apellido' => 'Pérez', + 'dni' => '12345678', + 'email' => 'juan@example.com', + 'password' => Hash::make('fan123') + ]); + + $response = $this->post('/login', [ + 'tipo' => 'player', + 'dni' => '12345678', + 'password' => 'fan123', + 'cf-turnstile-response' => self::TURNSTILE_BYPASS + ]); + + $response->assertRedirect('/'); + $response->assertSessionHas('user_logged_in', true); + $response->assertSessionHas('user_tipo', 'aficionado'); + } + + public function test_logout_clears_session() + { + // Simulamos admin logged in + $this->withSession(['admin_logged_in' => true, 'admin_username' => 'testadmin']); + + $response = $this->post('/logout'); + + $response->assertSessionMissing('admin_logged_in'); + $response->assertSessionMissing('admin_username'); + $response->assertRedirectContains('logout_msg='); + } +} diff --git a/tests/Feature/CleanupTest.php b/tests/Feature/CleanupTest.php new file mode 100644 index 0000000..9f252b6 --- /dev/null +++ b/tests/Feature/CleanupTest.php @@ -0,0 +1,45 @@ + $idEvento, + 'nombre_evento' => 'Old Event', + 'fecha_evento' => now()->subDays(40)->format('Y-m-d'), + 'hora_inicio' => '10:00', + 'hora_fin' => '12:00' + ]); + + // 2. Create a QR for this event + QrCode::create([ + 'id_qr' => 'qr_old_1', + 'id_evento' => $idEvento, + 'tipo_qr' => 'publico' + ]); + + // 3. Ensure they exist + $this->assertDatabaseHas('eventos', ['id_evento' => $idEvento]); + $this->assertDatabaseHas('qr_codes', ['id_qr' => 'qr_old_1']); + + // 4. Run the cleanup command + Artisan::call('app:cleanup-old-events'); + + // 5. Verify QRs are gone but event remains + $this->assertDatabaseMissing('qr_codes', ['id_qr' => 'qr_old_1']); + $this->assertDatabaseHas('eventos', ['id_evento' => $idEvento]); + } +} diff --git a/tests/Feature/EventoQRTest.php b/tests/Feature/EventoQRTest.php new file mode 100644 index 0000000..e22913b --- /dev/null +++ b/tests/Feature/EventoQRTest.php @@ -0,0 +1,129 @@ + 'Club Test']); + $idJugador = 'JUG_' . substr(uniqid(), 0, 15); + $jugador = \App\Models\Jugador::create([ + 'id_jugador' => $idJugador, + 'nombre' => 'Carlos', + 'apellido' => 'Test', + 'documento' => '12345678', + 'email' => 'carlos@test.com', + 'password' => bcrypt('password123'), + 'id_club_actual' => $club->id_club, + 'activo' => true + ]); + + // 2. Crear un equipo del club + $idEquipo = (int) (rand(1000, 9999)); + $equipo = \App\Models\Equipo::create([ + 'id_club' => $club->id_club, + 'categoria' => 'Mayores', + 'division' => 'A' + ]); + + // Vincular jugador al equipo + \Illuminate\Support\Facades\DB::table('jugador_equipo')->insert([ + 'id_jugador' => $jugador->id_jugador, + 'id_equipo' => $equipo->id_equipo + ]); + + // 3. Crear un evento donde juegue ese equipo + $evento = Evento::create([ + 'id_evento' => 'EVE_' . substr(uniqid(), 0, 15), + 'nombre_evento' => 'Partido Amistoso', + 'fecha_evento' => now()->addDays(2)->format('Y-m-d'), + 'hora_inicio' => '20:00', + 'hora_fin' => '22:00', + 'id_equipo_local' => $equipo->id_equipo, + 'id_equipo_visitante' => null, + 'limite_qr_jugador' => 3 + ]); + + // 4. Simular la sesión iniciada como jugador + $this->withSession([ + 'user_logged_in' => true, + 'user_tipo' => 'jugador', + 'user_id' => $jugador->id_jugador, + 'user_name' => 'Carlos Test' + ]); + + // 5. Hacer el request POST + $response = $this->post(route('panel.solicitar.qr'), [ + 'id_evento' => $evento->id_evento + ]); + + // 6. Verificaciones + $response->assertRedirect(route('panel.mis.qrs', ['evento' => $evento->id_evento])); + $response->assertSessionHas('panel_msg'); + + // Verificar que en la tabla 'qr_codes' existan los QRs + $this->assertEquals(3, QrCode::where('id_evento', $evento->id_evento) + ->where('id_jugador', $jugador->id_jugador) + ->count()); + } + + public function test_jugador_no_puede_solicitar_dos_veces_qrs_para_mismo_evento() + { + $club = \App\Models\Club::create(['nombre' => 'Club Test']); + $idJugador = 'JUG_' . substr(uniqid(), 0, 15); + $jugador = \App\Models\Jugador::create([ + 'id_jugador' => $idJugador, + 'nombre' => 'Carlos', + 'apellido' => 'Test', + 'documento' => '12345678', + 'email' => 'carlos@test.com', + 'password' => bcrypt('password123'), + 'id_club_actual' => $club->id_club, + 'activo' => true + ]); + + $evento = Evento::create([ + 'id_evento' => 'EVE_' . substr(uniqid(), 0, 15), + 'nombre_evento' => 'Partido Amistoso', + 'fecha_evento' => now()->addDays(2)->format('Y-m-d'), + 'hora_inicio' => '18:00', + 'hora_fin' => '20:00' + ]); + + // Generar el primer QR directamente en BD simulando que ya lo pidió antes + QrCode::create([ + 'id_qr' => 'qr_' . uniqid(), + 'id_evento' => $evento->id_evento, + 'id_jugador' => $jugador->id_jugador, + 'tipo_qr' => 'invitado', + 'escaneos_restantes' => 1, + 'creado' => now() + ]); + + $this->withSession([ + 'user_logged_in' => true, + 'user_tipo' => 'jugador', + 'user_id' => $jugador->id_jugador + ]); + + $response = $this->post(route('panel.solicitar.qr'), [ + 'id_evento' => $evento->id_evento + ]); + + // Debería tirar mensaje de error de que ya solicitó + $response->assertSessionHas('panel_error', 'Ya solicitaste QRs para este evento.'); + $this->assertEquals(1, QrCode::where('id_evento', $evento->id_evento)->count()); + } +} diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php new file mode 100644 index 0000000..8364a84 --- /dev/null +++ b/tests/Feature/ExampleTest.php @@ -0,0 +1,19 @@ +get('/'); + + $response->assertStatus(200); + } +} diff --git a/tests/Feature/JugadorCreationTest.php b/tests/Feature/JugadorCreationTest.php new file mode 100644 index 0000000..829c305 --- /dev/null +++ b/tests/Feature/JugadorCreationTest.php @@ -0,0 +1,80 @@ +clubA = Club::create(['id_club' => 101, 'nombre' => 'Club Alpha']); + $this->clubB = Club::create(['id_club' => 102, 'nombre' => 'Club Beta']); + } + + public function test_duplicate_dni_shows_club_name_error() + { + // 1. Create an existing player in Club Alpha + Jugador::create([ + 'id_jugador' => '1019001', + 'documento' => '99999999', + 'nombre' => 'Existing', + 'apellido' => 'Player', + 'fecha_nacimiento' => '1990-01-01', + 'id_club_actual' => 101, + 'id_club_origen' => 101, + 'activo' => 1 + ]); + + // 2. Log in as SuperAdmin (Role 1) + $this->withSession([ + 'admin_logged_in' => true, + 'admin_role' => 1, + 'admin_username' => 'supertest' + ]); + + // 3. Try to create a new player with the same DNI + $response = $this->post(route('admin.jugadores.store'), [ + 'documento' => '99999999', + 'nombre' => 'New', + 'apellido' => 'Player', + 'fecha_nacimiento' => '1995-05-05', + 'id_club_actual' => 102, + 'id_club_origen' => 102 + ]); + + // 4. Assert error message contains the club name + $response->assertSessionHasErrors(['documento']); + $errors = session('errors')->get('documento'); + $this->assertStringContainsString('Club Alpha', $errors[0]); + } + + public function test_club_admin_can_see_all_clubs_in_origen() + { + // Log in as Club Admin (Role 2) + $this->withSession([ + 'admin_logged_in' => true, + 'admin_role' => 2, + 'admin_id_club' => 101, + 'admin_username' => 'clubadmin' + ]); + + $response = $this->get(route('admin.jugadores.create')); + + $response->assertStatus(200); + $response->assertSee('Club Alpha'); + $response->assertSee('Club Beta'); + } +} diff --git a/tests/Feature/PaseTest.php b/tests/Feature/PaseTest.php new file mode 100644 index 0000000..14c2268 --- /dev/null +++ b/tests/Feature/PaseTest.php @@ -0,0 +1,64 @@ + 101, 'nombre' => 'Club A']); + $clubB = Club::create(['id_club' => 102, 'nombre' => 'Club B']); + + $jugador = Jugador::create([ + 'id_jugador' => 'T-PASE-1', + 'documento' => '99000111', + 'nombre' => 'Pedro', + 'apellido' => 'Pase', + 'id_club_actual' => $clubA->id_club, + 'id_club_origen' => $clubA->id_club, + 'activo' => true + ]); + + // 2. Crear solicitud de pase + $pase = Pase::create([ + 'id_jugador' => $jugador->id_jugador, + 'id_club_origen' => $clubA->id_club, + 'id_club_destino' => $clubB->id_club, + 'estado' => 'Pendiente' + ]); + + // 3. Loguear como SuperAdmin + $this->withSession([ + 'admin_role' => 1, + 'admin_logged_in' => true + ]); + + // 4. Ejecutar la aprobación + $response = $this->put(route('admin.pases.aprobar', $pase->id_pase)); + + // 5. Verificar redirección y base de datos + $response->assertRedirect(route('admin.pases.index')); + + // El pase debe estar aprobado + $this->assertDatabaseHas('pases', [ + 'id_jugador' => $jugador->id_jugador, + 'estado' => 'Aprobado' + ]); + + // El jugador debe haber cambiado de club actual + $jugador->refresh(); + $this->assertEquals($clubB->id_club, $jugador->id_club_actual); + $this->assertEquals($clubA->id_club, $jugador->id_club_origen); + } +} diff --git a/tests/Feature/SoftDeleteTest.php b/tests/Feature/SoftDeleteTest.php new file mode 100644 index 0000000..edad084 --- /dev/null +++ b/tests/Feature/SoftDeleteTest.php @@ -0,0 +1,52 @@ + 888, 'nombre' => 'Club Borrable']); + $jugador = Jugador::create([ + 'id_jugador' => 'T-SD-1', + 'documento' => '11111111', + 'nombre' => 'Borra', + 'apellido' => 'Mela', + 'id_club_actual' => $club->id_club, + 'id_club_origen' => $club->id_club, + 'activo' => true + ]); + $evento = Evento::create([ + 'id_evento' => 'UUID-SD-1', + 'nombre_evento' => 'Evento Prueba SD', + 'fecha_evento' => now()->format('Y-m-d'), + 'hora_inicio' => '18:00', + 'hora_fin' => '20:00' + ]); + + // 2. Ejecutar borrado + $club->delete(); + $jugador->delete(); + $evento->delete(); + + // 3. Verificar que siguen en la base de datos (físicamente) pero con deleted_at + $this->assertSoftDeleted('clubes', ['id_club' => 888]); + $this->assertSoftDeleted('jugadores', ['id_jugador' => 'T-SD-1']); + $this->assertSoftDeleted('eventos', ['id_evento' => 'UUID-SD-1']); + + // 4. Verificar que no aparecen en consultas normales + $this->assertNull(Club::find(888)); + + // 5. Verificar que aparecen si usamos withTrashed() + $this->assertNotNull(Club::withTrashed()->find(888)); + } +} diff --git a/tests/Feature/TFICorrectionsTest.php b/tests/Feature/TFICorrectionsTest.php new file mode 100644 index 0000000..434edd9 --- /dev/null +++ b/tests/Feature/TFICorrectionsTest.php @@ -0,0 +1,108 @@ + 'superadmin_test', + 'password' => bcrypt('password'), + 'role' => 1 // SuperAdmin + ]); + + $club1 = Club::create(['nombre' => 'Club Viejo']); + $club2 = Club::create(['nombre' => 'Club Nuevo']); + + $this->withSession(['admin_logged_in' => true, 'admin_role' => 1]); + + $response = $this->get(route('admin.clubes.index')); + + $response->assertStatus(200); + + // El club con mayor ID (el último creado) debería aparecer primero en el HTML + $response->assertSeeInOrder(['Club Nuevo', 'Club Viejo']); + } + + /** + * Test que verifica el bloqueo de botón y validación JS (indirectamente via form attributes). + */ + public function test_admin_layout_contains_form_validation_script() + { + $this->withSession(['admin_logged_in' => true, 'admin_role' => 1]); + $response = $this->get(route('admin.dashboard')); + + $response->assertStatus(200); + // Verificar que el script de validación inyectado esté presente + $response->assertSee('handleFormValidation'); + } + + /** + * Test que verifica la ruta de descarga de QR en PDF. + */ + public function test_qr_pdf_download_route_is_accessible() + { + $club = Club::create(['nombre' => 'Club Test']); + $jugador = Jugador::create([ + 'id_jugador' => 'JUG_TEST_PDF', + 'nombre' => 'Test', + 'apellido' => 'User', + 'documento' => '99999999', + 'email' => 'test@pdf.com', + 'id_club_actual' => $club->id_club + ]); + + $evento = Evento::create([ + 'id_evento' => 'EVE_TEST_PDF', + 'nombre_evento' => 'Evento PDF', + 'fecha_evento' => now()->toDateString(), + 'hora_inicio' => '10:00', + 'hora_fin' => '12:00' + ]); + + $qr = QrCode::create([ + 'id_qr' => 'qr_pdf_test', + 'id_evento' => $evento->id_evento, + 'id_jugador' => $jugador->id_jugador, + 'tipo_qr' => 'invitado', + 'creado' => now() + ]); + + $this->withSession([ + 'user_logged_in' => true, + 'user_tipo' => 'jugador', + 'user_id' => $jugador->id_jugador + ]); + + $response = $this->get(route('panel.qr.descargar', $qr->id_qr)); + + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/pdf'); + } + + /** + * Test que verifica la visibilidad del manual de usuario. + */ + public function test_documentation_is_accessible() + { + $response = $this->get(route('documentacion.index')); + + $response->assertStatus(200); + $response->assertSee('Manual de Usuario'); + } +} diff --git a/tests/GUIA_TESTS.md b/tests/GUIA_TESTS.md new file mode 100644 index 0000000..7b8102f --- /dev/null +++ b/tests/GUIA_TESTS.md @@ -0,0 +1,65 @@ +# 🃏 Machete de Tests — OnAPB +### Resumen para la Defensa del Proyecto (Taller de Integración) + +Este documento sirve como guía para explicarle al profesor qué estamos probando exactamente con cada comando de `php artisan test`. + +--- + +## 🏗️ Conceptos Clave (Para responder si preguntan) +- **Feature Tests:** Prueban una funcionalidad completa desde la perspectiva del usuario (ej: hacer login, crear un club). +- **DatabaseTransactions:** Es un "Trait" que usamos en los tests para que cada prueba se envuelva en una transacción de base de datos y se haga un **Rollback** al final. Así, los datos de prueba nunca ensucian la base de datos real. +- **Assertions (Afirmaciones):** Son las comprobaciones que hacemos, como `assertStatus(200)` (la página cargó bien) o `assertDatabaseHas` (el dato se guardó en la BD). + +--- + +## 📂 Detalle de los Tests (Qué hace cada uno) + +### 1. `AuthTest.php` (Autenticación) +- **Qué prueba:** Que los 3 tipos de usuarios (Admin, Jugador, Aficionado) puedan entrar al sistema con sus credenciales y que el Logout funcione. +- **Escenario crítico:** Verifica que el captcha de Cloudflare (Turnstile) sea sorteado correctamente en el entorno de pruebas. + +### 2. `AdminTest.php` (Seguridad y Roles) +- **Qué prueba:** El control de acceso. + - Un visitante o un jugador **no deben** poder entrar al `/admin` (Error 403). + - Un Súper Admin **sí debe** poder entrar y ver el "Panel de Control". + - El Súper Admin puede crear un Club y este se guarda correctamente en la BD. + +### 3. `PaseTest.php` (Lógica de Negocio — **NUEVO**) +- **Qué prueba:** El flujo de traspaso de un jugador. +- **Escenario:** Un Súper Admin aprueba un pase pendiente. El test verifica que: + 1. El estado del pase cambie a "Aprobado". + 2. El `id_club_actual` del jugador se actualice automáticamente al club de destino. + +### 4. `SoftDeleteTest.php` (Recuperación ante fallos — **NUEVO**) +- **Qué prueba:** Que el borrado sea "lógico" y no "físico". +- **Escenario:** Al eliminar un Club o un Jugador, el test verifica que el registro **siga existiendo** en la base de datos pero con la columna `deleted_at` completa. Esto demuestra la capacidad de recuperación del sistema. + +### 5. `EventoQRTest.php` (Generación de QRs) +- **Qué prueba:** La funcionalidad estrella del sistema. +- **Escenario:** Un aficionado solicita un QR para un partido. El test verifica que se genere el registro en la tabla `qr_codes` y que el sistema **impida** solicitar un segundo QR para el mismo evento (evita fraudes). + +### 6. `JugadorCreationTest.php` (Validaciones) +- **Qué prueba:** Reglas de integridad de datos. +- **Escenario:** Intentar crear un jugador con un DNI que ya existe. El test verifica que el sistema devuelva un error amigable indicando a qué club pertenece ese DNI ya registrado. + +### 7. `CleanupTest.php` (Mantenimiento) +- **Qué prueba:** Que el sistema de limpieza automática funcione. +- **Escenario:** Simula la limpieza de eventos viejos. Verifica que se borren los QRs (para ahorrar espacio) pero que el **Evento (partido)** se mantenga para el historial de torneos. + +--- + +### 8. `TFICorrectionsTest.php` (Correcciones Finales — **NUEVO**) +- **Qué prueba:** Las últimas mejoras solicitadas por los profesores. +- **Escenario:** + 1. Verifica que los listados de administración estén ordenados de forma descendente (último creado, primero en verse). + 2. Verifica que la descarga de QRs en formato PDF funcione correctamente y devuelva el archivo esperado. + 3. Verifica que el Manual de Usuario sea accesible y se renderice correctamente en la web. + 4. Verifica la presencia del sistema de bloqueo de botones y validación proactiva en el panel. + +--- + +## 🚀 Cómo ejecutar y mostrar +Si el profesor pide ver los tests en vivo: +1. Abrir la terminal. +2. Correr: `php artisan test` +3. Explicar: *"Instalamos una batería de 22 tests que cubren desde el login y la lógica de pases hasta las nuevas correcciones de ordenamiento, descarga de PDFs y documentación integrada. Esto asegura que el sistema sea extremadamente robusto para la entrega final."* diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..fe1ffc2 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,10 @@ +assertTrue(true); + } +}