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.
114 lines
3.8 KiB
PHP
114 lines
3.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Scolarite\Infrastructure\Cache;
|
|
|
|
use App\Scolarite\Domain\Model\Evaluation\ClassStatistics;
|
|
use App\Scolarite\Domain\Model\Evaluation\EvaluationId;
|
|
use App\Scolarite\Infrastructure\Cache\CachingEvaluationStatisticsRepository;
|
|
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryEvaluationStatisticsRepository;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
|
|
|
final class CachingEvaluationStatisticsRepositoryTest extends TestCase
|
|
{
|
|
private InMemoryEvaluationStatisticsRepository $inner;
|
|
private CachingEvaluationStatisticsRepository $cached;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->inner = new InMemoryEvaluationStatisticsRepository();
|
|
$this->cached = new CachingEvaluationStatisticsRepository(
|
|
inner: $this->inner,
|
|
cache: new ArrayAdapter(),
|
|
);
|
|
}
|
|
|
|
#[Test]
|
|
public function itCachesStatisticsOnRead(): void
|
|
{
|
|
$evaluationId = EvaluationId::generate();
|
|
$stats = new ClassStatistics(average: 14.5, min: 8.0, max: 19.0, median: 15.0, gradedCount: 5);
|
|
|
|
$this->inner->save($evaluationId, $stats);
|
|
|
|
// Premier appel : va au inner
|
|
$result1 = $this->cached->findByEvaluation($evaluationId);
|
|
self::assertNotNull($result1);
|
|
self::assertSame(14.5, $result1->average);
|
|
|
|
// Deuxième appel : devrait venir du cache (même résultat)
|
|
$result2 = $this->cached->findByEvaluation($evaluationId);
|
|
self::assertNotNull($result2);
|
|
self::assertSame(14.5, $result2->average);
|
|
}
|
|
|
|
#[Test]
|
|
public function itInvalidatesCacheOnSave(): void
|
|
{
|
|
$evaluationId = EvaluationId::generate();
|
|
$stats1 = new ClassStatistics(average: 14.0, min: 8.0, max: 19.0, median: 14.0, gradedCount: 3);
|
|
|
|
// Sauvegarder et lire pour remplir le cache
|
|
$this->cached->save($evaluationId, $stats1);
|
|
$this->cached->findByEvaluation($evaluationId);
|
|
|
|
// Mettre à jour
|
|
$stats2 = new ClassStatistics(average: 16.0, min: 10.0, max: 20.0, median: 16.0, gradedCount: 4);
|
|
$this->cached->save($evaluationId, $stats2);
|
|
|
|
$result = $this->cached->findByEvaluation($evaluationId);
|
|
self::assertNotNull($result);
|
|
self::assertSame(16.0, $result->average);
|
|
}
|
|
|
|
#[Test]
|
|
public function itReturnsNullForUnknownEvaluation(): void
|
|
{
|
|
$result = $this->cached->findByEvaluation(EvaluationId::generate());
|
|
self::assertNull($result);
|
|
}
|
|
|
|
#[Test]
|
|
public function itInvalidatesCacheOnDelete(): void
|
|
{
|
|
$evaluationId = EvaluationId::generate();
|
|
$stats = new ClassStatistics(average: 14.5, min: 8.0, max: 19.0, median: 15.0, gradedCount: 5);
|
|
|
|
$this->cached->save($evaluationId, $stats);
|
|
|
|
// Remplir le cache
|
|
$this->cached->findByEvaluation($evaluationId);
|
|
|
|
// Supprimer
|
|
$this->cached->delete($evaluationId);
|
|
|
|
// Le cache ne doit plus retourner l'ancienne valeur
|
|
self::assertNull($this->cached->findByEvaluation($evaluationId));
|
|
}
|
|
|
|
#[Test]
|
|
public function itCachesNullResultForUnknownEvaluation(): void
|
|
{
|
|
$evaluationId = EvaluationId::generate();
|
|
|
|
// Premier appel : null → mis en cache
|
|
self::assertNull($this->cached->findByEvaluation($evaluationId));
|
|
|
|
// Sauvegarder directement dans inner (sans passer par le cache)
|
|
$this->inner->save($evaluationId, new ClassStatistics(
|
|
average: 12.0,
|
|
min: 10.0,
|
|
max: 14.0,
|
|
median: 12.0,
|
|
gradedCount: 2,
|
|
));
|
|
|
|
// Le cache retourne encore null (valeur cachée)
|
|
$result = $this->cached->findByEvaluation($evaluationId);
|
|
self::assertNull($result);
|
|
}
|
|
}
|