Files
annu-kute-ced/tests/php/cache-test.php
T

188 lines
8.4 KiB
PHP
Raw Normal View History

<?php
/**
* Tests unitaires pour la classe SimpleAPICache (includes/simple-cache.php).
*
* Le répertoire de cache est redirigé vers un dossier temporaire via
* réflexion afin de ne jamais toucher au cache réel (cache/api).
*/
$cache = new SimpleAPICache();
$cacheDirProperty = new ReflectionProperty(SimpleAPICache::class, 'cacheDir');
$cacheDirProperty->setAccessible(true);
$tmpDir = sys_get_temp_dir() . '/annu-kute-cache-test-' . getmypid();
if (!is_dir($tmpDir)) {
mkdir($tmpDir, 0755, true);
}
$cacheDirProperty->setValue($cache, $tmpDir);
// --- get sur un cache vide --------------------------------------------------
assertNull($cache->get('videos'), 'get retourne null quand aucune entrée n\'existe');
// --- set puis get -----------------------------------------------------------
$content = ['data' => ['video1', 'video2']];
$cache->set('videos', ['count' => 6], $content);
assertEquals($content, $cache->get('videos', ['count' => 6]), 'get retourne le contenu après set');
// Le fichier stocké contient bien content / expires / created
$files = glob($tmpDir . '/cache_*.json');
assertEquals(1, count($files), 'set crée exactement un fichier de cache');
$stored = json_decode(file_get_contents($files[0]), true);
assertEquals($content, $stored['content'], 'le fichier stocke le contenu sous la clé "content"');
assertTrue(isset($stored['expires']) && $stored['expires'] > time(), 'le fichier stocke une expiration future');
assertTrue(isset($stored['created']), 'le fichier stocke la date de création');
// --- Ordre des paramètres ---------------------------------------------------
// ksort() dans getCacheKey : l'ordre des paramètres ne change pas la clé
$cache->set('videos', ['b' => 2, 'a' => 1], 'ordonné');
assertEquals(
'ordonné',
$cache->get('videos', ['a' => 1, 'b' => 2]),
'get retrouve l\'entrée malgré un ordre de paramètres différent'
);
assertNull(
$cache->get('videos', ['a' => 9]),
'get retourne null pour des paramètres différents'
);
// --- Expiration -------------------------------------------------------------
$cache->set('videos', ['old' => 1], 'périmé', -1); // TTL négatif : déjà expiré
$filesBefore = count(glob($tmpDir . '/cache_*.json'));
assertNull($cache->get('videos', ['old' => 1]), 'get retourne null pour une entrée expirée');
assertEquals(
$filesBefore - 1,
count(glob($tmpDir . '/cache_*.json')),
'get supprime le fichier expiré'
);
// --- cleanup ----------------------------------------------------------------
$cache->set('cle-valide', [], 'valide', 300);
$cache->set('cle-perimee', [], 'périmée', -10);
$cleaned = $cache->cleanup();
assertEquals(1, $cleaned, 'cleanup supprime uniquement les entrées expirées');
assertEquals('valide', $cache->get('cle-valide'), 'cleanup conserve les entrées valides');
assertNull($cache->get('cle-perimee'), 'cleanup a bien supprimé l\'entrée expirée');
// --- Écriture verrouillée (LOCK_EX) ------------------------------------------
// Verrou structurel : set() écrit avec un verrou exclusif
$cacheSource = file_get_contents(dirname(__DIR__, 2) . '/includes/simple-cache.php');
assertContains('LOCK_EX', $cacheSource, 'set() écrit avec file_put_contents et LOCK_EX');
// Intégrité d'écriture : un contenu volumineux est relu sans troncature
$bigContent = ['payload' => str_repeat('données-€-', 5000)];
$cache->set('lock-test', [], $bigContent);
assertEquals($bigContent, $cache->get('lock-test'), 'une écriture volumineuse est relue intégralement');
// --- getPeerTubeCacheTtl() : correspondance exacte puis préfixe --------------
assertEquals(3600, getPeerTubeCacheTtl('videos/categories'), 'catégories : TTL 1 heure');
assertEquals(600, getPeerTubeCacheTtl('videos'), 'liste des vidéos : TTL 10 minutes');
assertEquals(600, getPeerTubeCacheTtl('search/videos'), 'recherche : TTL 10 minutes (match exact)');
assertEquals(900, getPeerTubeCacheTtl('wp-posts'), 'WordPress : TTL 15 minutes');
assertEquals(300, getPeerTubeCacheTtl('accounts'), 'comptes : TTL 5 minutes');
assertEquals(
300,
getPeerTubeCacheTtl('accounts/membre/videos'),
'vidéos d\'un compte (lives) : TTL 5 minutes — l\'ancien strpos capturait « videos » à 10 minutes'
);
assertEquals(600, getPeerTubeCacheTtl('videos/9cf2e3a1-abc'), 'détail vidéo : TTL 10 minutes');
assertEquals(600, getPeerTubeCacheTtl('videos/9cf2e3a1-abc/comment-threads'), 'commentaires : TTL 10 minutes');
assertEquals(600, getPeerTubeCacheTtl('video-channels/ma-chaine/videos'), 'vidéos d\'une chaîne : TTL 10 minutes');
assertEquals(300, getPeerTubeCacheTtl('endpoint-inconnu'), 'endpoint inconnu : TTL par défaut de 5 minutes');
// --- clear() : purge complète -------------------------------------------------
$cache->clear(); // repartir d'un dossier vide
$cache->set('purge-1', [], 'un');
$cache->set('purge-2', [], 'deux', 300);
assertEquals(2, count(glob($tmpDir . '/cache_*.json')), 'deux entrées présentes avant purge');
$deleted = $cache->clear();
assertEquals(2, $deleted, 'clear() supprime toutes les entrées, même valides');
assertEquals(0, count(glob($tmpDir . '/cache_*.json')), 'le dossier est vide après clear()');
assertNull($cache->get('purge-1'), 'une entrée purgée n\'est plus lisible');
// --- CACHE_ENABLED honoré par callPeerTubeApiCached ---------------------------
// Impossible de redéfinir une constante dans ce processus (CACHE_ENABLED=true
// dans bootstrap.php) : on teste dans un sous-processus PHP avec un stub de
// callPeerTubeApiOriginal qui compte les appels réels.
$root = dirname(__DIR__, 2);
$runCacheSnippet = function (string $code): string {
$runner = tempnam(sys_get_temp_dir(), 'cache-runner-') . '.php';
file_put_contents($runner, "<?php\n" . $code . "\n");
$output = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($runner) . ' 2>&1');
unlink($runner);
return $output ?? '';
};
$flagTestDir = sys_get_temp_dir() . '/annu-kute-cache-flag-' . getmypid();
mkdir($flagTestDir, 0755, true);
$snippetTemplate = <<<'PHP'
define('CACHE_ENABLED', %s);
require %s;
$GLOBALS['simple_api_cache'] = new SimpleAPICache(%s);
$GLOBALS['api_calls'] = 0;
function callPeerTubeApiOriginal($endpoint, $params = []) {
$GLOBALS['api_calls']++;
return ['data' => ['appel-' . $GLOBALS['api_calls']]];
}
callPeerTubeApiCached('videos-test-flag');
callPeerTubeApiCached('videos-test-flag');
echo $GLOBALS['api_calls'];
PHP;
$simpleCachePath = var_export($root . '/includes/simple-cache.php', true);
$flagDirExport = var_export($flagTestDir, true);
// Cache désactivé : deux appels API réels, aucune écriture de cache
$outDisabled = trim($runCacheSnippet(sprintf($snippetTemplate, 'false', $simpleCachePath, $flagDirExport)));
assertEquals('2', $outDisabled, 'CACHE_ENABLED=false : chaque appel va à l\'API (pas de lecture cache)');
assertEquals(0, count(glob($flagTestDir . '/cache_*.json')), 'CACHE_ENABLED=false : aucune écriture de cache');
// Cache activé : le second appel est servi par le cache
$outEnabled = trim($runCacheSnippet(sprintf($snippetTemplate, 'true', $simpleCachePath, $flagDirExport)));
assertEquals('1', $outEnabled, 'CACHE_ENABLED=true : le second appel est servi par le cache');
assertEquals(1, count(glob($flagTestDir . '/cache_*.json')), 'CACHE_ENABLED=true : une entrée écrite en cache');
foreach (glob($flagTestDir . '/cache_*.json') as $file) {
unlink($file);
}
rmdir($flagTestDir);
// --- scripts/purge-cache.php ---------------------------------------------------
$purgeDir = sys_get_temp_dir() . '/annu-kute-purge-' . getmypid();
mkdir($purgeDir, 0755, true);
file_put_contents($purgeDir . '/cache_a.json', '{}');
file_put_contents($purgeDir . '/cache_b.json', '{}');
file_put_contents($purgeDir . '/cache_c.json', '{}');
file_put_contents($purgeDir . '/autre.txt', 'conservé'); // ne doit pas être supprimé
$purgeOutput = shell_exec(
escapeshellarg(PHP_BINARY) . ' ' .
escapeshellarg($root . '/scripts/purge-cache.php') . ' ' .
escapeshellarg($purgeDir) . ' 2>&1'
) ?? '';
assertContains('Cache purgé : 3', $purgeOutput, 'le script rapporte les 3 entrées supprimées');
assertEquals(0, count(glob($purgeDir . '/cache_*.json')), 'le script supprime tous les fichiers de cache');
assertTrue(file_exists($purgeDir . '/autre.txt'), 'le script conserve les fichiers hors cache');
unlink($purgeDir . '/autre.txt');
rmdir($purgeDir);
// --- Nettoyage du dossier temporaire ----------------------------------------
foreach (glob($tmpDir . '/cache_*.json') as $file) {
unlink($file);
}
rmdir($tmpDir);