341 lines
11 KiB
PHP
341 lines
11 KiB
PHP
<?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);
|
|
});
|
|
});
|