seed(RolePermissionSeeder::class); }); // ─── Helper ─────────────────────────────────────────────── function createEmployeeUserWithPermission(PermissionEnum ...$permissions): User { $user = User::factory()->create(); $user->givePermissionTo( array_merge( [PermissionEnum::DASHBOARD_VIEW->value], array_map(fn (PermissionEnum $p) => $p->value, $permissions) ) ); $user->forgetCachedPermissions(); return $user; } function createEmployeeWithProfile(): User { $user = User::factory()->create(); UserProfile::factory()->create([ 'user_id' => $user->id, 'full_name' => fake()->name(), 'phone_number' => fake()->numerify('08##########'), 'gender' => fake()->randomElement(['male', 'female']), ]); Employee::factory()->create(['user_id' => $user->id]); $user->assignRole('marketing'); return $user; } function createDeveloperUser(): User { $user = User::factory()->create(); UserProfile::factory()->create([ 'user_id' => $user->id, 'full_name' => fake()->name(), 'phone_number' => fake()->numerify('08##########'), 'gender' => fake()->randomElement(['male', 'female']), ]); $user->assignRole('developer'); return $user; } function validEmployeePayload(): array { return [ 'email' => 'pegawai@example.com', 'username' => 'pegawai_baru', 'full_name' => 'Pegawai Baru', 'phone_number' => '081234567890', 'gender' => 'male', 'birth_date' => '1995-01-15', 'address' => 'Jl. Merdeka No. 1', 'role' => 'marketing', 'join_date' => '2024-01-01', 'employment_status' => 'full_time', 'base_salary' => 5000000, ]; } // ─── Index ──────────────────────────────────────────────── describe('Employee Index', function () { test('authenticated user with permission can view employee index', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW); $this->actingAs($user) ->get(route('admin.hr.employees.index')) ->assertOk(); }); test('guest is redirected to login', function () { $this->get(route('admin.hr.employees.index')) ->assertRedirect(route('login')); }); test('user without permission is forbidden', function () { $user = User::factory()->create(); $this->actingAs($user) ->get(route('admin.hr.employees.index')) ->assertForbidden(); }); test('index displays employees', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW); createEmployeeWithProfile(); createEmployeeWithProfile(); $this->actingAs($user) ->get(route('admin.hr.employees.index')) ->assertOk(); }); test('index can search by name', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW); $employee = createEmployeeWithProfile(); $employee->profile->update(['full_name' => 'Budi Santoso']); $this->actingAs($user) ->get(route('admin.hr.employees.index', ['search' => 'Budi'])) ->assertOk(); }); test('index can filter by role', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW); createEmployeeWithProfile(); $this->actingAs($user) ->get(route('admin.hr.employees.index', ['role' => 'marketing'])) ->assertOk(); }); test('index can filter by is_active status', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW); createEmployeeWithProfile(); $this->actingAs($user) ->get(route('admin.hr.employees.index', ['is_active' => '1'])) ->assertOk(); }); test('index does not display developer role users', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW); $developer = createDeveloperUser(); $employee = createEmployeeWithProfile(); $this->actingAs($user) ->get(route('admin.hr.employees.index')) ->assertOk() ->assertDontSee($developer->email) ->assertSee($employee->email); }); }); // ─── Create ─────────────────────────────────────────────── describe('Employee Create', function () { test('authenticated user with permission can view create form', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_CREATE); $this->actingAs($user) ->get(route('admin.hr.employees.create')) ->assertOk(); }); test('guest is redirected to login', function () { $this->get(route('admin.hr.employees.create')) ->assertRedirect(route('login')); }); test('user without create permission is forbidden', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW); $this->actingAs($user) ->get(route('admin.hr.employees.create')) ->assertForbidden(); }); }); // ─── Store ──────────────────────────────────────────────── describe('Employee Store', function () { test('authenticated user with permission can create an employee', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_CREATE); $this->actingAs($user) ->post(route('admin.hr.employees.store'), validEmployeePayload()) ->assertRedirect(route('admin.hr.employees.index')); $this->assertDatabaseHas('users', [ 'email' => 'pegawai@example.com', 'username' => 'pegawai_baru', ]); }); test('guest cannot create an employee', function () { $this->post(route('admin.hr.employees.store'), validEmployeePayload()) ->assertRedirect(route('login')); }); test('user without create permission is forbidden', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW); $this->actingAs($user) ->post(route('admin.hr.employees.store'), validEmployeePayload()) ->assertForbidden(); }); test('email is required', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_CREATE); $payload = validEmployeePayload(); $payload['email'] = ''; $this->actingAs($user) ->post(route('admin.hr.employees.store'), $payload) ->assertSessionHasErrors('email'); }); test('email must be unique', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_CREATE); $existing = User::factory()->create(); $payload = validEmployeePayload(); $payload['email'] = $existing->email; $this->actingAs($user) ->post(route('admin.hr.employees.store'), $payload) ->assertSessionHasErrors('email'); }); test('username is required', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_CREATE); $payload = validEmployeePayload(); $payload['username'] = ''; $this->actingAs($user) ->post(route('admin.hr.employees.store'), $payload) ->assertSessionHasErrors('username'); }); test('username must be unique', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_CREATE); $existing = User::factory()->create(); $payload = validEmployeePayload(); $payload['username'] = $existing->username; $this->actingAs($user) ->post(route('admin.hr.employees.store'), $payload) ->assertSessionHasErrors('username'); }); test('full_name is required', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_CREATE); $payload = validEmployeePayload(); $payload['full_name'] = ''; $this->actingAs($user) ->post(route('admin.hr.employees.store'), $payload) ->assertSessionHasErrors('full_name'); }); test('role is required', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_CREATE); $payload = validEmployeePayload(); $payload['role'] = ''; $this->actingAs($user) ->post(route('admin.hr.employees.store'), $payload) ->assertSessionHasErrors('role'); }); test('role must be assignable', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_CREATE); $payload = validEmployeePayload(); $payload['role'] = 'developer'; $this->actingAs($user) ->post(route('admin.hr.employees.store'), $payload) ->assertSessionHasErrors('role'); }); test('join_date is required for non-owner roles', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_CREATE); $payload = validEmployeePayload(); $payload['join_date'] = ''; $this->actingAs($user) ->post(route('admin.hr.employees.store'), $payload) ->assertSessionHasErrors('join_date'); }); test('employment_status is required for non-owner roles', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_CREATE); $payload = validEmployeePayload(); $payload['employment_status'] = ''; $this->actingAs($user) ->post(route('admin.hr.employees.store'), $payload) ->assertSessionHasErrors('employment_status'); }); test('base_salary is required for non-owner roles', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_CREATE); $payload = validEmployeePayload(); $payload['base_salary'] = ''; $this->actingAs($user) ->post(route('admin.hr.employees.store'), $payload) ->assertSessionHasErrors('base_salary'); }); test('phone_number must be valid format', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_CREATE); $payload = validEmployeePayload(); $payload['phone_number'] = '123invalid'; $this->actingAs($user) ->post(route('admin.hr.employees.store'), $payload) ->assertSessionHasErrors('phone_number'); }); test('creating employee also creates profile and employee record', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_CREATE); $this->actingAs($user) ->post(route('admin.hr.employees.store'), validEmployeePayload()); $newUser = User::where('email', 'pegawai@example.com')->first(); expect($newUser)->not->toBeNull(); expect($newUser->profile)->not->toBeNull(); expect($newUser->employee)->not->toBeNull(); expect($newUser->profile->full_name)->toBe('Pegawai Baru'); expect($newUser->employee->base_salary)->toBe(5000000); }); }); // ─── Edit ───────────────────────────────────────────────── describe('Employee Edit', function () { test('authenticated user with permission can view edit form', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_UPDATE); $employee = createEmployeeWithProfile(); $this->actingAs($user) ->get(route('admin.hr.employees.edit', $employee)) ->assertOk(); }); test('guest is redirected to login', function () { $employee = createEmployeeWithProfile(); $this->get(route('admin.hr.employees.edit', $employee)) ->assertRedirect(route('login')); }); test('user without update permission is forbidden', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW); $employee = createEmployeeWithProfile(); $this->actingAs($user) ->get(route('admin.hr.employees.edit', $employee)) ->assertForbidden(); }); }); // ─── Update ─────────────────────────────────────────────── describe('Employee Update', function () { test('authenticated user with permission can update an employee', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_UPDATE); $employee = createEmployeeWithProfile(); $payload = validEmployeePayload(); $payload['email'] = $employee->email; $payload['username'] = $employee->username; $payload['full_name'] = 'Nama Diubah'; $this->actingAs($user) ->put(route('admin.hr.employees.update', $employee), $payload) ->assertRedirect(route('admin.hr.employees.index')); expect($employee->fresh()->profile->full_name)->toBe('Nama Diubah'); }); test('guest cannot update an employee', function () { $employee = createEmployeeWithProfile(); $this->put(route('admin.hr.employees.update', $employee), validEmployeePayload()) ->assertRedirect(route('login')); }); test('user without update permission is forbidden', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW); $employee = createEmployeeWithProfile(); $this->actingAs($user) ->put(route('admin.hr.employees.update', $employee), validEmployeePayload()) ->assertForbidden(); }); test('email must be unique on update (ignoring self)', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_UPDATE); $employee = createEmployeeWithProfile(); $payload = validEmployeePayload(); $payload['email'] = $employee->email; $payload['username'] = $employee->username; $this->actingAs($user) ->put(route('admin.hr.employees.update', $employee), $payload) ->assertRedirect(route('admin.hr.employees.index')); }); }); // ─── Toggle Status ──────────────────────────────────────── describe('Employee Toggle Status', function () { test('authenticated user with permission can toggle status', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_TOGGLE_STATUS); $employee = createEmployeeWithProfile(); $originalStatus = $employee->is_active; $this->actingAs($user) ->patch(route('admin.hr.employees.toggle_status', $employee), [ 'is_active' => ! $originalStatus, ]) ->assertRedirect(route('admin.hr.employees.index')); expect($employee->fresh()->is_active)->toBe(! $originalStatus); }); test('guest cannot toggle status', function () { $employee = createEmployeeWithProfile(); $this->patch(route('admin.hr.employees.toggle_status', $employee), [ 'is_active' => false, ])->assertRedirect(route('login')); }); test('user without toggle permission is forbidden', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW); $employee = createEmployeeWithProfile(); $this->actingAs($user) ->patch(route('admin.hr.employees.toggle_status', $employee), [ 'is_active' => false, ]) ->assertForbidden(); }); test('is_active field is required', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_TOGGLE_STATUS); $employee = createEmployeeWithProfile(); $this->actingAs($user) ->patch(route('admin.hr.employees.toggle_status', $employee), [ 'is_active' => null, ]) ->assertSessionHasErrors('is_active'); }); }); // ─── Reset Password ─────────────────────────────────────── describe('Employee Reset Password', function () { test('authenticated user with permission can reset password', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_RESET_PASSWORD); $employee = createEmployeeWithProfile(); $this->actingAs($user) ->post(route('admin.hr.employees.reset_password', $employee)) ->assertRedirect(route('admin.hr.employees.index')); }); test('guest cannot reset password', function () { $employee = createEmployeeWithProfile(); $this->post(route('admin.hr.employees.reset_password', $employee)) ->assertRedirect(route('login')); }); test('user without reset password permission is forbidden', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW); $employee = createEmployeeWithProfile(); $this->actingAs($user) ->post(route('admin.hr.employees.reset_password', $employee)) ->assertForbidden(); }); }); // ─── Destroy ────────────────────────────────────────────── describe('Employee Destroy', function () { test('authenticated user with permission can delete an employee', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_DELETE); $employee = createEmployeeWithProfile(); $this->actingAs($user) ->delete(route('admin.hr.employees.destroy', $employee)) ->assertRedirect(route('admin.hr.employees.index')); $this->assertSoftDeleted('users', ['id' => $employee->id]); }); test('guest cannot delete an employee', function () { $employee = createEmployeeWithProfile(); $this->delete(route('admin.hr.employees.destroy', $employee)) ->assertRedirect(route('login')); $this->assertNotSoftDeleted('users', ['id' => $employee->id]); }); test('user without delete permission is forbidden', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW); $employee = createEmployeeWithProfile(); $this->actingAs($user) ->delete(route('admin.hr.employees.destroy', $employee)) ->assertForbidden(); $this->assertNotSoftDeleted('users', ['id' => $employee->id]); }); test('deleting employee also soft deletes profile and employee record', function () { $user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_DELETE); $employee = createEmployeeWithProfile(); $profileId = $employee->profile->id; $employeeId = $employee->employee->id; $this->actingAs($user) ->delete(route('admin.hr.employees.destroy', $employee)); $this->assertSoftDeleted('users', ['id' => $employee->id]); $this->assertSoftDeleted('user_profiles', ['id' => $profileId]); $this->assertSoftDeleted('employees', ['id' => $employeeId]); }); });