Lorsqu'un super-admin crée un établissement via l'interface, le système doit automatiquement créer la base tenant, exécuter les migrations, créer le premier utilisateur admin et envoyer l'invitation — le tout de manière asynchrone pour ne pas bloquer la réponse HTTP. Ce mécanisme rend chaque établissement opérationnel dès sa création sans intervention manuelle sur l'infrastructure.
110 lines
3.8 KiB
PHP
110 lines
3.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Scolarite\Application\Query\GetClassStatisticsDetail;
|
|
|
|
use App\Scolarite\Application\Port\PeriodFinder;
|
|
use App\Scolarite\Application\Port\PeriodInfo;
|
|
use App\Scolarite\Application\Query\GetClassStatisticsDetail\GetClassStatisticsDetailHandler;
|
|
use App\Scolarite\Application\Query\GetClassStatisticsDetail\GetClassStatisticsDetailQuery;
|
|
use App\Scolarite\Domain\Service\AverageCalculator;
|
|
use App\Scolarite\Domain\Service\TeacherStatisticsCalculator;
|
|
use App\Scolarite\Infrastructure\ReadModel\InMemoryTeacherStatisticsReader;
|
|
use App\Shared\Domain\Tenant\TenantId;
|
|
use DateTimeImmutable;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class GetClassStatisticsDetailHandlerTest extends TestCase
|
|
{
|
|
private const string TEACHER_ID = '550e8400-e29b-41d4-a716-446655440010';
|
|
private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
|
|
private const string CLASS_ID = '550e8400-e29b-41d4-a716-446655440020';
|
|
private const string SUBJECT_ID = '550e8400-e29b-41d4-a716-446655440030';
|
|
|
|
private InMemoryTeacherStatisticsReader $reader;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->reader = new InMemoryTeacherStatisticsReader();
|
|
}
|
|
|
|
#[Test]
|
|
public function itReturnsEmptyWhenNoPeriodFound(): void
|
|
{
|
|
$handler = $this->createHandler(periodInfo: null);
|
|
|
|
$result = $handler($this->query());
|
|
|
|
self::assertNull($result->average);
|
|
self::assertSame(0.0, $result->successRate);
|
|
self::assertSame([], $result->students);
|
|
}
|
|
|
|
#[Test]
|
|
public function itComputesClassStatisticsFromGrades(): void
|
|
{
|
|
$this->reader->feedClassGrades([8.0, 10.0, 12.0, 14.0, 16.0]);
|
|
$this->reader->feedMonthlyAverages([
|
|
['month' => '2026-01', 'average' => 11.0],
|
|
['month' => '2026-02', 'average' => 12.5],
|
|
]);
|
|
$this->reader->feedStudentAverages([
|
|
['studentId' => 's1', 'studentName' => 'Alice Dupont', 'average' => 14.0],
|
|
['studentId' => 's2', 'studentName' => 'Bob Martin', 'average' => 7.0],
|
|
]);
|
|
|
|
$handler = $this->createHandler(periodInfo: $this->currentPeriod());
|
|
$result = $handler($this->query());
|
|
|
|
self::assertSame(12.0, $result->average);
|
|
self::assertSame(80.0, $result->successRate); // 4/5 >= 10
|
|
self::assertSame([0, 0, 0, 1, 2, 1, 1, 0], $result->distribution);
|
|
self::assertCount(2, $result->evolution);
|
|
self::assertCount(2, $result->students);
|
|
self::assertFalse($result->students[0]->inDifficulty); // Alice 14.0 >= 8.0
|
|
self::assertTrue($result->students[1]->inDifficulty); // Bob 7.0 < 8.0
|
|
}
|
|
|
|
private function query(): GetClassStatisticsDetailQuery
|
|
{
|
|
return new GetClassStatisticsDetailQuery(
|
|
teacherId: self::TEACHER_ID,
|
|
classId: self::CLASS_ID,
|
|
subjectId: self::SUBJECT_ID,
|
|
tenantId: self::TENANT_ID,
|
|
);
|
|
}
|
|
|
|
private function createHandler(?PeriodInfo $periodInfo): GetClassStatisticsDetailHandler
|
|
{
|
|
$periodFinder = new class($periodInfo) implements PeriodFinder {
|
|
public function __construct(private readonly ?PeriodInfo $info)
|
|
{
|
|
}
|
|
|
|
public function findForDate(DateTimeImmutable $date, TenantId $tenantId): ?PeriodInfo
|
|
{
|
|
return $this->info;
|
|
}
|
|
};
|
|
|
|
return new GetClassStatisticsDetailHandler(
|
|
$this->reader,
|
|
$periodFinder,
|
|
new TeacherStatisticsCalculator(),
|
|
new AverageCalculator(),
|
|
);
|
|
}
|
|
|
|
private function currentPeriod(): PeriodInfo
|
|
{
|
|
return new PeriodInfo(
|
|
periodId: 'period-1',
|
|
startDate: new DateTimeImmutable('2026-01-05'),
|
|
endDate: new DateTimeImmutable('2026-03-31'),
|
|
);
|
|
}
|
|
}
|