Files
HRM-System/tests/Unit/AttendanceUnificationTest.php

161 lines
6.0 KiB
PHP

<?php
use App\Models\User;
use App\Models\Employee;
use App\Models\AttendanceRecord;
use App\Models\AttendanceBreak;
use App\Services\AttendanceService;
use Tests\TestCase;
uses(TestCase::class);
test('unified attendance process clock handles full lifecycle across channels', function () {
updateSetting('working_days', json_encode([0,1,2,3,4,5,6]));
$user = User::where('email', 'admin@example.com')->orWhere('type', 'company')->first() ?? User::first();
$employee = Employee::where('user_id', $user->id)->first() ?? Employee::first();
AttendanceRecord::where('employee_id', $user->id)->orWhere('employee_id', $employee->user_id)->delete();
$service = new AttendanceService();
// 1. Web Clock In
$resIn = $service->processClock($user, $employee, [
'action' => 'clock_in',
'source' => 'web',
'latitude' => 14.5995,
'longitude' => 120.9842,
]);
expect($resIn['success'])->toBeTrue();
// 2. Mobile Break In
$resBreakIn = $service->processClock($user, $employee, [
'action' => 'break_in',
'source' => 'mobile',
'latitude' => 14.5995,
'longitude' => 120.9842,
]);
expect($resBreakIn['success'])->toBeTrue();
// 3. Mobile Break Out
$resBreakOut = $service->processClock($user, $employee, [
'action' => 'break_out',
'source' => 'mobile',
'latitude' => 14.5995,
'longitude' => 120.9842,
]);
expect($resBreakOut['success'])->toBeTrue();
// 4. Web Clock Out
$resOut = $service->processClock($user, $employee, [
'action' => 'clock_out',
'source' => 'web',
'latitude' => 14.5995,
'longitude' => 120.9842,
]);
expect($resOut['success'])->toBeTrue();
$rec = AttendanceRecord::where('employee_id', $user->id)->whereDate('date', date('Y-m-d'))->first();
expect($rec)->not->toBeNull();
expect($rec->clock_in_source)->toBe('web');
expect($rec->clock_out_source)->toBe('web');
$breaksCount = AttendanceBreak::where('attendance_record_id', $rec->id)->count();
expect($breaksCount)->toBeGreaterThanOrEqual(1);
});
test('ATT-MOB-WEB-001: mobile clock-in followed by web clock-out creates unified record with source metadata', function () {
updateSetting('working_days', json_encode([0,1,2,3,4,5,6]));
$user = User::where('email', 'admin@example.com')->orWhere('type', 'company')->first() ?? User::first();
$employee = Employee::where('user_id', $user->id)->first() ?? Employee::first();
AttendanceRecord::where('employee_id', $user->id)->orWhere('employee_id', $employee->user_id)->delete();
$service = new AttendanceService();
// Mobile Clock In
$resIn = $service->processClock($user, $employee, [
'action' => 'clock_in',
'source' => 'mobile',
'latitude' => 14.5995,
'longitude' => 120.9842,
]);
expect($resIn['success'])->toBeTrue();
// Web Clock Out
$resOut = $service->processClock($user, $employee, [
'action' => 'clock_out',
'source' => 'web',
'latitude' => 14.5995,
'longitude' => 120.9842,
]);
expect($resOut['success'])->toBeTrue();
$rec = AttendanceRecord::where('employee_id', $user->id)->whereDate('date', date('Y-m-d'))->first();
expect($rec)->not->toBeNull();
expect($rec->clock_in_source)->toBe('mobile');
expect($rec->clock_out_source)->toBe('web');
expect($rec->shift_id)->not->toBeNull();
});
test('web calendar query payload includes mobile record and clock_in_source', function () {
updateSetting('working_days', json_encode([0,1,2,3,4,5,6]));
$user = User::where('email', 'admin@example.com')->orWhere('type', 'company')->first() ?? User::first();
$employee = Employee::where('user_id', $user->id)->first() ?? Employee::first();
AttendanceRecord::where('employee_id', $user->id)->orWhere('employee_id', $employee->user_id)->delete();
$service = new AttendanceService();
$service->processClock($user, $employee, [
'action' => 'clock_in',
'source' => 'mobile',
'latitude' => 14.5995,
'longitude' => 120.9842,
]);
$controller = new \App\Http\Controllers\AttendanceRecordController();
$request = \Illuminate\Http\Request::create('/hr/attendance-records/calendar', 'GET', [
'month' => date('n'),
'year' => date('Y'),
]);
// Authenticate as user
$this->actingAs($user);
$response = $controller->calendar($request);
expect($response)->not->toBeNull();
});
test('duplicate clock in is rejected with 422', function () {
updateSetting('working_days', json_encode([0,1,2,3,4,5,6]));
$user = User::where('email', 'admin@example.com')->orWhere('type', 'company')->first() ?? User::first();
$employee = Employee::where('user_id', $user->id)->first() ?? Employee::first();
AttendanceRecord::where('employee_id', $user->id)->orWhere('employee_id', $employee->user_id)->delete();
$service = new AttendanceService();
$res1 = $service->processClock($user, $employee, ['action' => 'clock_in', 'source' => 'mobile', 'latitude' => 14.5995, 'longitude' => 120.9842]);
expect($res1['success'])->toBeTrue();
$res2 = $service->processClock($user, $employee, ['action' => 'clock_in', 'source' => 'mobile', 'latitude' => 14.5995, 'longitude' => 120.9842]);
expect($res2['success'])->toBeFalse();
expect($res2['status_code'])->toBe(422);
});
test('clock out without clock in is rejected with 422', function () {
updateSetting('working_days', json_encode([0,1,2,3,4,5,6]));
$user = User::where('email', 'admin@example.com')->orWhere('type', 'company')->first() ?? User::first();
$employee = Employee::where('user_id', $user->id)->first() ?? Employee::first();
AttendanceRecord::where('employee_id', $user->id)->orWhere('employee_id', $employee->user_id)->delete();
$service = new AttendanceService();
$res = $service->processClock($user, $employee, ['action' => 'clock_out', 'source' => 'mobile', 'latitude' => 14.5995, 'longitude' => 120.9842]);
expect($res['success'])->toBeFalse();
expect($res['status_code'])->toBe(422);
});