Paillasse/portail/tests/Feature/CacheApplicatifTest.php
Adam Belkacemi ca544ac620 ajoute le cache Redis sur les lectures lourdes (bloc B)
Catalogue (labs, banque de quiz officielle, cours) et liste des
classes du tableau de bord prof - jamais d'expiration, invalidation
explicite par les commandes de synchronisation et par
archiver/désarchiver/purge RGPD.

Bug trouvé en vérifiant en réel sur le serveur (pas en local, où
CACHE_STORE=array ne sérialise jamais rien) : mettre en cache des
modèles Eloquent hydratés plante à la relecture via Redis
("incomplete class" à l'unserialize). Corrigé en cachant les attributs
bruts et en reconstruisant de vrais modèles avec Model::hydrate().

Mesuré en réel : 5 appels directs = 5 requêtes SQL, 5 appels via le
cache = 1 requête SQL.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-14 07:34:55 +02:00

84 lines
3.5 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
use App\Models\Classe;
use App\Models\Cours;
use App\Models\Lab;
use App\Models\Quiz;
use App\Models\User;
use App\Services\CacheTableauDeBordProf;
use App\Services\Catalogue\CacheCatalogue;
test('CacheCatalogue::labs() est en cache tant que viderLabs() n’est pas appelé', function () {
Lab::factory()->create(['identifiant' => 'a-lab']);
expect(CacheCatalogue::labs())->toHaveCount(1);
Lab::factory()->create(['identifiant' => 'b-lab']);
expect(CacheCatalogue::labs())->toHaveCount(1); // toujours la version en cache
CacheCatalogue::viderLabs();
expect(CacheCatalogue::labs())->toHaveCount(2);
});
test('CacheCatalogue::quizOfficiels() est en cache tant que viderQuizOfficiels() n’est pas appelé', function () {
$classe = Classe::factory()->create();
Quiz::create(['classe_id' => $classe->id, 'titre' => 'Un', 'theme' => 'NET', 'partage' => true]);
expect(CacheCatalogue::quizOfficiels())->toHaveCount(1);
Quiz::create(['classe_id' => $classe->id, 'titre' => 'Deux', 'theme' => 'LIN', 'partage' => true]);
expect(CacheCatalogue::quizOfficiels())->toHaveCount(1);
CacheCatalogue::viderQuizOfficiels();
expect(CacheCatalogue::quizOfficiels())->toHaveCount(2);
});
test('CacheCatalogue::cours() est en cache tant que viderCours() n’est pas appelé', function () {
Cours::create(['identifiant' => 'a', 'titre' => 'Un', 'contenu_markdown' => 'x']);
expect(CacheCatalogue::cours())->toHaveCount(1);
Cours::create(['identifiant' => 'b', 'titre' => 'Deux', 'contenu_markdown' => 'x']);
expect(CacheCatalogue::cours())->toHaveCount(1);
CacheCatalogue::viderCours();
expect(CacheCatalogue::cours())->toHaveCount(2);
});
test('CacheTableauDeBordProf::classes() est isolé par prof et vidé par vider()', function () {
$profA = User::factory()->create();
$profB = User::factory()->create();
Classe::factory()->create(['user_id' => $profA->id]);
Classe::factory()->create(['user_id' => $profB->id]);
expect(CacheTableauDeBordProf::classes($profA->id))->toHaveCount(1)
->and(CacheTableauDeBordProf::classes($profB->id))->toHaveCount(1);
Classe::factory()->create(['user_id' => $profA->id]);
expect(CacheTableauDeBordProf::classes($profA->id))->toHaveCount(1); // en cache
CacheTableauDeBordProf::vider($profA->id);
expect(CacheTableauDeBordProf::classes($profA->id))->toHaveCount(2)
->and(CacheTableauDeBordProf::classes($profB->id))->toHaveCount(1); // l'autre prof n'a pas bougé
});
test('archiver une classe invalide le cache tout de suite (pas de statut périmé sur /mes-classes)', function () {
$prof = User::factory()->create();
$classe = Classe::factory()->create(['user_id' => $prof->id]);
prof($this, $prof)->get(route('mes-classes.index'))->assertOk(); // remplit le cache
prof($this, $prof)->post(route('mes-classes.archiver', $classe))->assertRedirect();
$page = prof($this, $prof)->get(route('mes-classes.index'));
$page->assertOk()->assertSee('archivée');
});
test('purger une classe invalide le cache du prof : elle disparaît tout de suite de /mes-classes', function () {
$prof = User::factory()->create();
$classe = Classe::factory()->create(['user_id' => $prof->id, 'nom' => 'À purger']);
prof($this, $prof)->get(route('mes-classes.index'))->assertSee('À purger'); // remplit le cache
$classe->forceFill(['archivee_a' => now()->subDays(91)])->save();
$this->artisan('classes:purger-archivees');
prof($this, $prof)->get(route('mes-classes.index'))->assertDontSee('À purger');
});