fix is_new issue with enrollment fixes
This commit is contained in:
@@ -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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
<?php
|
||||
|
||||
namespace {
|
||||
if (!function_exists('view')) {
|
||||
function view($name, array $data = [], $options = [])
|
||||
{
|
||||
return ['view' => $name, 'data' => $data, 'options' => $options];
|
||||
}
|
||||
}
|
||||
|
||||
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\GradingController;
|
||||
use App\Services\BelowSixtyService;
|
||||
use App\Services\GradingScoreService;
|
||||
use App\Services\PlacementGradingService;
|
||||
use App\Services\StudentDecisionService;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use Config\Services;
|
||||
|
||||
class GradingDummyRequest
|
||||
{
|
||||
public function __construct(private array $get = [], private array $post = [])
|
||||
{
|
||||
}
|
||||
|
||||
public function getGet($key = null)
|
||||
{
|
||||
if ($key === null) {
|
||||
return $this->get;
|
||||
}
|
||||
|
||||
return $this->get[$key] ?? null;
|
||||
}
|
||||
|
||||
public function getPost($key = null)
|
||||
{
|
||||
if ($key === null) {
|
||||
return $this->post;
|
||||
}
|
||||
|
||||
return $this->post[$key] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
class GradingFakeJsonResponse
|
||||
{
|
||||
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 GradingFakeRenderer
|
||||
{
|
||||
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 TestableGradingController extends GradingController
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// Skip ConfigurationModel lookup in the real constructor.
|
||||
}
|
||||
|
||||
public function setTerm(string $schoolYear, string $semester): self
|
||||
{
|
||||
$this->schoolYear = $schoolYear;
|
||||
$this->semester = $semester;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setRequest($request): self
|
||||
{
|
||||
$this->request = $request;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setResponse($response): self
|
||||
{
|
||||
$this->response = $response;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
class GradingControllerTest extends CIUnitTestCase
|
||||
{
|
||||
private TestableGradingController $controller;
|
||||
private GradingFakeRenderer $renderer;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Services::resetSingle('session');
|
||||
session()->start();
|
||||
|
||||
$this->controller = (new TestableGradingController())
|
||||
->setTerm('2024-2025', 'Fall')
|
||||
->setRequest(new GradingDummyRequest());
|
||||
$this->renderer = new GradingFakeRenderer();
|
||||
Services::injectMock('renderer', $this->renderer);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
foreach ([
|
||||
'renderer',
|
||||
'gradingScore',
|
||||
'placementGrading',
|
||||
'belowSixty',
|
||||
'studentDecision',
|
||||
] as $service) {
|
||||
Services::resetSingle($service);
|
||||
}
|
||||
session()->destroy();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testGradingPageRendersViewFromScoreService(): void
|
||||
{
|
||||
$scoreService = $this->createMock(GradingScoreService::class);
|
||||
$scoreService->expects($this->once())->method('setTerm')->with('2024-2025', 'Fall');
|
||||
$scoreService->expects($this->once())
|
||||
->method('gradingPage')
|
||||
->willReturn([
|
||||
'kind' => 'view',
|
||||
'view' => 'grading/grading_main',
|
||||
'data' => [
|
||||
'semester' => 'Fall',
|
||||
'schoolYear' => '2024-2025',
|
||||
'grades' => [],
|
||||
],
|
||||
]);
|
||||
Services::injectMock('gradingScore', $scoreService);
|
||||
|
||||
$result = $this->controller->grading();
|
||||
$data = $this->viewData($result, 'grading/grading_main');
|
||||
|
||||
$this->assertSame('Fall', $data['semester']);
|
||||
$this->assertSame('2024-2025', $data['schoolYear']);
|
||||
}
|
||||
|
||||
public function testUpdateMapsFlashResultToRedirect(): void
|
||||
{
|
||||
$scoreService = $this->createMock(GradingScoreService::class);
|
||||
$scoreService->method('setTerm');
|
||||
$scoreService->expects($this->once())
|
||||
->method('updateScores')
|
||||
->willReturn([
|
||||
'kind' => 'flash',
|
||||
'redirect' => 'back',
|
||||
'type' => 'status',
|
||||
'message' => 'Scores updated successfully.',
|
||||
]);
|
||||
Services::injectMock('gradingScore', $scoreService);
|
||||
|
||||
$this->controller->setRequest(new GradingDummyRequest([], [
|
||||
'type' => 'homework',
|
||||
'student_id' => 1,
|
||||
]));
|
||||
|
||||
$response = $this->controller->update();
|
||||
|
||||
$this->assertInstanceOf(RedirectResponse::class, $response);
|
||||
$this->assertSame('Scores updated successfully.', session()->getFlashdata('status'));
|
||||
}
|
||||
|
||||
public function testGetScoreCommentReturnsJsonFromScoreService(): void
|
||||
{
|
||||
$payload = ['1' => ['homework' => [['score' => '10']]]];
|
||||
|
||||
$scoreService = $this->createMock(GradingScoreService::class);
|
||||
$scoreService->method('setTerm');
|
||||
$scoreService->expects($this->once())
|
||||
->method('getScoreComment')
|
||||
->willReturn($payload);
|
||||
Services::injectMock('gradingScore', $scoreService);
|
||||
|
||||
$response = new GradingFakeJsonResponse();
|
||||
$this->controller->setResponse($response);
|
||||
|
||||
$result = $this->controller->getScoreComment();
|
||||
|
||||
$this->assertSame($response, $result);
|
||||
$this->assertSame($payload, $response->json);
|
||||
}
|
||||
|
||||
public function testPlacementUsesPlacementService(): void
|
||||
{
|
||||
$placement = $this->createMock(PlacementGradingService::class);
|
||||
$placement->expects($this->once())->method('setSchoolYear')->with('2024-2025');
|
||||
$placement->expects($this->once())
|
||||
->method('placementPage')
|
||||
->willReturn([
|
||||
'kind' => 'view',
|
||||
'view' => 'grading/placement_index',
|
||||
'data' => ['batches' => []],
|
||||
]);
|
||||
Services::injectMock('placementGrading', $placement);
|
||||
|
||||
$result = $this->controller->placement();
|
||||
$data = $this->viewData($result, 'grading/placement_index');
|
||||
|
||||
$this->assertSame([], $data['batches']);
|
||||
}
|
||||
|
||||
public function testAllDecisionsUsesDecisionService(): void
|
||||
{
|
||||
$decision = $this->createMock(StudentDecisionService::class);
|
||||
$decision->method('setTerm');
|
||||
$decision->expects($this->once())
|
||||
->method('allDecisionsPage')
|
||||
->willReturn([
|
||||
'kind' => 'view',
|
||||
'view' => 'grading/all_decisions',
|
||||
'data' => ['rows' => [], 'schoolYear' => '2024-2025'],
|
||||
]);
|
||||
Services::injectMock('studentDecision', $decision);
|
||||
|
||||
$result = $this->controller->allDecisions();
|
||||
$data = $this->viewData($result, 'grading/all_decisions');
|
||||
|
||||
$this->assertSame('2024-2025', $data['schoolYear']);
|
||||
}
|
||||
|
||||
public function testRespondGradingMapsJsonKindWithStatus(): void
|
||||
{
|
||||
$belowSixty = $this->createMock(BelowSixtyService::class);
|
||||
$belowSixty->method('setTerm');
|
||||
$belowSixty->expects($this->once())
|
||||
->method('studentDecisionDetails')
|
||||
->willReturn([
|
||||
'kind' => 'json',
|
||||
'status' => 400,
|
||||
'data' => ['error' => 'Missing student or school year.'],
|
||||
]);
|
||||
Services::injectMock('belowSixty', $belowSixty);
|
||||
|
||||
$response = new GradingFakeJsonResponse();
|
||||
$this->controller->setResponse($response);
|
||||
|
||||
$result = $this->controller->studentDecisionDetails();
|
||||
|
||||
$this->assertSame($response, $result);
|
||||
$this->assertSame(400, $response->statusCode);
|
||||
$this->assertSame(['error' => 'Missing student or school year.'], $response->json);
|
||||
}
|
||||
|
||||
public function testShowTypeDelegatesRouteArgsToScoreService(): void
|
||||
{
|
||||
$scoreService = $this->createMock(GradingScoreService::class);
|
||||
$scoreService->method('setTerm');
|
||||
$scoreService->expects($this->once())
|
||||
->method('showType')
|
||||
->with('homework', '12', '34', $this->isType('array'))
|
||||
->willReturn([
|
||||
'kind' => 'view',
|
||||
'view' => 'grading/homework',
|
||||
'data' => ['type' => 'homework'],
|
||||
]);
|
||||
Services::injectMock('gradingScore', $scoreService);
|
||||
|
||||
$result = $this->controller->show('homework', '12', '34');
|
||||
$data = $this->viewData($result, 'grading/homework');
|
||||
|
||||
$this->assertSame('homework', $data['type']);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\App\Controllers\View;
|
||||
|
||||
use App\Controllers\View\ParentController;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use ReflectionMethod;
|
||||
|
||||
final class ParentControllerEnrollmentContactTest extends CIUnitTestCase
|
||||
{
|
||||
public function testNormalizeEnrollmentParentContactAcceptsValidAddressAndPhone(): void
|
||||
{
|
||||
$result = $this->normalize([
|
||||
'cellphone' => '(203) 555-1212',
|
||||
'address_street' => '12 Oak Street',
|
||||
'apt' => '2B',
|
||||
'city' => 'bridgeport',
|
||||
'state' => 'ct',
|
||||
'zip' => '06604',
|
||||
]);
|
||||
|
||||
$this->assertSame([], $result['errors']);
|
||||
$this->assertSame('203-555-1212', $result['data']['cellphone']);
|
||||
$this->assertSame('12 Oak Street', $result['data']['address_street']);
|
||||
$this->assertSame('2B', $result['data']['apt']);
|
||||
$this->assertSame('Bridgeport', $result['data']['city']);
|
||||
$this->assertSame('CT', $result['data']['state']);
|
||||
$this->assertSame('06604', $result['data']['zip']);
|
||||
}
|
||||
|
||||
public function testNormalizeEnrollmentParentContactRejectsMissingPhoneAndAddress(): void
|
||||
{
|
||||
$result = $this->normalize([
|
||||
'cellphone' => '12345',
|
||||
'address_street' => '12',
|
||||
'city' => '',
|
||||
'state' => '',
|
||||
'zip' => 'abc',
|
||||
]);
|
||||
|
||||
$this->assertNotSame([], $result['errors']);
|
||||
$this->assertSame([], $result['data']);
|
||||
$this->assertStringContainsString('phone', strtolower(implode(' ', $result['errors'])));
|
||||
$this->assertStringContainsString('street address', strtolower(implode(' ', $result['errors'])));
|
||||
$this->assertStringContainsString('city', strtolower(implode(' ', $result['errors'])));
|
||||
$this->assertStringContainsString('state', strtolower(implode(' ', $result['errors'])));
|
||||
$this->assertStringContainsString('zip', strtolower(implode(' ', $result['errors'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $fields
|
||||
* @return array{errors: list<string>, data: array<string, string>}
|
||||
*/
|
||||
private function normalize(array $fields): array
|
||||
{
|
||||
$controller = new class extends ParentController {
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
$method = new ReflectionMethod(ParentController::class, 'normalizeEnrollmentParentContact');
|
||||
$method->setAccessible(true);
|
||||
|
||||
return $method->invoke($controller, $fields);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user