Files
alrahma_sunday_school/tests/app/Controllers/Api/AttendanceControllerTest.php
T
2026-02-10 22:11:06 -05:00

466 lines
13 KiB
PHP

<?php
namespace Tests\App\Controllers\Api;
use App\Controllers\Api\AttendanceController;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\HTTP\URI;
use CodeIgniter\HTTP\UserAgent;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\Mock\MockIncomingRequest;
use Config\App;
class FakeAttendanceRequest extends MockIncomingRequest
{
public function __construct(private readonly ?array $json = null, private readonly array $gets = [])
{
parent::__construct(config(App::class), new URI('https://test.alrahmaisgl.org'), 'php://input', new UserAgent());
}
public function getJSON(bool $assoc = false, int $depth = 512, int $options = 0): ?array
{
return $this->json;
}
public function getGet($key = null, $filter = null, $default = null)
{
if ($key === null) {
return $this->gets;
}
return $this->gets[$key] ?? $default;
}
}
class StubPager
{
public function __construct(private readonly int $current, private readonly int $perPage, private readonly int $total)
{
}
public function getCurrentPage()
{
return $this->current;
}
public function getPerPage()
{
return $this->perPage;
}
public function getTotal()
{
return $this->total;
}
public function getPageCount()
{
return (int) ceil($this->total / max(1, $this->perPage));
}
}
class StubAttendanceModel
{
public array $whereHistory = [];
public array $findResult = [];
public array $findAllResult = [];
public array $saved = [];
public array $attendanceLookup = [];
public int $insertId = 0;
public array $pagerData = ['current' => 1, 'perPage' => 20, 'total' => 0];
public array $firstQueue = [];
public array $updatedAttendance = [];
public function where(string $column, $value = null): self
{
$this->whereHistory[$column] = $value;
return $this;
}
public function paginate(int $perPage, string $group, int $page)
{
$this->pager = new StubPager($this->pagerData['current'], $this->pagerData['perPage'], $this->pagerData['total']);
return $this->findAllResult;
}
public function find($id)
{
return $this->findResult[$id] ?? null;
}
public function insert(array $data)
{
$this->insertId++;
$this->saved[$this->insertId] = $data;
$this->findResult[$this->insertId] = ['id' => $this->insertId] + $data;
return $this->insertId;
}
public function getInsertID()
{
return $this->insertId;
}
public function update($id, array $data)
{
$this->saved[$id] = $data;
$this->findResult[$id] = ['id' => $id] + $data;
}
public function delete($id)
{
unset($this->findResult[$id]);
}
public function first()
{
return array_shift($this->firstQueue) ?? null;
}
public function queueFirst($row): void
{
$this->firstQueue[] = $row;
}
public function findAll(): array
{
return $this->findAllResult;
}
public function getAttendance(int $studentId, int $classSectionId, string $date): array
{
return $this->attendanceLookup[$studentId] ?? [];
}
public function setAttendanceLookup(int $studentId, array $row): void
{
$this->attendanceLookup[$studentId] = $row;
}
public function updateAttendance(int $id, array $data): bool
{
$this->updatedAttendance[$id] = $data;
$this->findResult[$id] = ['id' => $id] + $data;
return true;
}
public function orderBy(...$args): self
{
return $this;
}
}
class StubStudentClassModel
{
public ?string $grade = null;
public function getStudentGrade(int $studentId)
{
return $this->grade;
}
}
class StubConfigModel
{
public function __construct(private readonly array $values)
{
}
public function getConfig(string $key)
{
return $this->values[$key] ?? null;
}
}
class StubDbTable
{
private array $results = [];
public function __construct(array $rows)
{
$this->results = $rows;
}
public function select(...$args)
{
return $this;
}
public function join(...$args)
{
return $this;
}
public function where(...$args)
{
return $this;
}
public function orderBy(...$args)
{
return $this;
}
public function get()
{
return $this;
}
public function getResultArray()
{
return $this->results;
}
}
class StubDbConnection
{
public function __construct(private readonly array $rows)
{
}
public function table($name)
{
return new StubDbTable($this->rows);
}
}
class TestableAttendanceController extends AttendanceController
{
public bool $validationResult = true;
public array $validationErrors = [];
public $validator;
private ?StubDbConnection $db = null;
public function __construct()
{
}
public function setModels(StubAttendanceModel $attendanceModel, StubStudentClassModel $studentClassModel, StubConfigModel $configModel): void
{
$this->attendanceModel = $attendanceModel;
$this->studentClassModel = $studentClassModel;
$this->configModel = $configModel;
$this->schoolYear = (string) $configModel->getConfig('school_year');
$this->semester = (string) $configModel->getConfig('semester');
}
public function setDatabaseConnection(StubDbConnection $db): void
{
$this->db = $db;
}
public function setValidationResult(bool $result, array $errors = []): void
{
$this->validationResult = $result;
$this->validationErrors = $errors;
}
protected function validate($rules, array $messages = []): bool
{
$this->validator = new class($this->validationErrors) {
private array $errors;
public function __construct(array $errors)
{
$this->errors = $errors;
}
public function getErrors()
{
return $this->errors;
}
};
return $this->validationResult;
}
protected function getDatabaseConnection()
{
return $this->db ?? parent::getDatabaseConnection();
}
protected function respond($data = null, ?int $status = null, string $message = '')
{
return ['data' => $data, 'code' => $status, 'message' => $message];
}
protected function error(string $message = 'An error occurred', int $code = 400, ?array $errors = null)
{
$payload = ['status' => false, 'message' => $message, 'code' => $code];
if (!empty($errors)) {
$payload['errors'] = $errors;
}
return $payload;
}
}
class AttendanceControllerTest extends CIUnitTestCase
{
private TestableAttendanceController $controller;
private StubAttendanceModel $attendanceModel;
private StubStudentClassModel $studentClassModel;
private StubConfigModel $configModel;
protected function setUp(): void
{
parent::setUp();
$this->controller = new TestableAttendanceController();
$this->attendanceModel = new StubAttendanceModel();
$this->studentClassModel = new StubStudentClassModel();
$this->configModel = new StubConfigModel(['school_year' => '2024-2025', 'semester' => 'Spring']);
$this->controller->setModels($this->attendanceModel, $this->studentClassModel, $this->configModel);
}
public function testIndexUsesFilters()
{
$request = new FakeAttendanceRequest(null, ['student_id' => '5', 'date' => '2024-09-01', 'page' => '2', 'per_page' => '10']);
$this->attendanceModel->findAllResult = [['id' => 1]];
$this->attendanceModel->pagerData = ['current' => 2, 'perPage' => 10, 'total' => 30];
$this->controller->setRequest($request);
$response = $this->controller->index();
$this->assertSame('Attendance records retrieved successfully', $response['message']);
$this->assertSame(2, $response['data']['pagination']['current_page']);
$this->assertSame(1, $response['data']['data'][0]['id']);
}
public function testShowNotFound()
{
$response = $this->controller->show(7);
$this->assertSame(ResponseInterface::HTTP_NOT_FOUND, $response['code']);
}
public function testShowSuccess()
{
$this->attendanceModel->findResult[8] = ['id' => 8];
$response = $this->controller->show(8);
$this->assertSame('Attendance record retrieved successfully', $response['message']);
}
public function testStoreValidationFails()
{
$this->controller->setValidationResult(false, ['status' => 'required']);
$request = new FakeAttendanceRequest(['student_id'=>1,'date'=>'','status'=>'']);
$this->controller->setRequest($request);
$response = $this->controller->store();
$this->assertSame(422, $response['code']);
$this->assertArrayHasKey('status', $response['errors']);
}
public function testStoreAlreadyRecorded()
{
$request = new FakeAttendanceRequest(['student_id'=>2,'date'=>'2024-09-01','status'=>'present']);
$this->controller->setRequest($request);
$this->attendanceModel->queueFirst(['id'=>5]);
$response = $this->controller->store();
$this->assertSame(409, $response['code']);
}
public function testStoreCreatesRecord()
{
$request = new FakeAttendanceRequest(['student_id'=>2,'date'=>'2024-09-01','status'=>'present','notes'=>'ok']);
$this->controller->setRequest($request);
$this->controller->setValidationResult(true);
$response = $this->controller->store();
$this->assertSame('Attendance record created successfully', $response['message']);
$this->assertSame(1, $this->attendanceModel->insertId);
}
public function testUpdateNotFound()
{
$response = $this->controller->update(99);
$this->assertSame(ResponseInterface::HTTP_NOT_FOUND, $response['code']);
}
public function testUpdateValidationFails()
{
$this->attendanceModel->findResult[12] = ['id' => 12];
$this->controller->setValidationResult(false, ['status' => 'invalid']);
$this->controller->setRequest(new FakeAttendanceRequest(['status'=>'bad']));
$response = $this->controller->update(12);
$this->assertSame(422, $response['code']);
}
public function testUpdateSuccess()
{
$this->attendanceModel->findResult[13] = ['id' => 13];
$this->controller->setValidationResult(true);
$this->controller->setRequest(new FakeAttendanceRequest(['status'=>'absent']));
$response = $this->controller->update(13);
$this->assertSame('Attendance record updated successfully', $response['message']);
$this->assertSame('absent', $this->attendanceModel->findResult[13]['status']);
}
public function testDeleteNotFound()
{
$response = $this->controller->delete(20);
$this->assertSame(ResponseInterface::HTTP_NOT_FOUND, $response['code']);
}
public function testDeleteSuccess()
{
$this->attendanceModel->findResult[21] = ['id' => 21];
$response = $this->controller->delete(21);
$this->assertSame('Attendance record deleted successfully', $response['message']);
}
public function testStudentEndpoint()
{
$this->attendanceModel->findAllResult = [['id' => 5]];
$response = $this->controller->student(5);
$this->assertSame('Student attendance retrieved successfully', $response['message']);
}
public function testClassRosterAggregatesAttendance()
{
$stubRows = [
['student_id' => 1, 'firstname' => 'A', 'lastname' => 'B'],
['student_id' => 2, 'firstname' => 'C', 'lastname' => 'D'],
];
$db = new StubDbConnection($stubRows);
$this->controller->setDatabaseConnection($db);
$this->controller->setRequest(new FakeAttendanceRequest(null, ['date' => '2024-10-05']));
$this->attendanceModel->setAttendanceLookup(1, ['status' => 'present', 'reason' => '']);
$this->attendanceModel->setAttendanceLookup(2, []);
$response = $this->controller->classRoster(3);
$this->assertSame('Class roster attendance retrieved', $response['message']);
$this->assertSame(2, count($response['data']['students']));
}
public function testRecordClassValidationFails()
{
$this->controller->setRequest(new FakeAttendanceRequest(['records' => []]));
$response = $this->controller->recordClass(1);
$this->assertSame(422, $response['code']);
}
public function testRecordClassSavesRows()
{
$this->studentClassModel->grade = '5';
$this->attendanceModel->setAttendanceLookup(7, ['id' => 99]);
$payload = [
'date' => '2024-10-01',
'records' => [
['student_id' => 7, 'status' => 'present'],
['student_id' => 8, 'status' => 'late'],
],
];
$this->controller->setRequest(new FakeAttendanceRequest($payload));
$response = $this->controller->recordClass(12);
$this->assertSame('2 attendance row(s) saved', $response['message']);
$this->assertSame(2, $response['data']['saved']);
}
}