dstpabuaran.com/tests/Feature/Admin/HR/AttendanceTest.php
Yoga Pangestu 2a3e70b78d Refactor role management and pagination in admin panel
- Updated RoleIndex component to handle pagination, sorting, and searching for roles.
- Adjusted data structure for roles to include pagination details.
- Enhanced tests for various admin features (Finance, HR, Master) to validate pagination and data structure.
- Ensured all relevant tests check for data structure consistency, including total counts and pagination details.
2026-08-01 02:54:24 +07:00

752 lines
23 KiB
PHP

<?php
use App\Models\Attendance;
use App\Models\Employee;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Inertia\Testing\AssertableInertia as Assert;
uses(RefreshDatabase::class);
/*
|--------------------------------------------------------------------------
| AUTHENTICATION
|--------------------------------------------------------------------------
*/
test('guests are redirected to the login page', function () {
$response = $this->get(route('admin.hr.attendances.index'));
$response->assertRedirect(route('login'));
});
test('authenticated users can visit the attendance index page', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->get(route('admin.hr.attendances.index'));
$response->assertOk();
});
/*
|--------------------------------------------------------------------------
| INDEX PAGE
|--------------------------------------------------------------------------
*/
test('index page displays attendances', function () {
$user = User::factory()->create();
$this->actingAs($user);
$employeeUser = User::factory()->create();
$employeeUser->employee()->create([
'join_date' => '2024-01-01',
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
Attendance::factory()->create([
'employee_id' => $employeeUser->employee->id,
'attendance_date' => now()->toDateString(),
'check_in_at' => now()->setHour(8)->setMinute(0),
'check_out_at' => now()->setHour(17)->setMinute(0),
]);
$response = $this->get(route('admin.hr.attendances.index'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/attendance/index')
->has('attendances.data', 1)
->where('attendances.total', 1)
->where('attendances.current_page', 1)
->where('attendances.per_page', 25)
);
});
test('index page works with zero attendances', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->get(route('admin.hr.attendances.index'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/attendance/index')
->has('attendances.data', 0)
->where('attendances.total', 0)
->where('attendances.current_page', 1)
->where('attendances.per_page', 25)
);
});
test('index page passes current year and month', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->get(route('admin.hr.attendances.index'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->where('currentYear', now()->year)
->where('currentMonth', now()->month)
);
});
test('index page can filter by year and month', function () {
$user = User::factory()->create();
$this->actingAs($user);
$employeeUser = User::factory()->create();
$employeeUser->employee()->create([
'join_date' => '2024-01-01',
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
Attendance::factory()->create([
'employee_id' => $employeeUser->employee->id,
'attendance_date' => '2024-03-15',
'check_in_at' => '2024-03-15 08:00:00',
'check_out_at' => '2024-03-15 17:00:00',
]);
$response = $this->get(route('admin.hr.attendances.index', ['year' => 2024, 'month' => 3]));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->where('currentYear', 2024)
->where('currentMonth', 3)
->has('attendances.data', 1)
->where('attendances.total', 1)
->where('attendances.current_page', 1)
->where('attendances.per_page', 25)
);
});
test('index page excludes attendances from other months', function () {
$user = User::factory()->create();
$this->actingAs($user);
$employeeUser = User::factory()->create();
$employeeUser->employee()->create([
'join_date' => '2024-01-01',
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
Attendance::factory()->create([
'employee_id' => $employeeUser->employee->id,
'attendance_date' => '2024-03-15',
'check_in_at' => '2024-03-15 08:00:00',
]);
$response = $this->get(route('admin.hr.attendances.index', ['year' => 2024, 'month' => 4]));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->has('attendances.data', 0)
->where('attendances.total', 0)
->where('attendances.current_page', 1)
->where('attendances.per_page', 25)
);
});
test('index page passes hrSettings props', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->get(route('admin.hr.attendances.index'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->has('hrSettings.scheduled_check_in_time')
->has('hrSettings.scheduled_check_out_time')
);
});
test('index page passes monthStats', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->get(route('admin.hr.attendances.index'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->has('monthStats.working_days')
->has('monthStats.present')
->has('monthStats.absent')
->has('monthStats.leave')
);
});
test('index page passes todayAttendance as null when no attendance today', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->get(route('admin.hr.attendances.index'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->where('todayAttendance', null)
);
});
/*
|--------------------------------------------------------------------------
| STORE / CHECK-IN
|--------------------------------------------------------------------------
*/
test('guest cannot check in', function () {
$response = $this->post(route('admin.hr.attendances.store'), [
'photo' => 'data:image/jpeg;base64,'.base64_encode('fake-image'),
'latitude' => -6.2088,
'longitude' => 106.8456,
]);
$response->assertRedirect(route('login'));
});
test('authenticated user without employee cannot check in', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.hr.attendances.store'), [
'photo' => 'data:image/jpeg;base64,'.base64_encode('fake-image'),
'latitude' => -6.2088,
'longitude' => 106.8456,
]);
$response->assertRedirect();
});
test('employee can check in successfully', function () {
$user = User::factory()->create();
$this->actingAs($user);
$employee = Employee::factory()->create([
'user_id' => $user->id,
'join_date' => now()->subMonth()->toDateString(),
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
$response = $this->post(route('admin.hr.attendances.store'), [
'photo' => 'data:image/jpeg;base64,'.base64_encode('fake-image'),
'latitude' => -6.2088,
'longitude' => 106.8456,
]);
$response->assertRedirect();
$this->assertDatabaseHas('attendances', [
'employee_id' => $employee->id,
'attendance_date' => now()->toDateString(),
]);
});
test('employee cannot check in twice on the same day', function () {
$user = User::factory()->create();
$this->actingAs($user);
$employee = Employee::factory()->create([
'user_id' => $user->id,
'join_date' => now()->subMonth()->toDateString(),
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
Attendance::factory()->create([
'employee_id' => $employee->id,
'attendance_date' => now()->toDateString(),
'check_in_at' => now(),
]);
$response = $this->post(route('admin.hr.attendances.store'), [
'photo' => 'data:image/jpeg;base64,'.base64_encode('fake-image'),
'latitude' => -6.2088,
'longitude' => 106.8456,
]);
$response->assertRedirect();
$this->assertDatabaseCount('attendances', 1);
});
test('check in requires photo', function () {
$user = User::factory()->create();
$this->actingAs($user);
Employee::factory()->create([
'user_id' => $user->id,
'join_date' => now()->subMonth()->toDateString(),
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
$response = $this->post(route('admin.hr.attendances.store'), [
'latitude' => -6.2088,
'longitude' => 106.8456,
]);
$response->assertRedirect();
$this->assertDatabaseCount('attendances', 0);
});
test('check in requires latitude', function () {
$user = User::factory()->create();
$this->actingAs($user);
Employee::factory()->create([
'user_id' => $user->id,
'join_date' => now()->subMonth()->toDateString(),
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
$response = $this->post(route('admin.hr.attendances.store'), [
'photo' => 'data:image/jpeg;base64,'.base64_encode('fake-image'),
'longitude' => 106.8456,
]);
$response->assertRedirect();
$this->assertDatabaseCount('attendances', 0);
});
test('check in requires longitude', function () {
$user = User::factory()->create();
$this->actingAs($user);
Employee::factory()->create([
'user_id' => $user->id,
'join_date' => now()->subMonth()->toDateString(),
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
$response = $this->post(route('admin.hr.attendances.store'), [
'photo' => 'data:image/jpeg;base64,'.base64_encode('fake-image'),
'latitude' => -6.2088,
]);
$response->assertRedirect();
$this->assertDatabaseCount('attendances', 0);
});
test('check in latitude must be numeric', function () {
$user = User::factory()->create();
$this->actingAs($user);
Employee::factory()->create([
'user_id' => $user->id,
'join_date' => now()->subMonth()->toDateString(),
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
$response = $this->post(route('admin.hr.attendances.store'), [
'photo' => 'data:image/jpeg;base64,'.base64_encode('fake-image'),
'latitude' => 'not-a-number',
'longitude' => 106.8456,
]);
$response->assertRedirect();
$this->assertDatabaseCount('attendances', 0);
});
test('check in longitude must be numeric', function () {
$user = User::factory()->create();
$this->actingAs($user);
Employee::factory()->create([
'user_id' => $user->id,
'join_date' => now()->subMonth()->toDateString(),
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
$response = $this->post(route('admin.hr.attendances.store'), [
'photo' => 'data:image/jpeg;base64,'.base64_encode('fake-image'),
'latitude' => -6.2088,
'longitude' => 'not-a-number',
]);
$response->assertRedirect();
$this->assertDatabaseCount('attendances', 0);
});
/*
|--------------------------------------------------------------------------
| UPDATE / CHECK-OUT
|--------------------------------------------------------------------------
*/
test('guest cannot check out', function () {
$attendance = Attendance::factory()->create([
'check_in_at' => now()->subHours(8),
'check_out_at' => null,
]);
$response = $this->put(route('admin.hr.attendances.update', $attendance), [
'photo' => 'data:image/jpeg;base64,'.base64_encode('fake-image'),
'latitude' => -6.2088,
'longitude' => 106.8456,
]);
$response->assertRedirect(route('login'));
});
test('employee can check out successfully', function () {
$user = User::factory()->create();
$this->actingAs($user);
$employee = Employee::factory()->create([
'user_id' => $user->id,
'join_date' => now()->subMonth()->toDateString(),
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
$attendance = Attendance::factory()->create([
'employee_id' => $employee->id,
'attendance_date' => now()->toDateString(),
'check_in_at' => now()->subHours(8),
'check_out_at' => null,
]);
$response = $this->put(route('admin.hr.attendances.update', $attendance), [
'photo' => 'data:image/jpeg;base64,'.base64_encode('fake-image'),
'latitude' => -6.2088,
'longitude' => 106.8456,
]);
$response->assertRedirect();
$attendance->refresh();
$this->assertNotNull($attendance->check_out_at);
$this->assertEquals(-6.2088, $attendance->check_out_latitude);
$this->assertEquals(106.8456, $attendance->check_out_longitude);
});
test('check out requires photo', function () {
$user = User::factory()->create();
$this->actingAs($user);
$employee = Employee::factory()->create([
'user_id' => $user->id,
'join_date' => now()->subMonth()->toDateString(),
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
$attendance = Attendance::factory()->create([
'employee_id' => $employee->id,
'attendance_date' => now()->toDateString(),
'check_in_at' => now()->subHours(8),
'check_out_at' => null,
]);
$response = $this->put(route('admin.hr.attendances.update', $attendance), [
'latitude' => -6.2088,
'longitude' => 106.8456,
]);
$response->assertRedirect();
$attendance->refresh();
$this->assertNull($attendance->check_out_at);
});
test('check out requires latitude', function () {
$user = User::factory()->create();
$this->actingAs($user);
$employee = Employee::factory()->create([
'user_id' => $user->id,
'join_date' => now()->subMonth()->toDateString(),
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
$attendance = Attendance::factory()->create([
'employee_id' => $employee->id,
'attendance_date' => now()->toDateString(),
'check_in_at' => now()->subHours(8),
'check_out_at' => null,
]);
$response = $this->put(route('admin.hr.attendances.update', $attendance), [
'photo' => 'data:image/jpeg;base64,'.base64_encode('fake-image'),
'longitude' => 106.8456,
]);
$response->assertRedirect();
$attendance->refresh();
$this->assertNull($attendance->check_out_at);
});
test('check out requires longitude', function () {
$user = User::factory()->create();
$this->actingAs($user);
$employee = Employee::factory()->create([
'user_id' => $user->id,
'join_date' => now()->subMonth()->toDateString(),
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
$attendance = Attendance::factory()->create([
'employee_id' => $employee->id,
'attendance_date' => now()->toDateString(),
'check_in_at' => now()->subHours(8),
'check_out_at' => null,
]);
$response = $this->put(route('admin.hr.attendances.update', $attendance), [
'photo' => 'data:image/jpeg;base64,'.base64_encode('fake-image'),
'latitude' => -6.2088,
]);
$response->assertRedirect();
$attendance->refresh();
$this->assertNull($attendance->check_out_at);
});
test('updating non-existent attendance returns 404', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->put(route('admin.hr.attendances.update', 999999), [
'photo' => 'data:image/jpeg;base64,'.base64_encode('fake-image'),
'latitude' => -6.2088,
'longitude' => 106.8456,
]);
$response->assertStatus(404);
});
/*
|--------------------------------------------------------------------------
| BY-DATE
|--------------------------------------------------------------------------
*/
test('guest cannot access by-date endpoint', function () {
$response = $this->get(route('admin.hr.attendances.by-date'));
$response->assertRedirect(route('login'));
});
test('by-date returns empty when no attendance for date', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->getJson(route('admin.hr.attendances.by-date', ['date' => '2024-01-01']));
$response->assertOk();
});
test('by-date defaults to today when no date parameter', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->getJson(route('admin.hr.attendances.by-date'));
$response->assertOk();
});
/*
|--------------------------------------------------------------------------
| MONTH STATS
|--------------------------------------------------------------------------
*/
test('month stats calculates correct working days for january 2024', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->get(route('admin.hr.attendances.index', ['year' => 2024, 'month' => 1]));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->where('monthStats.working_days', 23)
);
});
test('month stats present count matches attendance records', function () {
$user = User::factory()->create();
$this->actingAs($user);
$employeeUser = User::factory()->create();
$employeeUser->employee()->create([
'join_date' => '2024-01-01',
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
Attendance::factory()->create([
'employee_id' => $employeeUser->employee->id,
'attendance_date' => '2024-01-15',
'check_in_at' => '2024-01-15 08:00:00',
]);
Attendance::factory()->create([
'employee_id' => $employeeUser->employee->id,
'attendance_date' => '2024-01-16',
'check_in_at' => '2024-01-16 08:00:00',
]);
$response = $this->get(route('admin.hr.attendances.index', ['year' => 2024, 'month' => 1]));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->where('monthStats.present', 2)
);
});
/*
|--------------------------------------------------------------------------
| MULTIPLE ATTENDANCES
|--------------------------------------------------------------------------
*/
test('multiple employees can have attendances on the same day', function () {
$user = User::factory()->create();
$this->actingAs($user);
$emp1User = User::factory()->create();
$emp1User->employee()->create([
'join_date' => '2024-01-01',
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
$emp2User = User::factory()->create();
$emp2User->employee()->create([
'join_date' => '2024-01-01',
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
Attendance::factory()->create([
'employee_id' => $emp1User->employee->id,
'attendance_date' => '2024-01-15',
'check_in_at' => '2024-01-15 08:00:00',
]);
Attendance::factory()->create([
'employee_id' => $emp2User->employee->id,
'attendance_date' => '2024-01-15',
'check_in_at' => '2024-01-15 08:30:00',
]);
$response = $this->get(route('admin.hr.attendances.index', ['year' => 2024, 'month' => 1]));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->has('attendances.data', 2)
->where('attendances.total', 2)
->where('attendances.current_page', 1)
->where('attendances.per_page', 25)
);
});
test('employee can have multiple attendances across different days', function () {
$user = User::factory()->create();
$this->actingAs($user);
$employeeUser = User::factory()->create();
$employeeUser->employee()->create([
'join_date' => '2024-01-01',
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
Attendance::factory()->create([
'employee_id' => $employeeUser->employee->id,
'attendance_date' => '2024-01-15',
'check_in_at' => '2024-01-15 08:00:00',
]);
Attendance::factory()->create([
'employee_id' => $employeeUser->employee->id,
'attendance_date' => '2024-01-16',
'check_in_at' => '2024-01-16 08:00:00',
]);
Attendance::factory()->create([
'employee_id' => $employeeUser->employee->id,
'attendance_date' => '2024-01-17',
'check_in_at' => '2024-01-17 08:00:00',
]);
$response = $this->get(route('admin.hr.attendances.index', ['year' => 2024, 'month' => 1]));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->has('attendances.data', 3)
->where('attendances.total', 3)
->where('attendances.current_page', 1)
->where('attendances.per_page', 25)
);
});
/*
|--------------------------------------------------------------------------
| DATA INTEGRITY
|--------------------------------------------------------------------------
*/
test('attendance stores correct check-in coordinates', function () {
$user = User::factory()->create();
$this->actingAs($user);
$employee = Employee::factory()->create([
'user_id' => $user->id,
'join_date' => now()->subMonth()->toDateString(),
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
$this->post(route('admin.hr.attendances.store'), [
'photo' => 'data:image/jpeg;base64,'.base64_encode('fake-image'),
'latitude' => -6.2088,
'longitude' => 106.8456,
]);
$attendance = Attendance::where('employee_id', $employee->id)->first();
$this->assertEquals(-6.2088, $attendance->check_in_latitude);
$this->assertEquals(106.8456, $attendance->check_in_longitude);
});
test('attendance has correct attendance_date', function () {
$user = User::factory()->create();
$this->actingAs($user);
$employee = Employee::factory()->create([
'user_id' => $user->id,
'join_date' => now()->subMonth()->toDateString(),
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
$this->post(route('admin.hr.attendances.store'), [
'photo' => 'data:image/jpeg;base64,'.base64_encode('fake-image'),
'latitude' => -6.2088,
'longitude' => 106.8456,
]);
$this->assertDatabaseHas('attendances', [
'employee_id' => $employee->id,
'attendance_date' => now()->toDateString(),
]);
});
test('check-in sets check_out_at to null', function () {
$user = User::factory()->create();
$this->actingAs($user);
$employee = Employee::factory()->create([
'user_id' => $user->id,
'join_date' => now()->subMonth()->toDateString(),
'employment_status' => 'full_time',
'base_salary' => 5000000,
]);
$this->post(route('admin.hr.attendances.store'), [
'photo' => 'data:image/jpeg;base64,'.base64_encode('fake-image'),
'latitude' => -6.2088,
'longitude' => 106.8456,
]);
$attendance = Attendance::where('employee_id', $employee->id)->first();
$this->assertNull($attendance->check_out_at);
});