createResolver('2026-02-05 10:00:00'); $uuid = '550e8400-e29b-41d4-a716-446655440099'; self::assertSame($uuid, $resolver->resolve($uuid)); } #[Test] public function itReturnsNullForInvalidIdentifier(): void { $resolver = $this->createResolver('2026-02-05 10:00:00'); self::assertNull($resolver->resolve('invalid')); self::assertNull($resolver->resolve('')); self::assertNull($resolver->resolve('past')); } #[Test] public function itResolvesCurrentBeforeSeptember(): void { // February 2026 → school year 2025-2026 $resolver = $this->createResolver('2026-02-05 10:00:00'); $result = $resolver->resolve('current'); self::assertNotNull($result); self::assertTrue(\Ramsey\Uuid\Uuid::isValid($result)); } #[Test] public function itResolvesCurrentAfterSeptember(): void { // October 2025 → school year 2025-2026 $resolver = $this->createResolver('2025-10-15 10:00:00'); $result = $resolver->resolve('current'); self::assertNotNull($result); self::assertTrue(\Ramsey\Uuid\Uuid::isValid($result)); } #[Test] public function itResolvesSameUuidForSameSchoolYear(): void { // Both dates are in school year 2025-2026 $resolverOct = $this->createResolver('2025-10-15 10:00:00'); $resolverFeb = $this->createResolver('2026-02-05 10:00:00'); self::assertSame( $resolverOct->resolve('current'), $resolverFeb->resolve('current'), ); } #[Test] public function itResolvesDifferentUuidForDifferentSchoolYears(): void { // October 2025 → 2025-2026, October 2026 → 2026-2027 $resolver2025 = $this->createResolver('2025-10-15 10:00:00'); $resolver2026 = $this->createResolver('2026-10-15 10:00:00'); self::assertNotSame( $resolver2025->resolve('current'), $resolver2026->resolve('current'), ); } #[Test] public function itResolvesNextYear(): void { // February 2026, current = 2025-2026, next = 2026-2027 $resolver = $this->createResolver('2026-02-05 10:00:00'); $current = $resolver->resolve('current'); $next = $resolver->resolve('next'); self::assertNotNull($next); self::assertTrue(\Ramsey\Uuid\Uuid::isValid($next)); self::assertNotSame($current, $next); } #[Test] public function itResolvesPreviousYear(): void { // February 2026, current = 2025-2026, previous = 2024-2025 $resolver = $this->createResolver('2026-02-05 10:00:00'); $current = $resolver->resolve('current'); $previous = $resolver->resolve('previous'); self::assertNotNull($previous); self::assertTrue(\Ramsey\Uuid\Uuid::isValid($previous)); self::assertNotSame($current, $previous); } #[Test] public function nextOfCurrentYearMatchesCurrentOfNextYear(): void { // "next" from Feb 2026 should equal "current" from Oct 2026 $resolverFeb2026 = $this->createResolver('2026-02-05 10:00:00'); $resolverOct2026 = $this->createResolver('2026-10-15 10:00:00'); self::assertSame( $resolverFeb2026->resolve('next'), $resolverOct2026->resolve('current'), ); } #[Test] public function previousOfCurrentYearMatchesCurrentOfPreviousYear(): void { // "previous" from Feb 2026 (2024-2025) should equal "current" from Oct 2024 $resolverFeb2026 = $this->createResolver('2026-02-05 10:00:00'); $resolverOct2024 = $this->createResolver('2024-10-15 10:00:00'); self::assertSame( $resolverFeb2026->resolve('previous'), $resolverOct2024->resolve('current'), ); } #[Test] public function itResolvesDifferentUuidsForDifferentTenants(): void { $otherTenantUuid = '550e8400-e29b-41d4-a716-446655440099'; $resolver1 = $this->createResolver('2026-02-05 10:00:00', self::TENANT_UUID); $resolver2 = $this->createResolver('2026-02-05 10:00:00', $otherTenantUuid); self::assertNotSame( $resolver1->resolve('current'), $resolver2->resolve('current'), ); } #[Test] public function itIsDeterministic(): void { $resolver = $this->createResolver('2026-02-05 10:00:00'); self::assertSame( $resolver->resolve('current'), $resolver->resolve('current'), ); } #[Test] public function septemberBelongsToNewSchoolYear(): void { // September 1st 2026 should be in school year 2026-2027 $resolverSept = $this->createResolver('2026-09-01 08:00:00'); $resolverOct = $this->createResolver('2026-10-15 10:00:00'); self::assertSame( $resolverSept->resolve('current'), $resolverOct->resolve('current'), ); } #[Test] public function augustBelongsToPreviousSchoolYear(): void { // August 31st 2026 should still be in school year 2025-2026 $resolverAug = $this->createResolver('2026-08-31 23:59:59'); $resolverFeb = $this->createResolver('2026-02-05 10:00:00'); self::assertSame( $resolverAug->resolve('current'), $resolverFeb->resolve('current'), ); } private function createResolver(string $dateTime, string $tenantUuid = self::TENANT_UUID): CurrentAcademicYearResolver { $tenantContext = new TenantContext(); $tenantContext->setCurrentTenant(new TenantConfig( tenantId: TenantId::fromString($tenantUuid), subdomain: 'test', databaseUrl: 'sqlite:///:memory:', )); $clock = new class($dateTime) implements Clock { public function __construct(private readonly string $dateTime) { } public function now(): DateTimeImmutable { return new DateTimeImmutable($this->dateTime); } }; return new CurrentAcademicYearResolver($tenantContext, $clock); } }