This commit is contained in:
@@ -3,12 +3,13 @@
|
||||
namespace Tests\App\Controllers\View;
|
||||
|
||||
use App\Controllers\View\ParentController;
|
||||
use App\Support\Enrollment\DeliberationDecision;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use ReflectionMethod;
|
||||
|
||||
final class ParentControllerAgeTest extends CIUnitTestCase
|
||||
{
|
||||
public function testEnrollmentAgeUsesSchoolYearStartYearCutoff(): void
|
||||
public function testEnrollmentAgeUsesSeptemberFirstSchoolYearCutoff(): void
|
||||
{
|
||||
$controller = new class extends ParentController {
|
||||
public function __construct()
|
||||
@@ -19,8 +20,9 @@ final class ParentControllerAgeTest extends CIUnitTestCase
|
||||
$method = new ReflectionMethod(ParentController::class, 'calculateAgeAsOfSchoolYearStartYear');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$this->assertSame(6, $method->invoke($controller, '2019-09-15', '2025-2026'));
|
||||
$this->assertSame(5, $method->invoke($controller, '2019-09-15', '2025-2026'));
|
||||
$this->assertSame(5, $method->invoke($controller, '2020-01-01', '2025-2026'));
|
||||
$this->assertSame(4, $method->invoke($controller, '2020-09-02', '2025-2026'));
|
||||
}
|
||||
|
||||
public function testEnrollmentAgeRejectsInvalidInputs(): void
|
||||
@@ -39,4 +41,127 @@ final class ParentControllerAgeTest extends CIUnitTestCase
|
||||
$this->assertNull($method->invoke($controller, '2026-01-01', '2025-2026'));
|
||||
$this->assertNull($method->invoke($controller, '2019-09-15', ''));
|
||||
}
|
||||
|
||||
public function testRegistrationValidationUsesDecemberThirtyFirstOnlyForMinimumAgeGrace(): void
|
||||
{
|
||||
$controller = new class extends ParentController {
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
$this->assertTrue($controller->validateDobAge('2020-12-31', '2025-12-31', 5, 18, '2025-09-01')['isValid']);
|
||||
$this->assertFalse($controller->validateDobAge('2021-01-01', '2025-12-31', 5, 18, '2025-09-01')['isValid']);
|
||||
$this->assertTrue($controller->validateDobAge('2006-09-02', '2025-12-31', 5, 18, '2025-09-01')['isValid']);
|
||||
$this->assertFalse($controller->validateDobAge('2006-08-31', '2025-12-31', 5, 18, '2025-09-01')['isValid']);
|
||||
}
|
||||
|
||||
public function testEnrollmentEligibilityBlocksFinalDecisionStatuses(): void
|
||||
{
|
||||
$controller = new class extends ParentController {
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
$method = new ReflectionMethod(ParentController::class, 'enrollmentEligibilityMessageForStudent');
|
||||
$method->setAccessible(true);
|
||||
|
||||
foreach (['expel', 'withdrawn', 'deferred decision'] as $decision) {
|
||||
$message = $method->invoke(
|
||||
$controller,
|
||||
['firstname' => 'Ali', 'lastname' => 'Ahmed', 'dob' => '2010-01-01'],
|
||||
['decision' => $decision, 'source' => 'manual', 'class_section_name' => 'Level 5'],
|
||||
'2025-2026',
|
||||
null
|
||||
);
|
||||
|
||||
$this->assertTrue($message['blocking'], $decision . ' should block enrollment.');
|
||||
$this->assertNotSame('', $message['message']);
|
||||
}
|
||||
}
|
||||
|
||||
public function testEnrollmentEligibilityBlocksAdultStudentsOnSeptemberFirst(): void
|
||||
{
|
||||
$controller = new class extends ParentController {
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
$method = new ReflectionMethod(ParentController::class, 'enrollmentEligibilityMessageForStudent');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$message = $method->invoke(
|
||||
$controller,
|
||||
['firstname' => 'Ali', 'lastname' => 'Ahmed', 'dob' => '2006-08-31'],
|
||||
['decision' => 'pass', 'source' => 'manual', 'class_section_name' => 'Level 9'],
|
||||
'2025-2026',
|
||||
null
|
||||
);
|
||||
|
||||
$this->assertTrue($message['blocking']);
|
||||
$this->assertStringContainsString('18 years old or older on September 1', $message['message']);
|
||||
$this->assertStringContainsString('A parent or guardian cannot complete registration', $message['message']);
|
||||
}
|
||||
|
||||
public function testEnrollmentEligibilityAllowsFallMakeupExamWithWarning(): void
|
||||
{
|
||||
$controller = new class extends ParentController {
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
$method = new ReflectionMethod(ParentController::class, 'enrollmentEligibilityMessageForStudent');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$message = $method->invoke(
|
||||
$controller,
|
||||
['firstname' => 'Ali', 'lastname' => 'Ahmed', 'dob' => '2010-01-01'],
|
||||
['decision' => 'Make Up Exam in Fall', 'source' => 'manual', 'class_section_name' => 'Level 5'],
|
||||
'2025-2026',
|
||||
'2025-09-10'
|
||||
);
|
||||
|
||||
$this->assertFalse($message['blocking']);
|
||||
$this->assertSame('warning', $message['level']);
|
||||
$this->assertStringContainsString('will initially remain in the same grade', $message['message']);
|
||||
}
|
||||
|
||||
public function testRequiredActionDoesNotDefaultToContactAdministration(): void
|
||||
{
|
||||
$controller = new class extends ParentController {
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
$method = new ReflectionMethod(ParentController::class, 'requiredActionLabel');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$this->assertSame(
|
||||
'Complete re-enrollment before the registration deadline.',
|
||||
$method->invoke($controller, null)
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
'Complete re-enrollment before the registration deadline.',
|
||||
$method->invoke($controller, [
|
||||
'blockers' => [],
|
||||
'deliberation_decision' => DeliberationDecision::PASSED,
|
||||
'parent_enrollment_allowed' => true,
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
'Contact the school administration.',
|
||||
$method->invoke($controller, [
|
||||
'blockers' => ['Final decision is pending.'],
|
||||
'deliberation_decision' => DeliberationDecision::DEFERRED_DECISION,
|
||||
'parent_enrollment_allowed' => false,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,14 +15,43 @@ use Config\Services;
|
||||
|
||||
final class SchoolYearWritableFilterFakeModel extends SchoolYearModel
|
||||
{
|
||||
private array $filters = [];
|
||||
|
||||
public function __construct(private readonly array $row)
|
||||
{
|
||||
}
|
||||
|
||||
public function find($id = null)
|
||||
{
|
||||
return ((int) $id === (int) ($this->row['id'] ?? 0)) ? $this->row : null;
|
||||
}
|
||||
|
||||
public function active(): ?array
|
||||
{
|
||||
return $this->row;
|
||||
}
|
||||
|
||||
public function where($key, $value = null, ?bool $escape = null)
|
||||
{
|
||||
$this->filters[] = [$key, $value];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function first()
|
||||
{
|
||||
foreach ($this->filters as [$key, $value]) {
|
||||
if (($this->row[$key] ?? null) !== $value) {
|
||||
$this->filters = [];
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$this->filters = [];
|
||||
|
||||
return $this->row;
|
||||
}
|
||||
}
|
||||
|
||||
final class SchoolYearWritableFilterTest extends CIUnitTestCase
|
||||
@@ -59,6 +88,21 @@ final class SchoolYearWritableFilterTest extends CIUnitTestCase
|
||||
$this->assertNull((new SchoolYearWritableFilter())->before($request));
|
||||
}
|
||||
|
||||
public function testPostBodySchoolYearAgainstClosedYearIsBlocked(): void
|
||||
{
|
||||
$this->useSchoolYearContext(['id' => 1, 'name' => '2025-2026', 'status' => 'closed']);
|
||||
|
||||
$request = $this->request('POST', 'https://example.test/grading/below-60/decisions/save');
|
||||
$request->setHeader('Accept', 'application/json');
|
||||
$request->setGlobal('post', ['school_year' => '2025-2026']);
|
||||
|
||||
$result = (new SchoolYearWritableFilter())->before($request);
|
||||
|
||||
$this->assertInstanceOf(Response::class, $result);
|
||||
$this->assertSame(409, $result->getStatusCode());
|
||||
$this->assertStringContainsString('Read-only school year', $result->getBody());
|
||||
}
|
||||
|
||||
public function testSchoolYearSelectionPostIsExempt(): void
|
||||
{
|
||||
$this->useSchoolYearContext(['id' => 1, 'name' => '2025-2026', 'status' => 'closed']);
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\App\Services;
|
||||
|
||||
use App\Services\EmailService;
|
||||
use App\Services\EnrollmentRegistrationEmailService;
|
||||
use App\Services\EnrollmentTransitionService;
|
||||
use App\Support\Enrollment\DeliberationDecision;
|
||||
use CodeIgniter\Database\BaseBuilder;
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class EnrollmentRegistrationEmailServiceTest extends TestCase
|
||||
{
|
||||
public function testPassedStudentUsesReEnrollmentOpeningLanguage(): void
|
||||
{
|
||||
$service = $this->service();
|
||||
|
||||
$evaluation = [
|
||||
'deliberation_decision' => DeliberationDecision::PASSED,
|
||||
'placement_status' => 'exit_required',
|
||||
'source_grade_name' => '8',
|
||||
'adult_student' => false,
|
||||
'blockers' => [
|
||||
'The student has passed the highest available grade and must follow the school completion or exit process.',
|
||||
'Registration for the new school year has not opened yet.',
|
||||
],
|
||||
];
|
||||
|
||||
$status = $this->invoke($service, 'registrationStatus', [$evaluation]);
|
||||
$message = $this->invoke($service, 'decisionMessage', ['Student Name', $evaluation, 'August 1, 2026', 'August 31, 2026']);
|
||||
$action = $this->invoke($service, 'requiredAction', [$evaluation, 'August 31, 2026']);
|
||||
|
||||
$this->assertSame('Eligible', $status);
|
||||
$this->assertStringContainsString('We are pleased to inform you that Student Name has successfully passed Grade 8.', $message);
|
||||
$this->assertStringContainsString('Re-enrollment for the new school year will open on August 1, 2026.', $message);
|
||||
$this->assertStringContainsString('Please make sure to re-enroll your child before August 31, 2026', $message);
|
||||
$this->assertStringContainsString('sign in to the parent portal', $message);
|
||||
$this->assertStringContainsString('Complete re-enrollment before August 31, 2026.', $action);
|
||||
$this->assertStringNotContainsString('Not Eligible', $status);
|
||||
$this->assertStringNotContainsString('Registration for the new school year has not opened yet', $message);
|
||||
$this->assertStringNotContainsString('Contact the school administration.', $action);
|
||||
}
|
||||
|
||||
public function testFinancialSectionIsHiddenWhenNothingIsDue(): void
|
||||
{
|
||||
$service = $this->service();
|
||||
|
||||
$html = $this->invoke($service, 'financialSection', [10, [
|
||||
'registration_fee' => 0,
|
||||
'tuition_due_at_registration' => 0,
|
||||
'mandatory_fees' => 0,
|
||||
], null]);
|
||||
|
||||
$this->assertSame('', $html);
|
||||
}
|
||||
|
||||
public function testFinancialSectionShowsWhenAnyAmountIsDue(): void
|
||||
{
|
||||
$service = $this->service();
|
||||
|
||||
$html = $this->invoke($service, 'financialSection', [10, [
|
||||
'registration_fee' => 25,
|
||||
'tuition_due_at_registration' => 0,
|
||||
'mandatory_fees' => 0,
|
||||
], null]);
|
||||
|
||||
$this->assertStringContainsString('Family Account Information', $html);
|
||||
$this->assertStringContainsString('Total currently due:</strong> $25.00', $html);
|
||||
}
|
||||
|
||||
public function testAutomaticDistributionPlacementShowsOnlyGrade(): void
|
||||
{
|
||||
$service = $this->service();
|
||||
|
||||
$placement = $this->invoke($service, 'placementText', [[
|
||||
'placement_status' => 'automatic_distribution_pending',
|
||||
'assigned_grade_name' => '9',
|
||||
]]);
|
||||
|
||||
$this->assertSame('Grade 9', $placement);
|
||||
}
|
||||
|
||||
public function testForceCannotSendWithoutAdminLaunchApproval(): void
|
||||
{
|
||||
$emailService = $this->createMock(EmailService::class);
|
||||
$emailService->expects($this->never())->method('send');
|
||||
$service = $this->service($this->dbWithNoRecipientFamilies(), $emailService);
|
||||
|
||||
$summary = $service->sendForSchoolYear([
|
||||
'name' => '2026-2027',
|
||||
'registration_launch_approved_at' => null,
|
||||
], null, false, true);
|
||||
|
||||
$this->assertSame(0, $summary['sent']);
|
||||
$this->assertSame(1, $summary['skipped']);
|
||||
$this->assertStringContainsString('not approved', implode(' ', $summary['messages']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<mixed> $args
|
||||
*/
|
||||
private function invoke(EnrollmentRegistrationEmailService $service, string $method, array $args): mixed
|
||||
{
|
||||
$reflection = new \ReflectionMethod($service, $method);
|
||||
$reflection->setAccessible(true);
|
||||
|
||||
return $reflection->invokeArgs($service, $args);
|
||||
}
|
||||
|
||||
private function service(?BaseConnection $db = null, ?EmailService $emailService = null): EnrollmentRegistrationEmailService
|
||||
{
|
||||
$db ??= $this->createMock(BaseConnection::class);
|
||||
|
||||
return new EnrollmentRegistrationEmailService(
|
||||
$db,
|
||||
new EnrollmentTransitionService($db),
|
||||
$emailService ?? $this->createMock(EmailService::class),
|
||||
);
|
||||
}
|
||||
|
||||
private function dbWithNoRecipientFamilies(): BaseConnection
|
||||
{
|
||||
$query = new class {
|
||||
public function getResultArray(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
$builder = $this->createMock(BaseBuilder::class);
|
||||
$builder->method('select')->willReturnSelf();
|
||||
$builder->method('where')->willReturnSelf();
|
||||
$builder->method('get')->willReturn($query);
|
||||
|
||||
$db = $this->createMock(BaseConnection::class);
|
||||
$db->method('table')->willReturn($builder);
|
||||
$db->method('fieldExists')->with('user_type', 'users')->willReturn(false);
|
||||
|
||||
return $db;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\App\Services;
|
||||
|
||||
use App\Services\EnrollmentTransitionService;
|
||||
use CodeIgniter\Database\BaseBuilder;
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class EnrollmentTransitionServiceTest extends TestCase
|
||||
{
|
||||
public function testPassedClassNameWithPrefixFindsNextNumericGrade(): void
|
||||
{
|
||||
$service = $this->serviceExpectingClassLookup('9', ['id' => 9, 'class_name' => '9']);
|
||||
|
||||
$result = $this->invoke($service, 'nextClass', ['Class 8', '2026-2027']);
|
||||
|
||||
$this->assertSame(9, (int) $result['id']);
|
||||
}
|
||||
|
||||
public function testPassedClassNinePromotesToTenWhenGradeTenExists(): void
|
||||
{
|
||||
$service = $this->serviceExpectingClassLookup('10', ['id' => 10, 'class_name' => '10']);
|
||||
|
||||
$result = $this->invoke($service, 'nextClass', ['Class 9', '2026-2027']);
|
||||
|
||||
$this->assertSame(10, (int) $result['id']);
|
||||
}
|
||||
|
||||
public function testClassLookupFallsBackToGlobalRowsWhenSchoolYearSpecificRowIsMissing(): void
|
||||
{
|
||||
$firstBuilder = $this->builderReturning(null);
|
||||
$firstBuilder->expects($this->exactly(2))
|
||||
->method('where')
|
||||
->willReturnSelf();
|
||||
|
||||
$fallbackBuilder = $this->builderReturning(['id' => 10, 'class_name' => '10']);
|
||||
$fallbackBuilder->expects($this->once())
|
||||
->method('where')
|
||||
->with('UPPER(class_name)', '10')
|
||||
->willReturnSelf();
|
||||
|
||||
$db = $this->createMock(BaseConnection::class);
|
||||
$db->expects($this->exactly(2))
|
||||
->method('table')
|
||||
->with('classes')
|
||||
->willReturnOnConsecutiveCalls($firstBuilder, $fallbackBuilder);
|
||||
$db->method('fieldExists')->with('school_year', 'classes')->willReturn(true);
|
||||
|
||||
$service = new EnrollmentTransitionService($db);
|
||||
|
||||
$result = $this->invoke($service, 'nextClass', ['Class 9', '2026-2027']);
|
||||
|
||||
$this->assertSame(10, (int) $result['id']);
|
||||
}
|
||||
|
||||
private function serviceExpectingClassLookup(string $expectedClassName, array $row): EnrollmentTransitionService
|
||||
{
|
||||
$builder = $this->builderReturning($row);
|
||||
$builder->expects($this->once())
|
||||
->method('where')
|
||||
->with('UPPER(class_name)', $expectedClassName)
|
||||
->willReturnSelf();
|
||||
|
||||
$db = $this->createMock(BaseConnection::class);
|
||||
$db->expects($this->once())
|
||||
->method('table')
|
||||
->with('classes')
|
||||
->willReturn($builder);
|
||||
$db->method('fieldExists')->with('school_year', 'classes')->willReturn(false);
|
||||
|
||||
return new EnrollmentTransitionService($db);
|
||||
}
|
||||
|
||||
private function builderReturning(?array $row): BaseBuilder
|
||||
{
|
||||
$builder = $this->createMock(BaseBuilder::class);
|
||||
$builder->method('orderBy')->willReturnSelf();
|
||||
$builder->method('limit')->willReturnSelf();
|
||||
$builder->method('get')->willReturn(new class($row) {
|
||||
public function __construct(private readonly ?array $row)
|
||||
{
|
||||
}
|
||||
|
||||
public function getRowArray(): ?array
|
||||
{
|
||||
return $this->row;
|
||||
}
|
||||
});
|
||||
|
||||
return $builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<mixed> $args
|
||||
*/
|
||||
private function invoke(EnrollmentTransitionService $service, string $method, array $args): mixed
|
||||
{
|
||||
$reflection = new \ReflectionMethod($service, $method);
|
||||
$reflection->setAccessible(true);
|
||||
|
||||
return $reflection->invokeArgs($service, $args);
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,14 @@ use Config\App;
|
||||
|
||||
final class SchoolYearContextRequest extends MockIncomingRequest
|
||||
{
|
||||
public function __construct(private readonly array $gets = [])
|
||||
public function __construct(
|
||||
private readonly array $gets = [],
|
||||
private readonly array $posts = [],
|
||||
string $method = 'GET'
|
||||
)
|
||||
{
|
||||
parent::__construct(config(App::class), new URI('https://test.alrahmaisgl.org'), 'php://input', new UserAgent());
|
||||
$this->setMethod($method);
|
||||
}
|
||||
|
||||
public function getGet($key = null, $filter = null, $default = null)
|
||||
@@ -25,6 +30,15 @@ final class SchoolYearContextRequest extends MockIncomingRequest
|
||||
|
||||
return $this->gets[$key] ?? $default;
|
||||
}
|
||||
|
||||
public function getPost($index = null, $filter = null, $flags = null)
|
||||
{
|
||||
if ($index === null) {
|
||||
return $this->posts;
|
||||
}
|
||||
|
||||
return $this->posts[$index] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
final class SchoolYearContextFakeModel extends SchoolYearModel
|
||||
@@ -79,6 +93,11 @@ final class SchoolYearContextFakeModel extends SchoolYearModel
|
||||
|
||||
return $limit !== null ? array_slice($rows, $offset, $limit) : array_slice($rows, $offset);
|
||||
}
|
||||
|
||||
public function first()
|
||||
{
|
||||
return $this->findAll(1)[0] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
final class SchoolYearContextServiceTest extends CIUnitTestCase
|
||||
@@ -110,6 +129,56 @@ final class SchoolYearContextServiceTest extends CIUnitTestCase
|
||||
$this->assertSame(1, session()->get('selected_school_year_id'));
|
||||
}
|
||||
|
||||
public function testPostSchoolYearNameTakesPrecedenceOverActiveYearForWrites(): void
|
||||
{
|
||||
$service = new SchoolYearContextService(new SchoolYearContextFakeModel([
|
||||
1 => ['id' => 1, 'name' => '2025-2026', 'status' => 'closed'],
|
||||
2 => ['id' => 2, 'name' => '2026-2027', 'status' => 'active'],
|
||||
]));
|
||||
|
||||
$context = $service->resolve(new SchoolYearContextRequest(
|
||||
posts: ['school_year' => '2025-2026'],
|
||||
method: 'POST'
|
||||
));
|
||||
|
||||
$this->assertSame(1, $context->id());
|
||||
$this->assertSame('2025-2026', $context->yearName());
|
||||
$this->assertTrue($context->isReadonly());
|
||||
$this->assertTrue($context->isExplicitSelection());
|
||||
}
|
||||
|
||||
public function testPostSchoolYearIdTakesPrecedenceOverActiveYearForWrites(): void
|
||||
{
|
||||
$service = new SchoolYearContextService(new SchoolYearContextFakeModel([
|
||||
1 => ['id' => 1, 'name' => '2025-2026', 'status' => 'closed'],
|
||||
2 => ['id' => 2, 'name' => '2026-2027', 'status' => 'active'],
|
||||
]));
|
||||
|
||||
$context = $service->resolve(new SchoolYearContextRequest(
|
||||
posts: ['school_year_id' => '1'],
|
||||
method: 'POST'
|
||||
));
|
||||
|
||||
$this->assertSame(1, $context->id());
|
||||
$this->assertSame('2025-2026', $context->yearName());
|
||||
$this->assertTrue($context->isReadonly());
|
||||
}
|
||||
|
||||
public function testCanResolveContextFromStoredSchoolYearName(): void
|
||||
{
|
||||
$service = new SchoolYearContextService(new SchoolYearContextFakeModel([
|
||||
1 => ['id' => 1, 'name' => '2025-2026', 'status' => 'closed'],
|
||||
2 => ['id' => 2, 'name' => '2026-2027', 'status' => 'active'],
|
||||
]));
|
||||
|
||||
$context = $service->forYearName('2025-2026');
|
||||
|
||||
$this->assertSame(1, $context->id());
|
||||
$this->assertSame('2025-2026', $context->yearName());
|
||||
$this->assertTrue($context->isReadonly());
|
||||
$this->assertTrue($context->isExplicitSelection());
|
||||
}
|
||||
|
||||
public function testInvalidSessionSelectionFallsBackToActiveYearAndClearsSession(): void
|
||||
{
|
||||
session()->set('selected_school_year_id', 99);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\App\Support\Enrollment;
|
||||
|
||||
use App\Support\Enrollment\DeliberationDecision;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
|
||||
final class DeliberationDecisionTest extends CIUnitTestCase
|
||||
{
|
||||
public function testHistoricalDecisionValuesMapToCanonicalValues(): void
|
||||
{
|
||||
$cases = [
|
||||
'Pass' => DeliberationDecision::PASSED,
|
||||
'Passed' => DeliberationDecision::PASSED,
|
||||
'Repeat Class' => DeliberationDecision::REPEAT_CLASS,
|
||||
'Make-up exam in fall' => DeliberationDecision::MAKE_UP_EXAM,
|
||||
'Expel' => DeliberationDecision::EXPELLED,
|
||||
'Expelled' => DeliberationDecision::EXPELLED,
|
||||
'Withdrawn' => DeliberationDecision::WITHDRAWN,
|
||||
'Withdraw' => DeliberationDecision::WITHDRAWN,
|
||||
'Widthrwan' => DeliberationDecision::WITHDRAWN,
|
||||
'Deferred' => DeliberationDecision::DEFERRED_DECISION,
|
||||
'Deferred decision' => DeliberationDecision::DEFERRED_DECISION,
|
||||
];
|
||||
|
||||
foreach ($cases as $input => $expected) {
|
||||
$this->assertSame($expected, DeliberationDecision::normalize($input), $input);
|
||||
}
|
||||
}
|
||||
|
||||
public function testBlankAndUnknownDecisionsRemainUnmapped(): void
|
||||
{
|
||||
$this->assertNull(DeliberationDecision::normalize(''));
|
||||
$this->assertNull(DeliberationDecision::normalize('Teacher review needed'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\App\Support\Enrollment;
|
||||
|
||||
use App\Support\Enrollment\EnrollmentEligibility;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
|
||||
final class EnrollmentEligibilityTest extends CIUnitTestCase
|
||||
{
|
||||
public function testParentEnrollmentIsBlockedWhenStudentIsEighteenOnSeptemberFirst(): void
|
||||
{
|
||||
$message = EnrollmentEligibility::parentDecisionMessage(
|
||||
['firstname' => 'Adult', 'lastname' => 'Student', 'dob' => '2008-09-01'],
|
||||
['decision' => 'Pass', 'source' => 'manual'],
|
||||
'2026-2027'
|
||||
);
|
||||
|
||||
$this->assertTrue($message['blocking']);
|
||||
$this->assertStringContainsString('18 years old or older on September 1', $message['message']);
|
||||
}
|
||||
|
||||
public function testParentEnrollmentIsAllowedWhenStudentTurnsEighteenAfterSeptemberFirst(): void
|
||||
{
|
||||
$message = EnrollmentEligibility::parentDecisionMessage(
|
||||
['firstname' => 'Minor', 'lastname' => 'Student', 'dob' => '2008-09-02'],
|
||||
['decision' => 'Pass', 'source' => 'manual'],
|
||||
'2026-2027'
|
||||
);
|
||||
|
||||
$this->assertFalse($message['blocking']);
|
||||
}
|
||||
|
||||
public function testBlockedDecisionMessagesUseRequiredAdministrativeText(): void
|
||||
{
|
||||
$expelled = EnrollmentEligibility::parentDecisionMessage(
|
||||
['firstname' => 'A', 'lastname' => 'B', 'dob' => '2010-01-01'],
|
||||
['decision' => 'Expel', 'source' => 'manual'],
|
||||
'2026-2027'
|
||||
);
|
||||
$withdrawn = EnrollmentEligibility::parentDecisionMessage(
|
||||
['firstname' => 'A', 'lastname' => 'B', 'dob' => '2010-01-01'],
|
||||
['decision' => 'Widthrwan', 'source' => 'manual'],
|
||||
'2026-2027'
|
||||
);
|
||||
$deferred = EnrollmentEligibility::parentDecisionMessage(
|
||||
['firstname' => 'A', 'lastname' => 'B', 'dob' => '2010-01-01'],
|
||||
['decision' => 'Deferred', 'source' => 'manual'],
|
||||
'2026-2027'
|
||||
);
|
||||
|
||||
$this->assertTrue($expelled['blocking']);
|
||||
$this->assertTrue($withdrawn['blocking']);
|
||||
$this->assertTrue($deferred['blocking']);
|
||||
$this->assertStringContainsString('A B', $expelled['message']);
|
||||
$this->assertStringContainsString('A B', $withdrawn['message']);
|
||||
$this->assertStringContainsString('A B', $deferred['message']);
|
||||
$this->assertStringContainsString(EnrollmentEligibility::EXPELLED_MESSAGE, $expelled['message']);
|
||||
$this->assertStringContainsString(EnrollmentEligibility::WITHDRAWN_MESSAGE, $withdrawn['message']);
|
||||
$this->assertStringContainsString(EnrollmentEligibility::DEFERRED_MESSAGE, $deferred['message']);
|
||||
$this->assertStringContainsString('decision is expelled', $expelled['message']);
|
||||
$this->assertStringContainsString('decision is withdrawn', $withdrawn['message']);
|
||||
$this->assertStringContainsString('decision is deferred', $deferred['message']);
|
||||
}
|
||||
|
||||
public function testPendingSourceUsesMissingDecisionMessageNotDeferredDecisionMessage(): void
|
||||
{
|
||||
$message = EnrollmentEligibility::parentDecisionMessage(
|
||||
['firstname' => 'A', 'lastname' => 'B', 'dob' => '2010-01-01'],
|
||||
['decision' => '', 'source' => 'pending'],
|
||||
'2026-2027'
|
||||
);
|
||||
|
||||
$this->assertTrue($message['blocking']);
|
||||
$this->assertStringContainsString('A B', $message['message']);
|
||||
$this->assertStringContainsString(EnrollmentEligibility::MISSING_DECISION_MESSAGE, $message['message']);
|
||||
$this->assertStringNotContainsString(EnrollmentEligibility::DEFERRED_MESSAGE, $message['message']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user