fix is_new issue with enrollment fixes
Deploy to Shared Hosting / Shared hosting deploy (push) Failing after 48s
Tests / PHPUnit (push) Failing after 1m22s

This commit is contained in:
root
2026-08-20 19:57:25 -04:00
parent 127098b87c
commit 889c037660
29 changed files with 77306 additions and 293 deletions
@@ -20,30 +20,74 @@ namespace {
return date($format);
}
}
if (!function_exists('site_url')) {
function site_url($uri = '')
{
return 'https://test.alrahmaisgl.org/' . ltrim((string) $uri, '/');
}
}
if (!function_exists('base_url')) {
function base_url($uri = '')
{
return 'https://test.alrahmaisgl.org/' . ltrim((string) $uri, '/');
}
}
}
namespace Tests\App\Controllers\View {
use App\Controllers\View\AdministratorController;
use App\Models\UserModel;
use CodeIgniter\HTTP\RedirectResponse;
use App\Models\UserModel;
use App\Services\AdministratorDashboardService;
use App\Services\AdminNotificationSettingsService;
use App\Services\EnrollmentWithdrawalService;
use App\Services\TeacherSubmissionReportService;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\Test\CIUnitTestCase;
use Config\Services;
class DummyPostRequest
{
public function __construct(private array $post = [])
{
public function __construct(private array $post = [], private array $get = [])
{
}
public function getPost($key = null)
{
if ($key === null) {
return $this->post;
}
return $this->post[$key] ?? null;
}
public function getGet($key = null)
{
if ($key === null) {
return $this->get;
}
return $this->get[$key] ?? null;
}
}
public function getPost($key = null)
class FakeJsonResponse
{
if ($key === null) {
return $this->post;
public mixed $json = null;
public int $statusCode = 200;
public function setJSON($data): self
{
$this->json = $data;
return $this;
}
public function setStatusCode(int $code): self
{
$this->statusCode = $code;
return $this;
}
return $this->post[$key] ?? null;
}
}
class FakeRenderer
{
@@ -97,17 +141,17 @@ use CodeIgniter\HTTP\RedirectResponse;
// Skip expensive parent constructor to avoid real DB/models.
}
public function setUserModel(UserModel $model): self
{
$this->userModel = $model;
return $this;
}
public function setUserModel(UserModel $model): self
{
$this->userModel = $model;
return $this;
}
public function setStaffAttendanceModel($model): self
{
$this->staffAttendanceModel = $model;
return $this;
}
public function setStaffAttendanceModel($model): self
{
$this->staffAttendanceModel = $model;
return $this;
}
public function setSemester(string $semester): self
{
@@ -126,6 +170,12 @@ use CodeIgniter\HTTP\RedirectResponse;
$this->request = $request;
return $this;
}
public function setResponse($response): self
{
$this->response = $response;
return $this;
}
}
class AdministratorControllerTest extends CIUnitTestCase
@@ -147,117 +197,316 @@ use CodeIgniter\HTTP\RedirectResponse;
Services::injectMock('renderer', $this->renderer);
}
protected function tearDown(): void
{
Services::resetSingle('renderer');
session()->destroy();
parent::tearDown();
}
protected function tearDown(): void
{
Services::resetSingle('renderer');
Services::resetSingle('administratorDashboard');
Services::resetSingle('teacherSubmissionReport');
Services::resetSingle('adminNotificationSettings');
Services::resetSingle('enrollmentWithdrawal');
Services::resetSingle('emailService');
session()->destroy();
parent::tearDown();
}
public function testAbsenceFormRedirectsWhenNotLoggedIn()
{
session()->remove('user_id');
public function testAbsenceFormRedirectsWhenNotLoggedIn()
{
session()->remove('user_id');
$response = $this->controller->absenceFormAdmin();
$response = $this->controller->absenceFormAdmin();
$this->assertInstanceOf(RedirectResponse::class, $response);
$this->assertStringContainsString('/login', $response->getHeaderLine('Location') ?: '');
}
$this->assertInstanceOf(RedirectResponse::class, $response);
$this->assertStringContainsString('/login', $response->getHeaderLine('Location') ?: '');
}
public function testAbsenceFormBuildsViewWithAttendance()
{
session()->set('user_id', 7);
public function testAbsenceFormBuildsViewWithAttendance()
{
session()->set('user_id', 7);
$userModel = $this->createMock(UserModel::class);
$userModel->method('find')->with(7)->willReturn([
'firstname' => 'Alex',
'lastname' => 'Admin',
]);
$userModel = $this->createMock(UserModel::class);
$userModel->method('find')->with(7)->willReturn([
'firstname' => 'Alex',
'lastname' => 'Admin',
]);
$attendance = new FakeAttendanceModel([
['date' => '2024-12-01'],
]);
$attendance = new FakeAttendanceModel([
['date' => '2024-12-01'],
]);
$this->controller->setUserModel($userModel)->setStaffAttendanceModel($attendance);
$this->controller->setUserModel($userModel)->setStaffAttendanceModel($attendance);
$result = $this->controller->absenceFormAdmin();
$result = $this->controller->absenceFormAdmin();
if (is_array($result)) {
$this->assertSame('administrator/absence_vacation', $result['view']);
$data = $result['data'];
} else {
$this->assertSame('fake-render', $result);
$this->assertSame('administrator/absence_vacation', $this->renderer->lastName);
$data = $this->renderer->lastData;
}
$this->assertSame('Alex Admin', $data['admin_name']);
$this->assertSame('Fall', $data['semester']);
$this->assertSame('2024-2025', $data['schoolYear']);
$this->assertSame([['date' => '2024-12-01']], $data['existing']);
$this->assertIsArray($data['availableDates']);
}
public function testSubmitAbsenceRejectsMissingReason()
{
session()->set('user_id', 9);
$req = new DummyPostRequest([
'dates' => ['2024-12-01'],
'reason_type' => '',
'reason' => '',
]);
$this->controller->setRequest($req);
$response = $this->controller->submitAbsenceAdmin();
$this->assertInstanceOf(RedirectResponse::class, $response);
$this->assertStringContainsString('/administrator/absence', $response->getHeaderLine('Location') ?: '');
$this->assertSame('Reason is required.', session()->getFlashdata('message'));
}
public function testSubmitAbsenceSavesAndRedirects()
{
session()->set('user_id', 10);
$userModel = $this->createMock(UserModel::class);
$userModel->method('getUserRole')->with(10)->willReturn('administrator');
$userModel->method('find')->with(10)->willReturn([
'firstname' => 'Sam',
'lastname' => 'Admin',
'email' => 'sam@school.test',
]);
$attendance = new FakeAttendanceModel();
$this->controller->setUserModel($userModel)->setStaffAttendanceModel($attendance);
$method = new \ReflectionMethod($this->controller, 'allowedAbsenceDates');
$method->setAccessible(true);
$allowed = $method->invoke($this->controller);
if (empty($allowed)) {
$this->markTestSkipped('No allowed absence dates are currently available.');
}
$date = $allowed[0];
$req = new DummyPostRequest([
'dates' => [$date],
'reason_type' => 'Personal',
'reason' => 'Conference',
]);
$this->controller->setRequest($req);
$emailService = $this->createMock(\App\Services\EmailService::class);
$emailService->method('send')->willReturn(true);
Services::injectMock('emailService', $emailService);
$response = $this->controller->submitAbsenceAdmin();
$this->assertInstanceOf(RedirectResponse::class, $response);
$this->assertStringContainsString('/administrator/absence', $response->getHeaderLine('Location') ?: '');
$this->assertSame('success', session()->getFlashdata('status'));
$this->assertStringContainsString('saved', (string) session()->getFlashdata('message'));
}
public function testAdministratorDashboardUsesDashboardServiceSearch(): void
{
$dashboard = $this->createMock(AdministratorDashboardService::class);
$dashboard->expects($this->once())
->method('search')
->with('alex')
->willReturn([
'query' => 'alex',
'results' => ['users' => []],
'scope_used' => 'unscoped-raw',
'scope_label' => 'all years/semesters (raw, tokenized)',
'total_found' => 0,
]);
Services::injectMock('administratorDashboard', $dashboard);
$this->controller->setRequest(new DummyPostRequest([], ['query' => 'alex']));
$result = $this->controller->administratorDashboard();
$data = $this->viewData($result, 'administrator/administratordashboard');
$this->assertSame('alex', $data['query']);
$this->assertSame(0, $data['total_found']);
$this->assertArrayHasKey('dashboardEndpoint', $data);
}
public function testDashboardMetricsDelegatesToDashboardService(): void
{
$payload = [
'counts' => ['students' => 3],
'recentActivities' => [],
'meta' => ['schoolYear' => '2024-2025', 'semester' => 'Fall'],
];
$dashboard = $this->createMock(AdministratorDashboardService::class);
$dashboard->expects($this->once())
->method('metrics')
->with('2024-2025', 'Fall')
->willReturn($payload);
Services::injectMock('administratorDashboard', $dashboard);
$response = new FakeJsonResponse();
$this->controller->setResponse($response);
$result = $this->controller->dashboardMetrics();
$this->assertSame($response, $result);
$this->assertSame($payload, $response->json);
}
public function testUserSearchUsesDashboardService(): void
{
$dashboard = $this->createMock(AdministratorDashboardService::class);
$dashboard->expects($this->once())
->method('search')
->with('parent')
->willReturn([
'query' => 'parent',
'results' => [],
'total_found' => 0,
]);
Services::injectMock('administratorDashboard', $dashboard);
$this->controller->setRequest(new DummyPostRequest([], ['query' => 'parent']));
$result = $this->controller->userSearch();
$data = $this->viewData($result, 'administrator/search_results');
$this->assertSame('parent', $data['query']);
}
public function testNotificationsAlertsRedirectsWhenUnauthorized(): void
{
session()->remove('is_logged_in');
session()->remove('role');
$notification = $this->createMock(AdminNotificationSettingsService::class);
$notification->method('excludedRoles')->willReturn(['parent', 'teacher']);
Services::injectMock('adminNotificationSettings', $notification);
$response = $this->controller->notificationsAlerts();
$this->assertInstanceOf(RedirectResponse::class, $response);
$this->assertStringContainsString('/login', $response->getHeaderLine('Location') ?: '');
}
public function testNotificationsAlertsUsesNotificationServiceWhenAuthorized(): void
{
session()->set('is_logged_in', true);
session()->set('role', 'administrator');
$notification = $this->createMock(AdminNotificationSettingsService::class);
$notification->method('excludedRoles')->willReturn(['parent', 'teacher']);
$notification->expects($this->once())
->method('alertsPage')
->willReturn([
'admins' => [['id' => 1]],
'subjects' => ['finance' => 'Finance'],
'assignedSubjects' => [],
'tableReady' => true,
]);
Services::injectMock('adminNotificationSettings', $notification);
$result = $this->controller->notificationsAlerts();
$data = $this->viewData($result, 'administrator/notifications_alerts');
$this->assertTrue($data['tableReady']);
$this->assertSame([['id' => 1]], $data['admins']);
}
public function testSendTeacherSubmissionNotificationsRedirectsWithServiceFlash(): void
{
session()->set('user_id', 12);
$report = $this->createMock(TeacherSubmissionReportService::class);
$report->expects($this->once())
->method('sendNotifications')
->willReturn([
'redirect' => 'back',
'type' => 'success',
'message' => '1 notification sent.',
]);
Services::injectMock('teacherSubmissionReport', $report);
$this->controller->setRequest(new DummyPostRequest(['notify' => [1 => [2 => '1']]]));
$response = $this->controller->sendTeacherSubmissionNotifications();
$this->assertInstanceOf(RedirectResponse::class, $response);
$this->assertSame('1 notification sent.', session()->getFlashdata('success'));
}
public function testAdminEnrollmentWithdrawalHandlerUsesEnrollmentService(): void
{
session()->set('user_id', 5);
$enrollment = $this->createMock(EnrollmentWithdrawalService::class);
$enrollment->expects($this->once())
->method('updateStatuses')
->with(
['10' => 'enrolled'],
'2024-2025',
'Fall',
5
)
->willReturn([
'ok' => true,
'message' => 'Enrollment statuses updated and notifications sent.',
]);
Services::injectMock('enrollmentWithdrawal', $enrollment);
$this->controller->setRequest(new DummyPostRequest([
'enrollment_status' => ['10' => 'enrolled'],
]));
$response = $this->controller->adminEnrollmentWithdrawalHandler();
$this->assertInstanceOf(RedirectResponse::class, $response);
$this->assertStringContainsString('enroll_withdraw/enrollment_withdrawal', $response->getHeaderLine('Location') ?: '');
$this->assertSame(
'Enrollment statuses updated and notifications sent.',
session()->getFlashdata('success')
);
}
public function testShowNewStudentsUsesEnrollmentService(): void
{
$enrollment = $this->createMock(EnrollmentWithdrawalService::class);
$enrollment->expects($this->once())
->method('newStudents')
->with('2024-2025')
->willReturn([
'new_students' => [['id' => 1, 'new_student' => 'Yes']],
'total_new' => 1,
]);
Services::injectMock('enrollmentWithdrawal', $enrollment);
$result = $this->controller->showNewStudents();
$data = $this->viewData($result, 'enroll_withdraw/new-students');
$this->assertSame(1, $data['total_new']);
}
private function viewData(mixed $result, string $expectedView): array
{
if (is_array($result)) {
$this->assertSame($expectedView, $result['view']);
return $result['data'];
}
if (is_array($result)) {
$this->assertSame('administrator/absence_vacation', $result['view']);
$data = $result['data'];
} else {
$this->assertSame('fake-render', $result);
$this->assertSame('administrator/absence_vacation', $this->renderer->lastName);
$data = $this->renderer->lastData;
$this->assertSame($expectedView, $this->renderer->lastName);
return $this->renderer->lastData;
}
$this->assertSame('Alex Admin', $data['admin_name']);
$this->assertSame('Fall', $data['semester']);
$this->assertSame('2024-2025', $data['schoolYear']);
$this->assertSame([['date' => '2024-12-01']], $data['existing']);
$this->assertIsArray($data['availableDates']);
}
public function testSubmitAbsenceRejectsMissingReason()
{
session()->set('user_id', 9);
$req = new DummyPostRequest([
'dates' => ['2024-12-01'],
'reason_type' => '',
'reason' => '',
]);
$this->controller->setRequest($req);
$response = $this->controller->submitAbsenceAdmin();
$this->assertInstanceOf(RedirectResponse::class, $response);
$this->assertStringContainsString('/administrator/absence', $response->getHeaderLine('Location') ?: '');
$this->assertSame('Reason is required.', session()->getFlashdata('message'));
}
public function testSubmitAbsenceSavesAndRedirects()
{
session()->set('user_id', 10);
$userModel = $this->createMock(UserModel::class);
$userModel->method('getUserRole')->with(10)->willReturn('administrator');
$userModel->method('find')->with(10)->willReturn([
'firstname' => 'Sam',
'lastname' => 'Admin',
'email' => 'sam@school.test',
]);
$attendance = new FakeAttendanceModel();
$this->controller->setUserModel($userModel)->setStaffAttendanceModel($attendance);
$method = new \ReflectionMethod($this->controller, 'allowedAbsenceDates');
$method->setAccessible(true);
$allowed = $method->invoke($this->controller);
if (empty($allowed)) {
$this->markTestSkipped('No allowed absence dates are currently available.');
}
$date = $allowed[0];
$req = new DummyPostRequest([
'dates' => [$date],
'reason_type' => 'Personal',
'reason' => 'Conference',
]);
$this->controller->setRequest($req);
$emailService = $this->createMock(\App\Services\EmailService::class);
$emailService->method('send')->willReturn(true);
Services::injectMock('emailService', $emailService);
$response = $this->controller->submitAbsenceAdmin();
$this->assertInstanceOf(RedirectResponse::class, $response);
$this->assertStringContainsString('/administrator/absence', $response->getHeaderLine('Location') ?: '');
$this->assertSame('success', session()->getFlashdata('status'));
$this->assertStringContainsString('saved', (string) session()->getFlashdata('message'));
}
}
}