$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; } } }