feat: add comprehensive feature tests for Student management and update StudentFactory constraints
This commit is contained in:
parent
8050af54f6
commit
8bc5efdea5
@ -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(),
|
||||
];
|
||||
}
|
||||
|
||||
340
tests/Feature/StudentTest.php
Normal file
340
tests/Feature/StudentTest.php
Normal file
@ -0,0 +1,340 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\IsActive;
|
||||
use App\Enums\RoleEnum;
|
||||
use App\Enums\Sex;
|
||||
use App\Filament\Resources\Master\Students\Pages\CreateStudent;
|
||||
use App\Filament\Resources\Master\Students\Pages\EditStudent;
|
||||
use App\Filament\Resources\Master\Students\Pages\ListStudents;
|
||||
use App\Models\Student;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Livewire\Livewire;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
/**
|
||||
* Setup necessary roles and permissions
|
||||
*/
|
||||
beforeEach(function () {
|
||||
Role::findOrCreate(RoleEnum::Developer->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);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user