From 8bc5efdea517c18edcae2e891443c7a163502895 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Tue, 31 Mar 2026 10:41:22 +0700 Subject: [PATCH] feat: add comprehensive feature tests for Student management and update StudentFactory constraints --- database/factories/StudentFactory.php | 6 +- tests/Feature/StudentTest.php | 340 ++++++++++++++++++++++++++ 2 files changed, 343 insertions(+), 3 deletions(-) create mode 100644 tests/Feature/StudentTest.php diff --git a/database/factories/StudentFactory.php b/database/factories/StudentFactory.php index 94f6cd9..2958f6a 100644 --- a/database/factories/StudentFactory.php +++ b/database/factories/StudentFactory.php @@ -20,11 +20,11 @@ public function definition(): array return [ 'user_id' => User::factory(), 'full_name' => fake()->name(), - 'student_number' => fake()->unique()->numerify('##########'), - 'phone_number' => fake()->phoneNumber(), + 'student_number' => fake()->unique()->regexify('[1-9]{1}[0-9]{9}'), + 'phone_number' => '0812'.fake()->numerify('########'), 'sex' => fake()->randomElement(['male', 'female']), 'address' => fake()->address(), - 'date_of_birth' => fake()->date(), + 'date_of_birth' => fake()->dateTimeBetween('-30 years', '-16 years')->format('Y-m-d'), 'place_of_birth' => fake()->city(), ]; } diff --git a/tests/Feature/StudentTest.php b/tests/Feature/StudentTest.php new file mode 100644 index 0000000..9bfd740 --- /dev/null +++ b/tests/Feature/StudentTest.php @@ -0,0 +1,340 @@ +value); + Role::findOrCreate(RoleEnum::Kosma->value); + Role::findOrCreate(RoleEnum::Student->value); + + $permissions = [ + 'ViewAny:Student', + 'Create:Student', + 'Update:Student', + 'Delete:Student', + 'Restore:Student', + 'ForceDelete:Student', + 'RestoreAny:Student', + 'ForceDeleteAny:Student', + ]; + + foreach ($permissions as $permission) { + Permission::findOrCreate($permission); + } +}); + +describe('Student Authorization', function () { + it('restricts access to Developer and Kosma roles only', function () { + $developer = User::factory()->create(); + $developer->assignRole(RoleEnum::Developer); + $developer->givePermissionTo('ViewAny:Student'); + + $kosma = User::factory()->create(); + $kosma->assignRole(RoleEnum::Kosma); + $kosma->givePermissionTo('ViewAny:Student'); + + $regularStudent = User::factory()->create(); + $regularStudent->assignRole(RoleEnum::Student); + + $this->actingAs($developer); + Livewire::test(ListStudents::class)->assertSuccessful(); + + $this->actingAs($kosma); + Livewire::test(ListStudents::class)->assertSuccessful(); + + $this->actingAs($regularStudent); + Livewire::test(ListStudents::class)->assertStatus(403); + }); +}); + +describe('Student Model', function () { + it('has a relationship with User', function () { + $student = Student::factory()->create(); + expect($student->user)->toBeInstanceOf(User::class); + }); + + it('supports Soft Deletes', function () { + $student = Student::factory()->create(); + $student->delete(); + + expect($student->trashed())->toBeTrue(); + expect(Student::find($student->id))->toBeNull(); + }); + + it('has sex scopes', function () { + Student::factory()->create(['sex' => Sex::Male]); + Student::factory()->create(['sex' => Sex::Female]); + + expect(Student::male()->count())->toBe(1); + expect(Student::female()->count())->toBe(1); + }); +}); + +describe('Student CRUD in Filament', function () { + beforeEach(function () { + $user = User::factory()->create(); + $user->assignRole(RoleEnum::Developer); + $user->givePermissionTo(['ViewAny:Student', 'Create:Student', 'Update:Student', 'Delete:Student']); + $this->actingAs($user); + }); + + it('can list students', function () { + $students = Student::factory()->count(3)->create(); + Livewire::test(ListStudents::class) + ->assertCanSeeTableRecords($students); + }); + + it('can create a new student', function () { + Livewire::test(CreateStudent::class) + ->fillForm([ + 'email' => 'new.student@example.com', + 'username' => 'newstudent', + 'full_name' => 'New Student', + 'student_number' => '9999999999', + 'sex' => Sex::Male->value, + 'phone_number' => '08123456789', + 'date_of_birth' => '2000-01-01', + 'place_of_birth' => 'Jakarta', + 'address' => 'Test address', + ]) + ->call('create') + ->assertHasNoFormErrors(); + + $this->assertDatabaseHas('students', [ + 'full_name' => 'New Student', + 'student_number' => '9999999999', + ]); + + $this->assertDatabaseHas('users', [ + 'email' => 'new.student@example.com', + 'username' => 'newstudent', + ]); + }); + + it('can update student data', function () { + $student = Student::factory()->create(['full_name' => 'Old Name']); + + Livewire::test(EditStudent::class, [ + 'record' => $student->getRouteKey(), + ]) + ->fillForm([ + 'full_name' => 'Updated Name', + 'phone_number' => '0812 3333 4444', + ]) + ->call('save') + ->assertHasNoFormErrors(); + + expect($student->refresh()->full_name)->toBe('Updated Name'); + }); + + it('can delete a student', function () { + $student = Student::factory()->create(); + + Livewire::test(ListStudents::class) + ->callTableAction('delete', $student); + + expect(Student::find($student->id))->toBeNull(); + expect(Student::withTrashed()->find($student->id))->not->toBeNull(); + }); +}); + +describe('Student Specific Features', function () { + beforeEach(function () { + $user = User::factory()->create(); + $user->assignRole(RoleEnum::Developer); + $user->givePermissionTo([ + 'ViewAny:Student', + 'Update:Student', + 'Delete:Student', + 'Restore:Student', + 'RestoreAny:Student', + 'ForceDelete:Student', + ]); + $this->actingAs($user); + }); + + it('can change student password', function () { + $student = Student::factory()->create(); + $newPassword = 'NewPassword123!'; + + Livewire::test(ListStudents::class) + ->callTableAction('changePassword', $student, [ + 'password' => $newPassword, + ]); + + expect(Hash::check($newPassword, $student->user->refresh()->password))->toBeTrue(); + }); + + it('can toggle student active status', function () { + $student = Student::factory()->create(); + $user = $student->user; + $user->update(['is_active' => IsActive::Active]); + + Livewire::test(ListStudents::class) + ->call('updateTableColumnState', 'user.is_active', $student->getRouteKey(), false); + + expect($user->refresh()->is_active)->toBe(IsActive::Inactive); + }); + + it('can restore a deleted student', function () { + $student = Student::factory()->create(); + $student->delete(); + + Livewire::test(ListStudents::class) + ->filterTable('trashed', 'only') + ->callTableAction('restore', $student); + + expect($student->refresh()->trashed())->toBeFalse(); + }); + + it('can perform bulk actions (Delete & Restore)', function () { + $students = Student::factory()->count(3)->create(); + + // Bulk Delete + Livewire::test(ListStudents::class) + ->callTableBulkAction('delete', $students); + + foreach ($students as $student) { + expect($student->refresh()->trashed())->toBeTrue(); + } + + // Bulk Restore + Livewire::test(ListStudents::class) + ->filterTable('trashed', 'only') + ->assertCanSeeTableRecords($students) + ->callTableBulkAction('restore', $students); + + foreach ($students as $student) { + expect($student->refresh()->trashed())->toBeFalse(); + } + }); + + it('can permanently delete a student (Force Delete)', function () { + $student = Student::factory()->create(); + + // Soft delete first for standard flow + $student->delete(); + + Livewire::test(ListStudents::class) + ->filterTable('trashed', 'only') + ->callTableAction('forceDelete', $student); + + $this->assertDatabaseMissing('students', ['id' => $student->id]); + }); +}); + +describe('Validations and Side Effects', function () { + beforeEach(function () { + $user = User::factory()->create(); + $user->assignRole(RoleEnum::Developer); + $user->givePermissionTo(['ViewAny:Student', 'Create:Student', 'Update:Student']); + $this->actingAs($user); + }); + + it('clears active sessions when user status is toggled to Inactive', function () { + $student = Student::factory()->create(); + $user = $student->user; + $user->update(['is_active' => IsActive::Active]); + + // Mock an active session in the database + DB::table('sessions')->insert([ + 'id' => 'test_session_id', + 'user_id' => $user->id, + 'ip_address' => '127.0.0.1', + 'user_agent' => 'Pest Test', + 'payload' => 'test_payload', + 'last_activity' => time(), + ]); + + $this->assertDatabaseHas('sessions', ['user_id' => $user->id]); + + Livewire::test(ListStudents::class) + ->call('updateTableColumnState', 'user.is_active', $student->getRouteKey(), false); + + expect($user->refresh()->is_active)->toBe(IsActive::Inactive); + $this->assertDatabaseMissing('sessions', ['user_id' => $user->id]); + }); + + it('enforces unique student number validation', function () { + Student::factory()->create(['student_number' => '1234567890']); + + Livewire::test(CreateStudent::class) + ->fillForm(['student_number' => '1234567890']) + ->call('create') + ->assertHasFormErrors(['student_number' => 'unique']); + }); + + it('enforces student minimum age requirement (15 years)', function () { + // Form allows maxDate is Carbon::now()->subYears(15) + $youngDate = now()->subYears(10)->format('Y-m-d'); + + Livewire::test(CreateStudent::class) + ->fillForm(['date_of_birth' => $youngDate]) + ->call('create') + ->assertHasFormErrors(['date_of_birth']); + }); +}); + +describe('Search, Sorting, and Filters', function () { + beforeEach(function () { + $user = User::factory()->create(); + $user->assignRole(RoleEnum::Developer); + $user->givePermissionTo(['ViewAny:Student']); + $this->actingAs($user); + }); + + it('can search students by name or NIM', function () { + $target = Student::factory()->create([ + 'full_name' => 'Target Search', + 'student_number' => '1122334455', + ]); + $other = Student::factory()->create([ + 'full_name' => 'Other Student', + 'student_number' => '9988776655', + ]); + + Livewire::test(ListStudents::class) + ->searchTable('Target Search') + ->assertCanSeeTableRecords([$target]) + ->assertCanNotSeeTableRecords([$other]) + ->searchTable('1122334455') + ->assertCanSeeTableRecords([$target]); + }); + + it('can filter students by sex', function () { + $male = Student::factory()->create(['sex' => Sex::Male]); + $female = Student::factory()->create(['sex' => Sex::Female]); + + Livewire::test(ListStudents::class) + ->filterTable('sex', Sex::Male->value) + ->assertCanSeeTableRecords([$male]) + ->assertCanNotSeeTableRecords([$female]); + }); + + it('can sort students by name', function () { + Student::query()->forceDelete(); + + $studentA = Student::factory()->create(['full_name' => 'A Student']); + $studentB = Student::factory()->create(['full_name' => 'B Student']); + $studentC = Student::factory()->create(['full_name' => 'C Student']); + + Livewire::test(ListStudents::class) + ->sortTable('full_name', 'asc') + ->assertCanSeeTableRecords([$studentA, $studentB, $studentC], inOrder: true); + }); +});