createMock(\CodeIgniter\Database\BaseConnection::class); // ✅ Stub Config\Database::connect() globally using class_alias // so CodeIgniter's static Database::connect() returns our fake DB if (!class_exists('Config\\FakeDatabase')) { eval('namespace Config; class FakeDatabase extends \\Config\\Database { public static function connect($group = null, bool $getShared = true) { return $GLOBALS["__fakeDb__"]; } }'); } $GLOBALS['__fakeDb__'] = $fakeDb; // Temporarily alias Config\Database to our FakeDatabase class_alias('Config\\FakeDatabase', 'Config\\Database'); // ✅ Mock all required models/services foreach ([ \App\Models\UserModel::class, \App\Models\StudentModel::class, \App\Models\ConfigurationModel::class, \App\Models\LoginActivityModel::class, \App\Models\EnrollmentModel::class, \App\Models\RefundModel::class, \App\Models\InvoiceModel::class, \App\Models\ClassSectionModel::class, \App\Models\UserRoleModel::class, \App\Models\StudentClassModel::class, \App\Models\StaffAttendanceModel::class, \App\Services\FeeCalculationService::class, ] as $class) { $this->mockModel($class); } // ✅ Configuration mock $configMock = $this->mockModel(\App\Models\ConfigurationModel::class); $configMock->method('getConfig')->willReturnMap([ ['school_year', '2024-2025'], ['semester', 'Fall'], ]); // ✅ Instantiate controller safely (no DB hit) $this->controller = new AdministratorController(); } protected function mockModel(string $class) { $mock = $this->createMock($class); Services::injectMock($class, $mock); return $mock; } /** @test */ public function testDashboardSuccess() { $result = $this->controller->dashboard(); $this->assertNotNull($result); } /* -------------------------------------------------------------------------- * absenceInfo() * -------------------------------------------------------------------------- */ public function testAbsenceInfoWithValidUser() { helper('date'); $this->controller->setSession(['user_id' => 7]); $userMock = model(UserModel::class); $staffMock = model(StaffAttendanceModel::class); $userMock->method('find')->willReturn(['firstname' => 'Jane', 'lastname' => 'Admin']); $staffMock->method('where')->willReturnSelf(); $staffMock->method('orderBy')->willReturnSelf(); $staffMock->method('findAll')->willReturn([ ['date' => '2025-09-14', 'status' => 'present'], ]); $response = $this->controller->absenceInfo(); $this->assertTrue($response['status']); $this->assertArrayHasKey('available_dates', $response['data']); } public function testAbsenceInfoUnauthorized() { $this->controller->setSession([]); // no user $response = $this->controller->absenceInfo(); $this->assertFalse($response['status']); $this->assertEquals(401, $response['code']); } /* -------------------------------------------------------------------------- * submitAbsence() * -------------------------------------------------------------------------- */ public function testSubmitAbsenceRejectsMissingReason() { $this->controller->setSession(['user_id' => 2]); $req = $this->makeRequest('POST', '/api/v1/administrator/absence', [ 'dates' => ['2025-10-05'], 'reason' => '', ]); $this->controller->setRequest($req); $resp = $this->controller->submitAbsence(); $this->assertFalse($resp['status']); $this->assertEquals('Reason is required', $resp['message']); } public function testSubmitAbsenceValidDateAndReason() { $this->controller->setSession(['user_id' => 5]); $req = $this->makeRequest('POST', '/api/v1/administrator/absence', [ 'dates' => ['2025-09-07'], 'reason_type' => 'Personal', 'reason' => 'Travel', ]); $this->controller->setRequest($req); $staffMock = model(StaffAttendanceModel::class); $userMock = model(UserModel::class); $userMock->method('getUserRole')->willReturn('admin'); $staffMock->method('upsertOne')->willReturn(true); $userMock->method('find')->willReturn(['firstname' => 'Sarah', 'lastname' => 'Ali', 'email' => 's@a.com']); $email = Services::email(); $email->clear(); $resp = $this->controller->submitAbsence(); $this->assertTrue($resp['status']); $this->assertArrayHasKey('saved_dates', $resp['data']); } /* -------------------------------------------------------------------------- * search() * -------------------------------------------------------------------------- */ public function testSearchRequiresQueryParam() { $this->controller->setRequest($this->makeRequest('GET', '/api/v1/administrator/search?q=')); $resp = $this->controller->search(); $this->assertFalse($resp['status']); $this->assertEquals('Search query is required', $resp['message']); } public function testSearchReturnsStructuredResults() { $this->controller->setRequest($this->makeRequest('GET', '/api/v1/administrator/search?q=john')); $db = $this->createMock(\CodeIgniter\Database\BaseConnection::class); $builder = $this->createMock(\CodeIgniter\Database\BaseBuilder::class); $builder->method('select')->willReturnSelf(); $builder->method('limit')->willReturnSelf(); $builder->method('get')->willReturnSelf(); $builder->method('getResultArray')->willReturn([ ['id' => 1, 'firstname' => 'John', 'lastname' => 'Doe'], ]); $db->method('table')->willReturn($builder); service('database')->shouldReceive('connect')->andReturn($db); $resp = $this->controller->search(); $this->assertTrue($resp['status']); $this->assertArrayHasKey('users', $resp['data']); } /* -------------------------------------------------------------------------- * enrollmentWithdrawal() * -------------------------------------------------------------------------- */ public function testEnrollmentWithdrawalPagination() { $mock = model(EnrollmentModel::class); $mock->method('where')->willReturnSelf(); $mock->method('orderBy')->willReturnSelf(); $mock->method('findAll')->willReturn([ ['student_id' => 1, 'status' => 'enrolled'], ]); $resp = $this->controller->enrollmentWithdrawal(); $this->assertTrue($resp['status']); $this->assertEquals('Enrollment/withdrawal data retrieved successfully', $resp['message']); } /* -------------------------------------------------------------------------- * enrollmentWithdrawalData() * -------------------------------------------------------------------------- */ public function testEnrollmentWithdrawalDataBuildsStudentList() { $stuMock = model(StudentModel::class); $stuMock->method('getStudentsWithClassAndEnrollment')->willReturn([ ['id' => 1, 'firstname' => 'A', 'lastname' => 'Z', 'parent_id' => 10], ]); $clsMock = model(ClassSectionModel::class); $clsMock->method('select')->willReturnSelf(); $clsMock->method('where')->willReturnSelf(); $clsMock->method('orderBy')->willReturnSelf(); $clsMock->method('findAll')->willReturn([ ['id' => 1, 'class_section_name' => 'KG1'], ]); $enrollMock = model(EnrollmentModel::class); $enrollMock->method('getEnrollmentStatus')->willReturn('enrolled'); $resp = $this->controller->enrollmentWithdrawalData(); $this->assertTrue($resp['status']); $this->assertArrayHasKey('students', $resp['data']); $this->assertArrayHasKey('classes', $resp['data']); } /* -------------------------------------------------------------------------- * updateEnrollmentStatuses() * -------------------------------------------------------------------------- */ public function testUpdateEnrollmentStatusesRejectsEmptyPayload() { $req = $this->makeRequest('POST', '/api/v1/administrator/enrollment-withdrawal/update', []); $this->controller->setRequest($req); $resp = $this->controller->updateEnrollmentStatuses(); $this->assertFalse($resp['status']); $this->assertEquals('No enrollment statuses were submitted.', $resp['message']); } public function testUpdateEnrollmentStatusesWithValidStatus() { $req = $this->makeRequest('POST', '/api/v1/administrator/enrollment-withdrawal/update', [ 'enrollment_status' => [1 => 'enrolled'] ]); $this->controller->setRequest($req); $stuMock = model(StudentModel::class); $stuMock->method('find')->willReturn(['id' => 1, 'parent_id' => 2]); $enrollMock = model(EnrollmentModel::class); $enrollMock->method('where')->willReturnSelf(); $enrollMock->method('get')->willReturnSelf(); $enrollMock->method('getRowArray')->willReturn(['enrollment_status' => 'payment pending', 'parent_id' => 2]); $invoiceMock = model(InvoiceModel::class); $invoiceMock->method('where')->willReturnSelf(); $invoiceMock->method('orderBy')->willReturnSelf(); $invoiceMock->method('first')->willReturn(['id' => 10, 'school_year' => '2024-2025']); $refundMock = model(RefundModel::class); $refundMock->method('where')->willReturnSelf(); $refundMock->method('first')->willReturn(null); $resp = $this->controller->updateEnrollmentStatuses(); $this->assertTrue($resp['status']); $this->assertArrayHasKey('groupsByParentStatus', $resp['data']); } /* -------------------------------------------------------------------------- * studentProfiles() * -------------------------------------------------------------------------- */ public function testStudentProfilesReturnsList() { $studClassMock = model(StudentClassModel::class); $studClassMock->method('getClassSectionNameByStudentId')->willReturn('KG2'); $resp = $this->controller->studentProfiles(); $this->assertTrue($resp['status']); $this->assertArrayHasKey('students', $resp['data']); } /* -------------------------------------------------------------------------- * parentProfiles() * -------------------------------------------------------------------------- */ public function testParentProfilesWithParentRole() { $userMock = model(UserModel::class); $userMock->method('findAll')->willReturn([ ['id' => 20, 'firstname' => 'Layla', 'lastname' => 'Yusuf', 'email' => 'layla@test.com', 'cellphone' => '123', 'school_id' => 'P1'], ]); $roleMock = model(UserRoleModel::class); $roleMock->method('getRolesByUserId')->willReturn([ ['role_name' => 'parent'], ]); $invMock = model(InvoiceModel::class); $invMock->method('getLatestInvoicePaidAmount')->willReturn(150.00); $invMock->method('getLatestInvoiceBalance')->willReturn(50.00); $resp = $this->controller->parentProfiles(); $this->assertTrue($resp['status']); $this->assertArrayHasKey('parents', $resp['data']); $this->assertEquals('Layla', $resp['data']['parents'][0]['firstname']); } }