recreate project
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
|
||||
namespace {
|
||||
if (!function_exists('view')) {
|
||||
function view($name, array $data = [], $options = [])
|
||||
{
|
||||
return ['view' => $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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Tests\App\Controllers\View {
|
||||
|
||||
use App\Controllers\View\AdministratorController;
|
||||
use App\Models\UserModel;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use Config\Services;
|
||||
|
||||
class DummyPostRequest
|
||||
{
|
||||
public function __construct(private array $post = [])
|
||||
{
|
||||
}
|
||||
|
||||
public function getPost($key = null)
|
||||
{
|
||||
if ($key === null) {
|
||||
return $this->post;
|
||||
}
|
||||
return $this->post[$key] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
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();
|
||||
|
||||
$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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\App\Controllers\View {
|
||||
|
||||
use App\Controllers\View\RegisterController;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use Config\Services;
|
||||
|
||||
class TestableRegisterController extends RegisterController
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// Skip the heavy constructor that connects to the real database.
|
||||
}
|
||||
}
|
||||
|
||||
class RegisterControllerTest extends CIUnitTestCase
|
||||
{
|
||||
private TestableRegisterController $controller;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Services::resetSingle('session');
|
||||
session()->start();
|
||||
|
||||
$this->controller = new TestableRegisterController();
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
session()->remove('captcha_answer');
|
||||
session()->remove('user_email');
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testIndexGeneratesCaptchaWhenMissing()
|
||||
{
|
||||
session()->remove('captcha_answer');
|
||||
|
||||
$response = $this->controller->index();
|
||||
|
||||
$this->assertIsString($response);
|
||||
$captcha = session()->get('captcha_answer');
|
||||
$this->assertNotEmpty($captcha);
|
||||
$this->assertGreaterThanOrEqual(4, strlen($captcha));
|
||||
$this->assertLessThanOrEqual(8, strlen($captcha));
|
||||
}
|
||||
|
||||
public function testIndexReusesExistingCaptcha()
|
||||
{
|
||||
session()->set('captcha_answer', 'EXISTING');
|
||||
|
||||
$response = $this->controller->index();
|
||||
|
||||
$this->assertIsString($response);
|
||||
$this->assertSame('EXISTING', session()->get('captcha_answer'));
|
||||
}
|
||||
|
||||
public function testSuccessRedirectsWhenEmailMissing()
|
||||
{
|
||||
session()->remove('user_email');
|
||||
|
||||
$response = $this->controller->success();
|
||||
|
||||
$this->assertInstanceOf(RedirectResponse::class, $response);
|
||||
$this->assertStringContainsString('/register', $response->getHeaderLine('Location') ?: '');
|
||||
}
|
||||
|
||||
public function testSuccessReturnsViewWhenEmailPresent()
|
||||
{
|
||||
session()->set('user_email', 'parent@example.com');
|
||||
|
||||
$response = $this->controller->success();
|
||||
|
||||
$this->assertIsString($response);
|
||||
$this->assertSame('parent@example.com', session()->get('user_email'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace {
|
||||
if (!function_exists('base_url')) {
|
||||
function base_url($uri = '')
|
||||
{
|
||||
return 'https://test.alrahmaisgl.org/' . ltrim((string)$uri, '/');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('site_url')) {
|
||||
function site_url($uri = '')
|
||||
{
|
||||
return 'https://test.alrahmaisgl.org/' . ltrim((string)$uri, '/');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('view')) {
|
||||
function view($name, $data = [], $options = [])
|
||||
{
|
||||
return ['view' => $name, 'data' => $data, 'options' => $options];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Tests\App\Controllers\View {
|
||||
|
||||
use App\Controllers\View\ReimbursementController;
|
||||
use CodeIgniter\Database\BaseBuilder;
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use CodeIgniter\Database\BaseResult;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use Config\Services;
|
||||
|
||||
class TestableReimbursementController extends ReimbursementController
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// Skip the real parent constructor so we can inject mocks.
|
||||
}
|
||||
|
||||
public function setDb(BaseConnection $db): self
|
||||
{
|
||||
$this->db = $db;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
class FakeRenderer
|
||||
{
|
||||
private array $data = [];
|
||||
private array $lastRender = [];
|
||||
|
||||
public function setData(?array $data = null): self
|
||||
{
|
||||
$this->data = $data ?? [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render(string $view, array $data = [], $options = null)
|
||||
{
|
||||
$this->lastRender = [
|
||||
'view' => $view,
|
||||
'data' => $data ?: $this->data,
|
||||
'options' => $options,
|
||||
];
|
||||
|
||||
return 'fake-rendered:' . $view;
|
||||
}
|
||||
|
||||
public function getLastRender(): array
|
||||
{
|
||||
return $this->lastRender;
|
||||
}
|
||||
}
|
||||
|
||||
class ReimbursementControllerTest extends CIUnitTestCase
|
||||
{
|
||||
private TestableReimbursementController $controller;
|
||||
private FakeRenderer $renderer;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->renderer = new FakeRenderer();
|
||||
Services::injectMock('renderer', $this->renderer);
|
||||
|
||||
$result = $this->createMock(BaseResult::class);
|
||||
$result->method('getResultArray')->willReturn([
|
||||
[
|
||||
'id' => 5,
|
||||
'expense_receipt' => 'uploads/path/expense_receipt.pdf',
|
||||
'receipt_path' => null,
|
||||
],
|
||||
[
|
||||
'id' => 6,
|
||||
'expense_receipt' => null,
|
||||
'receipt_path' => 'uploads/receipt_path.pdf',
|
||||
],
|
||||
]);
|
||||
|
||||
$builder = $this->createMock(BaseBuilder::class);
|
||||
$builder->method('select')->willReturnSelf();
|
||||
$builder->method('join')->willReturnSelf();
|
||||
$builder->method('where')->willReturnSelf();
|
||||
$builder->method('orderBy')->willReturnSelf();
|
||||
$builder->method('get')->willReturn($result);
|
||||
|
||||
$db = $this->createMock(BaseConnection::class);
|
||||
$db->method('table')->willReturn($builder);
|
||||
|
||||
$this->controller = new TestableReimbursementController();
|
||||
$this->controller->setDb($db);
|
||||
}
|
||||
|
||||
public function testUnderProcessingBuildsViewWithExpenses()
|
||||
{
|
||||
$result = $this->controller->underProcessing();
|
||||
$this->assertSame('fake-rendered:reimbursements/under_processing', $result);
|
||||
|
||||
$rendered = $this->renderer->getLastRender();
|
||||
$this->assertSame('reimbursements/under_processing', $rendered['view']);
|
||||
$expenses = $rendered['data']['expenses'];
|
||||
$this->assertCount(2, $expenses);
|
||||
|
||||
$first = $expenses[0];
|
||||
$this->assertSame(site_url('receipts/expense_receipt.pdf'), $first['expense_receipt_url']);
|
||||
$this->assertSame(
|
||||
'<a href="' . base_url('reimbursements/create?expense_id=5') . '" class="btn btn-sm btn-primary">Reimburse</a>',
|
||||
$first['action']
|
||||
);
|
||||
|
||||
$second = $expenses[1];
|
||||
$this->assertSame(site_url('receipts/receipt_path.pdf'), $second['expense_receipt_url']);
|
||||
$this->assertSame(
|
||||
'<a href="' . base_url('reimbursements/create?expense_id=6') . '" class="btn btn-sm btn-primary">Reimburse</a>',
|
||||
$second['action']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user