seed(RolePermissionSeeder::class); Queue::fake(); }); // ─── Helper ─────────────────────────────────────────────── function createLeaveRequestUserWithPermission(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 createLeaveEmployeeUser(): User { $user = User::factory()->create(); UserProfile::factory()->create([ 'user_id' => $user->id, 'full_name' => fake()->name(), ]); Employee::factory()->create(['user_id' => $user->id]); $user->assignRole('marketing-offline'); $user->forgetCachedPermissions(); return $user; } function createEmployeeUserWithLeavePermission(PermissionEnum ...$permissions): User { $user = createLeaveEmployeeUser(); $user->givePermissionTo( array_merge( [PermissionEnum::DASHBOARD_VIEW->value], array_map(fn (PermissionEnum $p) => $p->value, $permissions) ) ); $user->forgetCachedPermissions(); return $user; } function leaveRequestPayload(): array { return [ 'start_date' => now()->addDays(2)->format('Y-m-d'), 'end_date' => now()->addDays(4)->format('Y-m-d'), ]; } // ─── Index ──────────────────────────────────────────────── describe('Leave Request Index', function () { test('authenticated user with permission can view leave request index', function () { $user = createLeaveRequestUserWithPermission(PermissionEnum::LEAVE_REQUESTS_VIEW); $this->actingAs($user) ->get(route('admin.hr.leave_requests.index')) ->assertOk(); }); test('guest is redirected to login', function () { $this->get(route('admin.hr.leave_requests.index')) ->assertRedirect(route('login')); }); test('user without permission is forbidden', function () { $user = User::factory()->create(); $this->actingAs($user) ->get(route('admin.hr.leave_requests.index')) ->assertForbidden(); }); test('index displays leave requests for manager role', function () { $user = createLeaveRequestUserWithPermission(PermissionEnum::LEAVE_REQUESTS_VIEW); LeaveRequest::factory()->count(3)->create(); $this->actingAs($user) ->get(route('admin.hr.leave_requests.index')) ->assertOk(); }); test('employee only sees own leave requests', function () { $user = createEmployeeUserWithLeavePermission( PermissionEnum::LEAVE_REQUESTS_VIEW, PermissionEnum::LEAVE_REQUESTS_CREATE, ); $ownRequest = LeaveRequest::factory()->create([ 'employee_id' => $user->employee->id, ]); $otherRequest = LeaveRequest::factory()->create(); $this->actingAs($user) ->get(route('admin.hr.leave_requests.index')) ->assertOk(); }); }); // ─── Store ──────────────────────────────────────────────── describe('Leave Request Store', function () { test('employee with permission can submit leave request', function () { $user = createEmployeeUserWithLeavePermission( PermissionEnum::LEAVE_REQUESTS_VIEW, PermissionEnum::LEAVE_REQUESTS_CREATE, ); $this->actingAs($user) ->post(route('admin.hr.leave_requests.store'), leaveRequestPayload()) ->assertRedirect(route('admin.hr.leave_requests.index')); $this->assertDatabaseHas('leave_requests', [ 'employee_id' => $user->employee->id, 'status' => LeaveRequestStatus::PENDING->value, ]); }); test('guest cannot submit leave request', function () { $this->post(route('admin.hr.leave_requests.store'), leaveRequestPayload()) ->assertRedirect(route('login')); }); test('user without create permission is forbidden', function () { $user = createLeaveRequestUserWithPermission(PermissionEnum::LEAVE_REQUESTS_VIEW); $this->actingAs($user) ->post(route('admin.hr.leave_requests.store'), leaveRequestPayload()) ->assertForbidden(); }); test('user without employee record cannot submit leave request', function () { $user = createLeaveRequestUserWithPermission( PermissionEnum::LEAVE_REQUESTS_VIEW, PermissionEnum::LEAVE_REQUESTS_CREATE, ); $this->actingAs($user) ->post(route('admin.hr.leave_requests.store'), leaveRequestPayload()) ->assertForbidden(); }); test('start_date is required', function () { $user = createEmployeeUserWithLeavePermission( PermissionEnum::LEAVE_REQUESTS_VIEW, PermissionEnum::LEAVE_REQUESTS_CREATE, ); $payload = leaveRequestPayload(); unset($payload['start_date']); $this->actingAs($user) ->post(route('admin.hr.leave_requests.store'), $payload) ->assertSessionHasErrors('start_date'); }); test('end_date is required', function () { $user = createEmployeeUserWithLeavePermission( PermissionEnum::LEAVE_REQUESTS_VIEW, PermissionEnum::LEAVE_REQUESTS_CREATE, ); $payload = leaveRequestPayload(); unset($payload['end_date']); $this->actingAs($user) ->post(route('admin.hr.leave_requests.store'), $payload) ->assertSessionHasErrors('end_date'); }); test('end_date must be after or equal to start_date', function () { $user = createEmployeeUserWithLeavePermission( PermissionEnum::LEAVE_REQUESTS_VIEW, PermissionEnum::LEAVE_REQUESTS_CREATE, ); $payload = [ 'start_date' => now()->addDays(5)->format('Y-m-d'), 'end_date' => now()->addDays(2)->format('Y-m-d'), ]; $this->actingAs($user) ->post(route('admin.hr.leave_requests.store'), $payload) ->assertSessionHasErrors('end_date'); }); test('cannot submit when there is a pending leave request', function () { $user = createEmployeeUserWithLeavePermission( PermissionEnum::LEAVE_REQUESTS_VIEW, PermissionEnum::LEAVE_REQUESTS_CREATE, ); LeaveRequest::factory()->create([ 'employee_id' => $user->employee->id, 'status' => LeaveRequestStatus::PENDING, ]); $this->actingAs($user) ->post(route('admin.hr.leave_requests.store'), leaveRequestPayload()) ->assertSessionHasErrors('start_date'); }); test('submitting leave request calculates total_days correctly', function () { $user = createEmployeeUserWithLeavePermission( PermissionEnum::LEAVE_REQUESTS_VIEW, PermissionEnum::LEAVE_REQUESTS_CREATE, ); $payload = [ 'start_date' => now()->addDays(2)->format('Y-m-d'), 'end_date' => now()->addDays(5)->format('Y-m-d'), ]; $this->actingAs($user) ->post(route('admin.hr.leave_requests.store'), $payload); $leaveRequest = LeaveRequest::where('employee_id', $user->employee->id)->first(); expect($leaveRequest->total_days)->toBe(4); }); test('submitting leave request creates with pending status', function () { $user = createEmployeeUserWithLeavePermission( PermissionEnum::LEAVE_REQUESTS_VIEW, PermissionEnum::LEAVE_REQUESTS_CREATE, ); $this->actingAs($user) ->post(route('admin.hr.leave_requests.store'), leaveRequestPayload()); $leaveRequest = LeaveRequest::where('employee_id', $user->employee->id)->first(); expect($leaveRequest->status)->toBe(LeaveRequestStatus::PENDING); expect($leaveRequest->verified_at)->toBeNull(); expect($leaveRequest->verified_by_id)->toBeNull(); }); }); // ─── Update ─────────────────────────────────────────────── describe('Leave Request Update', function () { test('employee with permission can update own pending leave request', function () { $user = createEmployeeUserWithLeavePermission( PermissionEnum::LEAVE_REQUESTS_VIEW, PermissionEnum::LEAVE_REQUESTS_UPDATE, ); $leaveRequest = LeaveRequest::factory()->create([ 'employee_id' => $user->employee->id, 'status' => LeaveRequestStatus::PENDING, ]); $payload = [ 'start_date' => now()->addDays(10)->format('Y-m-d'), 'end_date' => now()->addDays(12)->format('Y-m-d'), ]; $this->actingAs($user) ->put(route('admin.hr.leave_requests.update', $leaveRequest), $payload) ->assertRedirect(route('admin.hr.leave_requests.index')); expect($leaveRequest->fresh()->total_days)->toBe(3); }); test('guest cannot update leave request', function () { $leaveRequest = LeaveRequest::factory()->create(); $this->put(route('admin.hr.leave_requests.update', $leaveRequest), leaveRequestPayload()) ->assertRedirect(route('login')); }); test('user without update permission is forbidden', function () { $user = createLeaveRequestUserWithPermission(PermissionEnum::LEAVE_REQUESTS_VIEW); $leaveRequest = LeaveRequest::factory()->create([ 'employee_id' => Employee::factory()->create()->id, 'status' => LeaveRequestStatus::PENDING, ]); $this->actingAs($user) ->put(route('admin.hr.leave_requests.update', $leaveRequest), leaveRequestPayload()) ->assertForbidden(); }); test('employee cannot update other employee leave request', function () { $user = createEmployeeUserWithLeavePermission( PermissionEnum::LEAVE_REQUESTS_VIEW, PermissionEnum::LEAVE_REQUESTS_UPDATE, ); $otherLeaveRequest = LeaveRequest::factory()->create([ 'status' => LeaveRequestStatus::PENDING, ]); $this->actingAs($user) ->put(route('admin.hr.leave_requests.update', $otherLeaveRequest), leaveRequestPayload()) ->assertForbidden(); }); test('cannot update approved leave request', function () { $user = createEmployeeUserWithLeavePermission( PermissionEnum::LEAVE_REQUESTS_VIEW, PermissionEnum::LEAVE_REQUESTS_UPDATE, ); $leaveRequest = LeaveRequest::factory()->create([ 'employee_id' => $user->employee->id, 'status' => LeaveRequestStatus::APPROVED, ]); $this->actingAs($user) ->put(route('admin.hr.leave_requests.update', $leaveRequest), leaveRequestPayload()) ->assertForbidden(); }); }); // ─── Destroy ────────────────────────────────────────────── describe('Leave Request Destroy', function () { test('employee with permission can delete own leave request', function () { $user = createEmployeeUserWithLeavePermission( PermissionEnum::LEAVE_REQUESTS_VIEW, PermissionEnum::LEAVE_REQUESTS_DELETE, ); $leaveRequest = LeaveRequest::factory()->create([ 'employee_id' => $user->employee->id, ]); $this->actingAs($user) ->delete(route('admin.hr.leave_requests.destroy', $leaveRequest)) ->assertRedirect(route('admin.hr.leave_requests.index')); $this->assertDatabaseMissing('leave_requests', ['id' => $leaveRequest->id]); }); test('guest cannot delete leave request', function () { $leaveRequest = LeaveRequest::factory()->create(); $this->delete(route('admin.hr.leave_requests.destroy', $leaveRequest)) ->assertRedirect(route('login')); }); test('user without delete permission is forbidden', function () { $user = createLeaveRequestUserWithPermission(PermissionEnum::LEAVE_REQUESTS_VIEW); $leaveRequest = LeaveRequest::factory()->create(); $this->actingAs($user) ->delete(route('admin.hr.leave_requests.destroy', $leaveRequest)) ->assertForbidden(); }); }); // ─── Approve ────────────────────────────────────────────── describe('Leave Request Approve', function () { test('user with verify permission can approve leave request', function () { $user = createLeaveRequestUserWithPermission( PermissionEnum::LEAVE_REQUESTS_VIEW, PermissionEnum::LEAVE_REQUESTS_VERIFY, ); $leaveRequest = LeaveRequest::factory()->create([ 'status' => LeaveRequestStatus::PENDING, ]); $this->actingAs($user) ->post(route('admin.hr.leave_requests.approve', $leaveRequest)) ->assertRedirect(route('admin.hr.leave_requests.index')); expect($leaveRequest->fresh()->status)->toBe(LeaveRequestStatus::APPROVED); expect($leaveRequest->fresh()->verified_by_id)->toBe($user->id); expect($leaveRequest->fresh()->verified_at)->not->toBeNull(); }); test('guest cannot approve leave request', function () { $leaveRequest = LeaveRequest::factory()->create(); $this->post(route('admin.hr.leave_requests.approve', $leaveRequest)) ->assertRedirect(route('login')); }); test('user without verify permission is forbidden', function () { $user = createLeaveRequestUserWithPermission(PermissionEnum::LEAVE_REQUESTS_VIEW); $leaveRequest = LeaveRequest::factory()->create([ 'status' => LeaveRequestStatus::PENDING, ]); $this->actingAs($user) ->post(route('admin.hr.leave_requests.approve', $leaveRequest)) ->assertForbidden(); }); }); // ─── Reject ─────────────────────────────────────────────── describe('Leave Request Reject', function () { test('user with verify permission can reject leave request', function () { $user = createLeaveRequestUserWithPermission( PermissionEnum::LEAVE_REQUESTS_VIEW, PermissionEnum::LEAVE_REQUESTS_VERIFY, ); $leaveRequest = LeaveRequest::factory()->create([ 'status' => LeaveRequestStatus::PENDING, ]); $this->actingAs($user) ->post(route('admin.hr.leave_requests.reject', $leaveRequest), [ 'reason' => 'Tidak ada pengganti', ]) ->assertRedirect(route('admin.hr.leave_requests.index')); expect($leaveRequest->fresh()->status)->toBe(LeaveRequestStatus::REJECTED); expect($leaveRequest->fresh()->verified_by_id)->toBe($user->id); expect($leaveRequest->fresh()->verified_at)->not->toBeNull(); }); test('rejecting creates rejection record', function () { $user = createLeaveRequestUserWithPermission( PermissionEnum::LEAVE_REQUESTS_VIEW, PermissionEnum::LEAVE_REQUESTS_VERIFY, ); $leaveRequest = LeaveRequest::factory()->create([ 'status' => LeaveRequestStatus::PENDING, ]); $this->actingAs($user) ->post(route('admin.hr.leave_requests.reject', $leaveRequest), [ 'reason' => 'Stok karyawan kurang', ]); $this->assertDatabaseHas('rejections', [ 'rejectable_type' => LeaveRequest::class, 'rejectable_id' => $leaveRequest->id, 'reason' => 'Stok karyawan kurang', 'rejected_by_id' => $user->id, ]); }); test('guest cannot reject leave request', function () { $leaveRequest = LeaveRequest::factory()->create(); $this->post(route('admin.hr.leave_requests.reject', $leaveRequest), [ 'reason' => 'Test', ])->assertRedirect(route('login')); }); test('user without verify permission is forbidden', function () { $user = createLeaveRequestUserWithPermission(PermissionEnum::LEAVE_REQUESTS_VIEW); $leaveRequest = LeaveRequest::factory()->create([ 'status' => LeaveRequestStatus::PENDING, ]); $this->actingAs($user) ->post(route('admin.hr.leave_requests.reject', $leaveRequest), [ 'reason' => 'Test', ]) ->assertForbidden(); }); test('reason is required when rejecting', function () { $user = createLeaveRequestUserWithPermission( PermissionEnum::LEAVE_REQUESTS_VIEW, PermissionEnum::LEAVE_REQUESTS_VERIFY, ); $leaveRequest = LeaveRequest::factory()->create([ 'status' => LeaveRequestStatus::PENDING, ]); $this->actingAs($user) ->post(route('admin.hr.leave_requests.reject', $leaveRequest), [ 'reason' => '', ]) ->assertSessionHasErrors('reason'); }); }); // ─── Leave Request Model ────────────────────────────────── describe('Leave Request Model', function () { test('leave request has correct casts', function () { $leaveRequest = LeaveRequest::factory()->create([ 'total_days' => 5, ]); expect($leaveRequest->total_days)->toBeInt(); expect($leaveRequest->start_date)->toBeInstanceOf(CarbonInterface::class); expect($leaveRequest->end_date)->toBeInstanceOf(CarbonInterface::class); }); test('leave request belongs to employee', function () { $leaveRequest = LeaveRequest::factory()->create(); expect($leaveRequest->employee)->not->toBeNull(); expect($leaveRequest->employee)->toBeInstanceOf(Employee::class); }); test('leave request has status_label accessor', function () { $leaveRequest = LeaveRequest::factory()->create([ 'status' => LeaveRequestStatus::PENDING, ]); expect($leaveRequest->status_label)->toBe('Menunggu'); $leaveRequest->update(['status' => LeaveRequestStatus::APPROVED]); expect($leaveRequest->fresh()->status_label)->toBe('Disetujui'); $leaveRequest->update(['status' => LeaveRequestStatus::REJECTED]); expect($leaveRequest->fresh()->status_label)->toBe('Ditolak'); }); test('leave request has start_date_formatted accessor', function () { $leaveRequest = LeaveRequest::factory()->create([ 'start_date' => '2025-06-15', ]); expect($leaveRequest->start_date_formatted)->toBeString(); }); test('leave request has end_date_formatted accessor', function () { $leaveRequest = LeaveRequest::factory()->create([ 'end_date' => '2025-06-20', ]); expect($leaveRequest->end_date_formatted)->toBeString(); }); test('leave request has start_date_input accessor', function () { $leaveRequest = LeaveRequest::factory()->create([ 'start_date' => '2025-06-15', ]); expect($leaveRequest->start_date_input)->toBe('2025-06-15'); }); test('leave request has end_date_input accessor', function () { $leaveRequest = LeaveRequest::factory()->create([ 'end_date' => '2025-06-20', ]); expect($leaveRequest->end_date_input)->toBe('2025-06-20'); }); test('leave request has is_editable accessor', function () { $leaveRequest = LeaveRequest::factory()->create([ 'status' => LeaveRequestStatus::PENDING, ]); expect($leaveRequest->is_editable)->toBeTrue(); $leaveRequest->update(['status' => LeaveRequestStatus::APPROVED]); expect($leaveRequest->fresh()->is_editable)->toBeFalse(); }); test('leave request has can_verify accessor', function () { $leaveRequest = LeaveRequest::factory()->create([ 'status' => LeaveRequestStatus::PENDING, ]); expect($leaveRequest->can_verify)->toBeTrue(); $leaveRequest->update(['status' => LeaveRequestStatus::APPROVED]); expect($leaveRequest->fresh()->can_verify)->toBeFalse(); }); test('leave request has employee_name accessor', function () { $leaveRequest = LeaveRequest::factory()->create(); expect($leaveRequest->employee_name)->toBeString(); }); test('pending scope filters correctly', function () { LeaveRequest::factory()->create(['status' => LeaveRequestStatus::PENDING]); LeaveRequest::factory()->create(['status' => LeaveRequestStatus::APPROVED]); expect(LeaveRequest::query()->pending()->count())->toBe(1); }); test('approved scope filters correctly', function () { LeaveRequest::factory()->create(['status' => LeaveRequestStatus::PENDING]); LeaveRequest::factory()->create(['status' => LeaveRequestStatus::APPROVED]); expect(LeaveRequest::query()->approved()->count())->toBe(1); }); });