get(route('admin.hr.leave-requests.index')); $response->assertRedirect(route('login')); }); test('authenticated users can visit the leave request index page', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->get(route('admin.hr.leave-requests.index')); $response->assertOk(); }); /* |-------------------------------------------------------------------------- | INDEX PAGE |-------------------------------------------------------------------------- */ test('leave request index page displays leave requests', function () { $user = User::factory()->create(); $this->actingAs($user); LeaveRequest::factory()->count(3)->create(); $response = $this->get(route('admin.hr.leave-requests.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/hr/leave-request/index') ->has('leaveRequests', 3) ); }); test('index page works when no leave requests exist', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->get(route('admin.hr.leave-requests.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/hr/leave-request/index') ->has('leaveRequests', 0) ); }); test('index page displays correct count after delete', function () { $user = User::factory()->create(); $this->actingAs($user); LeaveRequest::factory()->count(2)->create(); $leaveRequest = LeaveRequest::first(); $this->delete(route('admin.hr.leave-requests.destroy', $leaveRequest)); $response = $this->get(route('admin.hr.leave-requests.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/hr/leave-request/index') ->has('leaveRequests', 1) ); }); /* |-------------------------------------------------------------------------- | FILTER - STATUS |-------------------------------------------------------------------------- */ test('index can filter by status pending', function () { $user = User::factory()->create(); $this->actingAs($user); LeaveRequest::factory()->count(2)->create(['status' => LeaveRequestStatus::PENDING]); LeaveRequest::factory()->create(['status' => LeaveRequestStatus::APPROVED]); LeaveRequest::factory()->create(['status' => LeaveRequestStatus::REJECTED]); $response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'pending'])); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/hr/leave-request/index') ->has('leaveRequests', 2) ); }); test('index can filter by status approved', function () { $user = User::factory()->create(); $this->actingAs($user); LeaveRequest::factory()->create(['status' => LeaveRequestStatus::PENDING]); LeaveRequest::factory()->count(2)->create(['status' => LeaveRequestStatus::APPROVED]); LeaveRequest::factory()->create(['status' => LeaveRequestStatus::REJECTED]); $response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'approved'])); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/hr/leave-request/index') ->has('leaveRequests', 2) ); }); test('index can filter by status rejected', function () { $user = User::factory()->create(); $this->actingAs($user); LeaveRequest::factory()->create(['status' => LeaveRequestStatus::PENDING]); LeaveRequest::factory()->create(['status' => LeaveRequestStatus::APPROVED]); LeaveRequest::factory()->count(2)->create(['status' => LeaveRequestStatus::REJECTED]); $response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'rejected'])); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/hr/leave-request/index') ->has('leaveRequests', 2) ); }); test('index can filter by status cancelled', function () { $user = User::factory()->create(); $this->actingAs($user); LeaveRequest::factory()->create(['status' => LeaveRequestStatus::PENDING]); LeaveRequest::factory()->create(['status' => LeaveRequestStatus::APPROVED]); LeaveRequest::factory()->count(2)->create(['status' => LeaveRequestStatus::CANCELLED]); $response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'cancelled'])); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/hr/leave-request/index') ->has('leaveRequests', 2) ); }); test('index without status filter shows all leave requests', function () { $user = User::factory()->create(); $this->actingAs($user); LeaveRequest::factory()->create(['status' => LeaveRequestStatus::PENDING]); LeaveRequest::factory()->create(['status' => LeaveRequestStatus::APPROVED]); LeaveRequest::factory()->create(['status' => LeaveRequestStatus::REJECTED]); LeaveRequest::factory()->create(['status' => LeaveRequestStatus::CANCELLED]); $response = $this->get(route('admin.hr.leave-requests.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/hr/leave-request/index') ->has('leaveRequests', 4) ); }); test('index passes status filter to view', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'approved'])); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->where('filters.status', 'approved') ); }); test('index passes empty filters when no status specified', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->get(route('admin.hr.leave-requests.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->where('filters', []) ); }); test('index with invalid status filter shows no leave requests', function () { $user = User::factory()->create(); $this->actingAs($user); LeaveRequest::factory()->create(['status' => LeaveRequestStatus::PENDING]); $response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'invalid_status'])); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/hr/leave-request/index') ->has('leaveRequests', 0) ); }); /* |-------------------------------------------------------------------------- | STORE |-------------------------------------------------------------------------- */ test('leave request can be created', function () { $user = User::factory()->create(); $this->actingAs($user); $employee = Employee::factory()->create(['user_id' => $user->id]); $response = $this->post(route('admin.hr.leave-requests.store'), [ 'start_date' => now()->addDays(1)->format('Y-m-d'), 'end_date' => now()->addDays(3)->format('Y-m-d'), ]); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.hr.leave-requests.index')); $this->assertDatabaseCount('leave_requests', 1); $this->assertDatabaseHas('leave_requests', [ 'employee_id' => $employee->id, 'status' => LeaveRequestStatus::PENDING->value, 'total_days' => 3, ]); }); test('leave request creates with correct total_days for single day', function () { $user = User::factory()->create(); $this->actingAs($user); Employee::factory()->create(['user_id' => $user->id]); $this->post(route('admin.hr.leave-requests.store'), [ 'start_date' => now()->addDays(1)->format('Y-m-d'), 'end_date' => now()->addDays(1)->format('Y-m-d'), ]); $this->assertDatabaseHas('leave_requests', [ 'total_days' => 1, ]); }); test('leave request creates with correct total_days for multi day', function () { $user = User::factory()->create(); $this->actingAs($user); Employee::factory()->create(['user_id' => $user->id]); $this->post(route('admin.hr.leave-requests.store'), [ 'start_date' => now()->addDays(1)->format('Y-m-d'), 'end_date' => now()->addDays(5)->format('Y-m-d'), ]); $this->assertDatabaseHas('leave_requests', [ 'total_days' => 5, ]); }); test('leave request always creates with pending status', function () { $user = User::factory()->create(); $this->actingAs($user); Employee::factory()->create(['user_id' => $user->id]); $this->post(route('admin.hr.leave-requests.store'), [ 'start_date' => now()->addDays(1)->format('Y-m-d'), 'end_date' => now()->addDays(3)->format('Y-m-d'), ]); $this->assertDatabaseHas('leave_requests', [ 'status' => LeaveRequestStatus::PENDING->value, ]); }); test('store flashes success toast via Inertia', function () { $user = User::factory()->create(); $this->actingAs($user); Employee::factory()->create(['user_id' => $user->id]); $response = $this->post(route('admin.hr.leave-requests.store'), [ 'start_date' => now()->addDays(1)->format('Y-m-d'), 'end_date' => now()->addDays(3)->format('Y-m-d'), ]); $response->assertRedirect(); }); /* |-------------------------------------------------------------------------- | STORE VALIDATION |-------------------------------------------------------------------------- */ test('leave request start_date is required', function () { $user = User::factory()->create(); $this->actingAs($user); Employee::factory()->create(['user_id' => $user->id]); $response = $this->post(route('admin.hr.leave-requests.store'), [ 'start_date' => '', 'end_date' => now()->addDays(3)->format('Y-m-d'), ]); $response->assertSessionHasErrors('start_date'); }); test('leave request start_date must be valid date', function () { $user = User::factory()->create(); $this->actingAs($user); Employee::factory()->create(['user_id' => $user->id]); $response = $this->post(route('admin.hr.leave-requests.store'), [ 'start_date' => 'not-a-date', 'end_date' => now()->addDays(3)->format('Y-m-d'), ]); $response->assertSessionHasErrors('start_date'); }); test('leave request end_date is required', function () { $user = User::factory()->create(); $this->actingAs($user); Employee::factory()->create(['user_id' => $user->id]); $response = $this->post(route('admin.hr.leave-requests.store'), [ 'start_date' => now()->addDays(1)->format('Y-m-d'), 'end_date' => '', ]); $response->assertSessionHasErrors('end_date'); }); test('leave request end_date must be valid date', function () { $user = User::factory()->create(); $this->actingAs($user); Employee::factory()->create(['user_id' => $user->id]); $response = $this->post(route('admin.hr.leave-requests.store'), [ 'start_date' => now()->addDays(1)->format('Y-m-d'), 'end_date' => 'not-a-date', ]); $response->assertSessionHasErrors('end_date'); }); test('leave request end_date must be after or equal start_date', function () { $user = User::factory()->create(); $this->actingAs($user); Employee::factory()->create(['user_id' => $user->id]); $response = $this->post(route('admin.hr.leave-requests.store'), [ 'start_date' => now()->addDays(5)->format('Y-m-d'), 'end_date' => now()->addDays(3)->format('Y-m-d'), ]); $response->assertSessionHasErrors('end_date'); }); test('leave request end_date equal to start_date is accepted', function () { $user = User::factory()->create(); $this->actingAs($user); Employee::factory()->create(['user_id' => $user->id]); $date = now()->addDays(1)->format('Y-m-d'); $response = $this->post(route('admin.hr.leave-requests.store'), [ 'start_date' => $date, 'end_date' => $date, ]); $response->assertSessionHasNoErrors(); }); test('leave request with past start_date is rejected', function () { $user = User::factory()->create(); $this->actingAs($user); Employee::factory()->create(['user_id' => $user->id]); $response = $this->post(route('admin.hr.leave-requests.store'), [ 'start_date' => now()->subDay()->format('Y-m-d'), 'end_date' => now()->addDays(3)->format('Y-m-d'), ]); $response->assertSessionHasErrors('start_date'); }); test('guest cannot create leave request', function () { $response = $this->post(route('admin.hr.leave-requests.store'), [ 'start_date' => now()->addDays(1)->format('Y-m-d'), 'end_date' => now()->addDays(3)->format('Y-m-d'), ]); $response->assertRedirect(route('login')); $this->assertDatabaseCount('leave_requests', 0); }); test('user tries to create leave request without submitting any data', function () { $user = User::factory()->create(); $this->actingAs($user); Employee::factory()->create(['user_id' => $user->id]); $response = $this->post(route('admin.hr.leave-requests.store'), []); $response->assertSessionHasErrors(['start_date', 'end_date']); }); /* |-------------------------------------------------------------------------- | UPDATE |-------------------------------------------------------------------------- */ test('leave request can be updated', function () { $user = User::factory()->create(); $this->actingAs($user); $employee = Employee::factory()->create(['user_id' => $user->id]); $leaveRequest = LeaveRequest::factory()->create([ 'employee_id' => $employee->id, 'start_date' => now()->addDays(1)->format('Y-m-d'), 'end_date' => now()->addDays(3)->format('Y-m-d'), 'total_days' => 3, ]); $response = $this->put(route('admin.hr.leave-requests.update', $leaveRequest), [ 'start_date' => now()->addDays(5)->format('Y-m-d'), 'end_date' => now()->addDays(7)->format('Y-m-d'), ]); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.hr.leave-requests.index')); $leaveRequest->refresh(); expect($leaveRequest->total_days)->toBe(3); }); test('update recalculates total_days correctly', function () { $user = User::factory()->create(); $this->actingAs($user); $employee = Employee::factory()->create(['user_id' => $user->id]); $leaveRequest = LeaveRequest::factory()->create([ 'employee_id' => $employee->id, 'start_date' => now()->addDays(1)->format('Y-m-d'), 'end_date' => now()->addDays(3)->format('Y-m-d'), 'total_days' => 3, ]); $this->put(route('admin.hr.leave-requests.update', $leaveRequest), [ 'start_date' => now()->addDays(10)->format('Y-m-d'), 'end_date' => now()->addDays(12)->format('Y-m-d'), ]); $leaveRequest->refresh(); expect($leaveRequest->total_days)->toBe(3); }); test('update flashes success toast via Inertia', function () { $user = User::factory()->create(); $this->actingAs($user); $leaveRequest = LeaveRequest::factory()->create(); $response = $this->put(route('admin.hr.leave-requests.update', $leaveRequest), [ 'start_date' => now()->addDays(5)->format('Y-m-d'), 'end_date' => now()->addDays(7)->format('Y-m-d'), ]); $response->assertRedirect(); }); test('updating non-existent leave request returns 404', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.hr.leave-requests.update', 999999), [ 'start_date' => now()->addDays(5)->format('Y-m-d'), 'end_date' => now()->addDays(7)->format('Y-m-d'), ]); $response->assertStatus(404); }); test('guest cannot update leave request', function () { $leaveRequest = LeaveRequest::factory()->create(); $response = $this->put(route('admin.hr.leave-requests.update', $leaveRequest), [ 'start_date' => now()->addDays(5)->format('Y-m-d'), 'end_date' => now()->addDays(7)->format('Y-m-d'), ]); $response->assertRedirect(route('login')); }); /* |-------------------------------------------------------------------------- | DELETE |-------------------------------------------------------------------------- */ test('leave request can be deleted', function () { $user = User::factory()->create(); $this->actingAs($user); $leaveRequest = LeaveRequest::factory()->create(); $response = $this->delete(route('admin.hr.leave-requests.destroy', $leaveRequest)); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.hr.leave-requests.index')); $this->assertSoftDeleted('leave_requests', ['id' => $leaveRequest->id]); }); test('delete flashes success toast via Inertia', function () { $user = User::factory()->create(); $this->actingAs($user); $leaveRequest = LeaveRequest::factory()->create(); $response = $this->delete(route('admin.hr.leave-requests.destroy', $leaveRequest)); $response->assertRedirect(); }); test('deleting non-existent leave request returns 404', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->delete(route('admin.hr.leave-requests.destroy', 999999)); $response->assertStatus(404); }); test('guest cannot delete leave request', function () { $leaveRequest = LeaveRequest::factory()->create(); $response = $this->delete(route('admin.hr.leave-requests.destroy', $leaveRequest)); $response->assertRedirect(route('login')); $this->assertDatabaseCount('leave_requests', 1); }); /* |-------------------------------------------------------------------------- | APPROVE |-------------------------------------------------------------------------- */ test('pending leave request can be approved', function () { $user = User::factory()->create(); $this->actingAs($user); $leaveRequest = LeaveRequest::factory()->create([ 'status' => LeaveRequestStatus::PENDING, ]); $response = $this->post(route('admin.hr.leave-requests.approve', $leaveRequest)); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.hr.leave-requests.index')); $leaveRequest->refresh(); expect($leaveRequest->status)->toBe(LeaveRequestStatus::APPROVED); expect($leaveRequest->verified_by_id)->toBe($user->id); expect($leaveRequest->verified_at)->not->toBeNull(); }); test('approve flashes success toast via Inertia', function () { $user = User::factory()->create(); $this->actingAs($user); $leaveRequest = LeaveRequest::factory()->create([ 'status' => LeaveRequestStatus::PENDING, ]); $response = $this->post(route('admin.hr.leave-requests.approve', $leaveRequest)); $response->assertRedirect(); }); test('approving non-existent leave request returns 404', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.hr.leave-requests.approve', 999999)); $response->assertStatus(404); }); test('guest cannot approve leave request', function () { $leaveRequest = LeaveRequest::factory()->create([ 'status' => LeaveRequestStatus::PENDING, ]); $response = $this->post(route('admin.hr.leave-requests.approve', $leaveRequest)); $response->assertRedirect(route('login')); $leaveRequest->refresh(); expect($leaveRequest->status)->toBe(LeaveRequestStatus::PENDING); }); /* |-------------------------------------------------------------------------- | REJECT |-------------------------------------------------------------------------- */ test('pending leave request can be rejected', function () { $user = User::factory()->create(); $this->actingAs($user); $leaveRequest = LeaveRequest::factory()->create([ 'status' => LeaveRequestStatus::PENDING, ]); $response = $this->post(route('admin.hr.leave-requests.reject', $leaveRequest)); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.hr.leave-requests.index')); $leaveRequest->refresh(); expect($leaveRequest->status)->toBe(LeaveRequestStatus::REJECTED); expect($leaveRequest->verified_by_id)->toBe($user->id); expect($leaveRequest->verified_at)->not->toBeNull(); }); test('reject flashes success toast via Inertia', function () { $user = User::factory()->create(); $this->actingAs($user); $leaveRequest = LeaveRequest::factory()->create([ 'status' => LeaveRequestStatus::PENDING, ]); $response = $this->post(route('admin.hr.leave-requests.reject', $leaveRequest)); $response->assertRedirect(); }); test('rejecting non-existent leave request returns 404', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.hr.leave-requests.reject', 999999)); $response->assertStatus(404); }); test('guest cannot reject leave request', function () { $leaveRequest = LeaveRequest::factory()->create([ 'status' => LeaveRequestStatus::PENDING, ]); $response = $this->post(route('admin.hr.leave-requests.reject', $leaveRequest)); $response->assertRedirect(route('login')); $leaveRequest->refresh(); expect($leaveRequest->status)->toBe(LeaveRequestStatus::PENDING); }); /* |-------------------------------------------------------------------------- | DATA INTEGRITY |-------------------------------------------------------------------------- */ test('created leave request has correct timestamps', function () { $user = User::factory()->create(); $this->actingAs($user); Employee::factory()->create(['user_id' => $user->id]); $this->post(route('admin.hr.leave-requests.store'), [ 'start_date' => now()->addDays(1)->format('Y-m-d'), 'end_date' => now()->addDays(3)->format('Y-m-d'), ]); $leaveRequest = LeaveRequest::first(); expect($leaveRequest->created_at)->not->toBeNull(); expect($leaveRequest->updated_at)->not->toBeNull(); }); test('leave request factory creates valid leave request', function () { $leaveRequest = LeaveRequest::factory()->create(); expect($leaveRequest->employee)->not->toBeNull(); expect($leaveRequest->start_date)->not->toBeNull(); expect($leaveRequest->end_date)->not->toBeNull(); expect($leaveRequest->total_days)->toBeGreaterThanOrEqual(1); expect($leaveRequest->status)->toBeInstanceOf(LeaveRequestStatus::class); }); test('approved leave request has verified_by_id and verified_at', function () { $user = User::factory()->create(); $this->actingAs($user); $leaveRequest = LeaveRequest::factory()->create([ 'status' => LeaveRequestStatus::PENDING, ]); $this->post(route('admin.hr.leave-requests.approve', $leaveRequest)); $leaveRequest->refresh(); expect($leaveRequest->verified_by_id)->toBe($user->id); expect($leaveRequest->verified_at)->not->toBeNull(); }); test('rejected leave request has verified_by_id and verified_at', function () { $user = User::factory()->create(); $this->actingAs($user); $leaveRequest = LeaveRequest::factory()->create([ 'status' => LeaveRequestStatus::PENDING, ]); $this->post(route('admin.hr.leave-requests.reject', $leaveRequest)); $leaveRequest->refresh(); expect($leaveRequest->verified_by_id)->toBe($user->id); expect($leaveRequest->verified_at)->not->toBeNull(); }); /* |-------------------------------------------------------------------------- | REALISTIC USER SCENARIOS |-------------------------------------------------------------------------- */ test('user creates leave request then immediately edits it', function () { $user = User::factory()->create(); $this->actingAs($user); Employee::factory()->create(['user_id' => $user->id]); $this->post(route('admin.hr.leave-requests.store'), [ 'start_date' => now()->addDays(1)->format('Y-m-d'), 'end_date' => now()->addDays(3)->format('Y-m-d'), ]); $leaveRequest = LeaveRequest::first(); $this->put(route('admin.hr.leave-requests.update', $leaveRequest), [ 'start_date' => now()->addDays(5)->format('Y-m-d'), 'end_date' => now()->addDays(7)->format('Y-m-d'), ]); $leaveRequest->refresh(); expect($leaveRequest->start_date->format('Y-m-d'))->toBe(now()->addDays(5)->format('Y-m-d')); expect($leaveRequest->end_date->format('Y-m-d'))->toBe(now()->addDays(7)->format('Y-m-d')); expect($leaveRequest->total_days)->toBe(3); }); test('user creates multiple leave requests and deletes one', function () { $user = User::factory()->create(); $this->actingAs($user); Employee::factory()->create(['user_id' => $user->id]); $this->post(route('admin.hr.leave-requests.store'), [ 'start_date' => now()->addDays(1)->format('Y-m-d'), 'end_date' => now()->addDays(3)->format('Y-m-d'), ]); $this->post(route('admin.hr.leave-requests.store'), [ 'start_date' => now()->addDays(10)->format('Y-m-d'), 'end_date' => now()->addDays(12)->format('Y-m-d'), ]); $toDelete = LeaveRequest::first(); $this->delete(route('admin.hr.leave-requests.destroy', $toDelete)); $response = $this->get(route('admin.hr.leave-requests.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/hr/leave-request/index') ->has('leaveRequests', 1) ); }); test('user approves a leave request and it no longer shows as pending', function () { $user = User::factory()->create(); $this->actingAs($user); LeaveRequest::factory()->create(['status' => LeaveRequestStatus::PENDING]); $pendingCount = LeaveRequest::where('status', LeaveRequestStatus::PENDING)->count(); expect($pendingCount)->toBe(1); $leaveRequest = LeaveRequest::first(); $this->post(route('admin.hr.leave-requests.approve', $leaveRequest)); $pendingCount = LeaveRequest::where('status', LeaveRequestStatus::PENDING)->count(); expect($pendingCount)->toBe(0); $approvedCount = LeaveRequest::where('status', LeaveRequestStatus::APPROVED)->count(); expect($approvedCount)->toBe(1); }); test('user filters by pending then approves a request', function () { $user = User::factory()->create(); $this->actingAs($user); LeaveRequest::factory()->count(2)->create(['status' => LeaveRequestStatus::PENDING]); LeaveRequest::factory()->create(['status' => LeaveRequestStatus::APPROVED]); $response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'pending'])); $response->assertInertia(fn (Assert $page) => $page ->has('leaveRequests', 2) ); $leaveRequest = LeaveRequest::where('status', LeaveRequestStatus::PENDING)->first(); $this->post(route('admin.hr.leave-requests.approve', $leaveRequest)); $response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'pending'])); $response->assertInertia(fn (Assert $page) => $page ->has('leaveRequests', 1) ); $response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'approved'])); $response->assertInertia(fn (Assert $page) => $page ->has('leaveRequests', 2) ); });