From e3b138d41008bec97b44acda8e826c366064b2b7 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Mon, 22 Jun 2026 18:20:42 +0700 Subject: [PATCH] refactor: update toggleStatus method in EmployeeController to accept validated request data for improved status management --- .../Admin/Hr/EmployeeController.php | 2 +- tests/Feature/Admin/Hr/EmployeeTest.php | 540 ++++++++++++++++++ 2 files changed, 541 insertions(+), 1 deletion(-) create mode 100644 tests/Feature/Admin/Hr/EmployeeTest.php diff --git a/app/Http/Controllers/Admin/Hr/EmployeeController.php b/app/Http/Controllers/Admin/Hr/EmployeeController.php index fb1c642..27e582d 100644 --- a/app/Http/Controllers/Admin/Hr/EmployeeController.php +++ b/app/Http/Controllers/Admin/Hr/EmployeeController.php @@ -99,7 +99,7 @@ public function update(EmployeeRequest $request, User $user): RedirectResponse public function toggleStatus(ToggleStatusRequest $request, User $user): RedirectResponse { - $this->employeeService->toggleStatus($user); + $this->employeeService->toggleStatus($user, $request->validated()); $this->flashSuccess('Status pegawai berhasil diubah.'); diff --git a/tests/Feature/Admin/Hr/EmployeeTest.php b/tests/Feature/Admin/Hr/EmployeeTest.php new file mode 100644 index 0000000..9ddc17e --- /dev/null +++ b/tests/Feature/Admin/Hr/EmployeeTest.php @@ -0,0 +1,540 @@ +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 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(); + }); +}); + +// ─── 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]); + }); +});