Files
Classeo/backend/tests/Unit/Administration/Infrastructure/Service/CurrentAcademicYearResolverTest.php
Mathias STRASSER f19d0ae3ef feat: Gestion des périodes scolaires
L'administration d'un établissement nécessite de découper l'année
scolaire en trimestres ou semestres avant de pouvoir saisir les notes
et générer les bulletins.

Ce module permet de configurer les périodes par année scolaire
(current/previous/next résolus en UUID v5 déterministes), de modifier
les dates individuelles avec validation anti-chevauchement, et de
consulter la période en cours avec le décompte des jours restants.

Les dates par défaut de février s'adaptent aux années bissextiles.
Le repository utilise UPSERT transactionnel pour garantir l'intégrité
lors du changement de mode (trimestres ↔ semestres). Les domain events
de Subject sont étendus pour couvrir toutes les mutations (code,
couleur, description) en plus du renommage.
2026-02-06 14:27:55 +01:00

215 lines
6.7 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Unit\Administration\Infrastructure\Service;
use App\Administration\Infrastructure\Service\CurrentAcademicYearResolver;
use App\Shared\Domain\Clock;
use App\Shared\Infrastructure\Tenant\TenantConfig;
use App\Shared\Infrastructure\Tenant\TenantContext;
use App\Shared\Infrastructure\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
final class CurrentAcademicYearResolverTest extends TestCase
{
private const string TENANT_UUID = '550e8400-e29b-41d4-a716-446655440001';
#[Test]
public function itPassesThroughValidUuid(): void
{
$resolver = $this->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);
}
}