setMethod($method); } public function getGet($key = null, $filter = null, $default = null) { if ($key === null) { return $this->gets; } 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 { private array $filters = []; public function __construct(private readonly array $rows) { } public function find($id = null) { return $this->rows[(int) $id] ?? null; } public function active(): ?array { foreach ($this->rows as $row) { if (($row['status'] ?? '') === 'active') { return $row; } } return null; } public function where($key, $value = null, ?bool $escape = null) { $this->filters[] = [$key, $value]; return $this; } public function orderBy($orderBy, string $direction = '', ?bool $escape = null) { return $this; } public function findAll(?int $limit = null, int $offset = 0) { $rows = array_values(array_filter($this->rows, function (array $row): bool { foreach ($this->filters as [$key, $value]) { if (($row[$key] ?? null) !== $value) { return false; } } return true; })); $this->filters = []; 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 { protected function setUp(): void { parent::setUp(); if (method_exists(session(), 'isStarted') && ! session()->isStarted()) { session()->start(); } } public function testClosedSessionSelectionIsRetainedAsExplicitReadonlyContext(): void { session()->set('selected_school_year_id', 1); $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()); $this->assertSame(1, $context->id()); $this->assertSame('2025-2026', $context->yearName()); $this->assertTrue($context->isReadonly()); $this->assertTrue($context->isExplicitSelection()); $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); $service = new SchoolYearContextService(new SchoolYearContextFakeModel([ 2 => ['id' => 2, 'name' => '2026-2027', 'status' => 'active'], ])); $context = $service->resolve(new SchoolYearContextRequest()); $this->assertSame(2, $context->id()); $this->assertSame('2026-2027', $context->yearName()); $this->assertNull(session()->get('selected_school_year_id')); } public function testSelectingActiveYearClearsHistoricalOverride(): void { session()->set('selected_school_year_id', 1); session()->set('class_section_id', 44); session()->set('semester', 'Fall'); session()->set('active_school_year', '2025-2026'); $service = new SchoolYearContextService(new SchoolYearContextFakeModel([ 1 => ['id' => 1, 'name' => '2025-2026', 'status' => 'closed'], 2 => ['id' => 2, 'name' => '2026-2027', 'status' => 'Active'], ])); $context = $service->select(2); $this->assertSame(2, $context->id()); $this->assertTrue($context->isActive()); $this->assertFalse($context->isExplicitSelection()); $this->assertNull(session()->get('selected_school_year_id')); $this->assertNull(session()->get('class_section_id')); $this->assertNull(session()->get('semester')); $this->assertNull(session()->get('active_school_year')); } public function testSingleClosingYearIsUsedAsReadonlyContextWhenNoActiveYearExists(): void { $service = new SchoolYearContextService(new SchoolYearContextFakeModel([ 1 => ['id' => 1, 'name' => '2025-2026', 'status' => 'closing'], 2 => ['id' => 2, 'name' => '2026-2027', 'status' => 'draft'], ])); $context = $service->resolve(new SchoolYearContextRequest()); $this->assertSame(1, $context->id()); $this->assertSame('2025-2026', $context->yearName()); $this->assertSame('closing', $context->status()); $this->assertTrue($context->isReadonly()); $this->assertFalse($context->isExplicitSelection()); } }