feat: Calculer automatiquement les moyennes après chaque saisie de notes
Les enseignants ont besoin de moyennes à jour immédiatement après la publication ou modification des notes, sans attendre un batch nocturne. Le système recalcule via Domain Events synchrones : statistiques d'évaluation (min/max/moyenne/médiane), moyennes matières pondérées (normalisation /20), et moyenne générale par élève. Les résultats sont stockés dans des tables dénormalisées avec cache Redis (TTL 5 min). Trois endpoints API exposent les données avec contrôle d'accès par rôle. Une commande console permet le backfill des données historiques au déploiement.
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Unit\Scolarite\Infrastructure\Cache;
|
||||
|
||||
use App\Administration\Domain\Model\Subject\SubjectId;
|
||||
use App\Administration\Domain\Model\User\UserId;
|
||||
use App\Scolarite\Infrastructure\Cache\CachingStudentAverageRepository;
|
||||
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryStudentAverageRepository;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||
|
||||
final class CachingStudentAverageRepositoryTest extends TestCase
|
||||
{
|
||||
private const string TENANT_ID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
|
||||
private const string STUDENT_ID = '22222222-2222-2222-2222-222222222222';
|
||||
private const string SUBJECT_ID = '66666666-6666-6666-6666-666666666666';
|
||||
private const string PERIOD_ID = '11111111-1111-1111-1111-111111111111';
|
||||
|
||||
private InMemoryStudentAverageRepository $inner;
|
||||
private CachingStudentAverageRepository $cached;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->inner = new InMemoryStudentAverageRepository();
|
||||
$this->cached = new CachingStudentAverageRepository(
|
||||
inner: $this->inner,
|
||||
cache: new ArrayAdapter(),
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itCachesSubjectAveragesOnRead(): void
|
||||
{
|
||||
$tenantId = TenantId::fromString(self::TENANT_ID);
|
||||
$studentId = UserId::fromString(self::STUDENT_ID);
|
||||
|
||||
$this->inner->saveSubjectAverage(
|
||||
$tenantId,
|
||||
$studentId,
|
||||
SubjectId::fromString(self::SUBJECT_ID),
|
||||
self::PERIOD_ID,
|
||||
15.0,
|
||||
3,
|
||||
);
|
||||
|
||||
$result1 = $this->cached->findSubjectAveragesForStudent($studentId, self::PERIOD_ID, $tenantId);
|
||||
$result2 = $this->cached->findSubjectAveragesForStudent($studentId, self::PERIOD_ID, $tenantId);
|
||||
|
||||
self::assertSame([15.0], $result1);
|
||||
self::assertSame([15.0], $result2);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itInvalidatesCacheOnSaveSubjectAverage(): void
|
||||
{
|
||||
$tenantId = TenantId::fromString(self::TENANT_ID);
|
||||
$studentId = UserId::fromString(self::STUDENT_ID);
|
||||
$subjectId = SubjectId::fromString(self::SUBJECT_ID);
|
||||
|
||||
$this->cached->saveSubjectAverage($tenantId, $studentId, $subjectId, self::PERIOD_ID, 14.0, 2);
|
||||
|
||||
// Remplir le cache
|
||||
$this->cached->findSubjectAveragesForStudent($studentId, self::PERIOD_ID, $tenantId);
|
||||
|
||||
// Mettre à jour → doit invalider le cache
|
||||
$this->cached->saveSubjectAverage($tenantId, $studentId, $subjectId, self::PERIOD_ID, 16.0, 3);
|
||||
|
||||
$result = $this->cached->findSubjectAveragesForStudent($studentId, self::PERIOD_ID, $tenantId);
|
||||
|
||||
self::assertSame([16.0], $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itReturnsEmptyArrayWhenNoAverages(): void
|
||||
{
|
||||
$result = $this->cached->findSubjectAveragesForStudent(
|
||||
UserId::fromString(self::STUDENT_ID),
|
||||
self::PERIOD_ID,
|
||||
TenantId::fromString(self::TENANT_ID),
|
||||
);
|
||||
|
||||
self::assertSame([], $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itDelegatesToInnerForGeneralAverage(): void
|
||||
{
|
||||
$tenantId = TenantId::fromString(self::TENANT_ID);
|
||||
$studentId = UserId::fromString(self::STUDENT_ID);
|
||||
|
||||
$this->cached->saveGeneralAverage($tenantId, $studentId, self::PERIOD_ID, 13.5);
|
||||
|
||||
$result = $this->cached->findGeneralAverageForStudent($studentId, self::PERIOD_ID, $tenantId);
|
||||
|
||||
self::assertSame(13.5, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itInvalidatesCacheOnDeleteSubjectAverage(): void
|
||||
{
|
||||
$tenantId = TenantId::fromString(self::TENANT_ID);
|
||||
$studentId = UserId::fromString(self::STUDENT_ID);
|
||||
$subjectId = SubjectId::fromString(self::SUBJECT_ID);
|
||||
|
||||
$this->cached->saveSubjectAverage($tenantId, $studentId, $subjectId, self::PERIOD_ID, 14.0, 2);
|
||||
|
||||
// Remplir le cache
|
||||
$this->cached->findSubjectAveragesForStudent($studentId, self::PERIOD_ID, $tenantId);
|
||||
|
||||
// Supprimer → doit invalider le cache
|
||||
$this->cached->deleteSubjectAverage($studentId, $subjectId, self::PERIOD_ID, $tenantId);
|
||||
|
||||
$result = $this->cached->findSubjectAveragesForStudent($studentId, self::PERIOD_ID, $tenantId);
|
||||
|
||||
self::assertSame([], $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itCachesMultipleSubjectAverages(): void
|
||||
{
|
||||
$tenantId = TenantId::fromString(self::TENANT_ID);
|
||||
$studentId = UserId::fromString(self::STUDENT_ID);
|
||||
$subject2Id = '77777777-7777-7777-7777-777777777777';
|
||||
|
||||
$this->cached->saveSubjectAverage(
|
||||
$tenantId,
|
||||
$studentId,
|
||||
SubjectId::fromString(self::SUBJECT_ID),
|
||||
self::PERIOD_ID,
|
||||
15.0,
|
||||
3,
|
||||
);
|
||||
$this->cached->saveSubjectAverage(
|
||||
$tenantId,
|
||||
$studentId,
|
||||
SubjectId::fromString($subject2Id),
|
||||
self::PERIOD_ID,
|
||||
12.0,
|
||||
2,
|
||||
);
|
||||
|
||||
$result = $this->cached->findSubjectAveragesForStudent($studentId, self::PERIOD_ID, $tenantId);
|
||||
|
||||
self::assertCount(2, $result);
|
||||
self::assertContains(15.0, $result);
|
||||
self::assertContains(12.0, $result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user