$name, 'data' => $data, 'options' => $options]; } } if (!function_exists('local_date')) { function local_date($timestamp, $format) { if ($timestamp instanceof \DateTimeInterface) { return $timestamp->format($format); } if (is_numeric($timestamp)) { return date($format, (int) $timestamp); } 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 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 = [], 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; } } class FakeJsonResponse { 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; } } class FakeRenderer { public array $lastData = []; public string $lastName = ''; public function setData(array $data, string $type = 'raw') { $this->lastData = $data; return $this; } public function render(string $name, array $options = [], bool $saveData = false) { $this->lastName = $name; return 'fake-render'; } } class FakeAttendanceModel { public function __construct(private array $existing = []) { } public function where(...$args) { return $this; } public function orderBy(...$args) { return $this; } public function findAll() { return $this->existing; } public function upsertOne(...$args) { return true; } } class TestableAdministratorController extends AdministratorController { public function __construct() { // Skip expensive parent constructor to avoid real DB/models. } public function setUserModel(UserModel $model): self { $this->userModel = $model; return $this; } public function setStaffAttendanceModel($model): self { $this->staffAttendanceModel = $model; return $this; } public function setSemester(string $semester): self { $this->semester = $semester; return $this; } public function setSchoolYear(string $schoolYear): self { $this->schoolYear = $schoolYear; return $this; } public function setRequest($request): self { $this->request = $request; return $this; } public function setResponse($response): self { $this->response = $response; return $this; } } class AdministratorControllerTest extends CIUnitTestCase { private TestableAdministratorController $controller; private FakeRenderer $renderer; protected function setUp(): void { parent::setUp(); Services::resetSingle('session'); helper('date'); session()->start(); $this->controller = new TestableAdministratorController(); $this->controller->setSemester('Fall')->setSchoolYear('2024-2025'); $this->renderer = new FakeRenderer(); Services::injectMock('renderer', $this->renderer); } 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'); $response = $this->controller->absenceFormAdmin(); $this->assertInstanceOf(RedirectResponse::class, $response); $this->assertStringContainsString('/login', $response->getHeaderLine('Location') ?: ''); } public function testAbsenceFormBuildsViewWithAttendance() { session()->set('user_id', 7); $userModel = $this->createMock(UserModel::class); $userModel->method('find')->with(7)->willReturn([ 'firstname' => 'Alex', 'lastname' => 'Admin', ]); $attendance = new FakeAttendanceModel([ ['date' => '2024-12-01'], ]); $this->controller->setUserModel($userModel)->setStaffAttendanceModel($attendance); $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']; } $this->assertSame('fake-render', $result); $this->assertSame($expectedView, $this->renderer->lastName); return $this->renderer->lastData; } } }