Files
annu-kute-ced/tests/php/dons-sync-test.php
T

209 lines
7.0 KiB
PHP

<?php
/**
* Tests du groupe « dons » : synchronisation dons.php / dons.php.sample,
* gabarits d'erreur 404/500 et blocage des artefacts de documentation.
*
* ARC-7 : dons.php inclut 404.php / 500.php — les gabarits .sample existent
* et le chemin d'erreur affiche une page propre (sans warning PHP).
* SEC-6 : DEPLOY.adoc/html/pdf à la racine sont bloqués par les modèles
* nginx / Apache.
* Dérive : linkUrlsInText() présent dans dons.php comme dans le .sample.
*/
$root = dirname(__DIR__, 2);
// --- Synchronisation dons.php / dons.php.sample -----------------------------
$donsLive = file_get_contents($root . '/dons.php');
$donsSample = file_get_contents($root . '/dons.php.sample');
assertContains(
'function linkUrlsInText',
$donsLive,
'dons.php définit linkUrlsInText() comme le .sample'
);
assertContains(
'linkUrlsInText(DONATIONS_OKI_DISCLAIMER)',
$donsLive,
'dons.php rend le message de transparence avec linkUrlsInText()'
);
assertNotContains(
'htmlspecialchars(DONATIONS_OKI_DISCLAIMER)',
$donsLive,
'dons.php n\'utilise plus un simple htmlspecialchars() pour le message de transparence'
);
// La définition de la fonction doit être identique dans les deux fichiers
// (verrou anti-dérive entre le modèle et la copie d'instance)
$extractFunction = function (string $source): ?string {
if (preg_match('/(function linkUrlsInText\(string \$text\): string \{.*?\n\})/s', $source, $m)) {
return $m[1];
}
return null;
};
assertEquals(
$extractFunction($donsSample),
$extractFunction($donsLive),
'linkUrlsInText() est identique dans dons.php et dons.php.sample'
);
// --- linkUrlsInText() : comportement ----------------------------------------
if (!function_exists('linkUrlsInText')) {
eval($extractFunction($donsSample));
}
assertEquals(
'Texte &amp; &quot;échappé&quot;',
linkUrlsInText('Texte & "échappé"'),
'linkUrlsInText échappe le texte sans URL'
);
$linked = linkUrlsInText('Soutenez-nous sur https://example.com/dons merci');
assertContains(
'<a href="https://example.com/dons" target="_blank" rel="noopener noreferrer">https://example.com/dons</a>',
$linked,
'linkUrlsInText rend les URLs cliquables avec target et rel'
);
assertEquals(
'&lt;script&gt;alert(1)&lt;/script&gt;',
linkUrlsInText('<script>alert(1)</script>'),
'linkUrlsInText échappe le HTML injecté dans le texte'
);
assertContains(
'href="https://example.com/?a=1&amp;b=2"',
linkUrlsInText('Lien : https://example.com/?a=1&b=2'),
'linkUrlsInText échappe les & dans l\'URL du lien'
);
// --- Gabarits d'erreur 404.php.sample / 500.php.sample (ARC-7) ---------------
foreach (['404' => '404.php.sample', '500' => '500.php.sample'] as $code => $file) {
$path = $root . '/' . $file;
assertTrue(file_exists($path), "$file existe");
$template = file_get_contents($path);
assertContains(
"http_response_code($code)",
$template,
"$file positionne le code HTTP $code"
);
assertContains(
'noindex',
$template,
"$file demande la non-indexation (robots noindex)"
);
assertNotContains(
'style="',
$template,
"$file n'utilise pas d'attribut style inline (bloqué par la CSP)"
);
// Tout <script> inline (sans src) doit porter un nonce CSP
preg_match_all('/<script(?![^>]*\bsrc=)[^>]*>/i', $template, $inlineScripts);
$allNonced = true;
foreach ($inlineScripts[0] as $scriptTag) {
if (strpos($scriptTag, 'nonce=') === false) {
$allNonced = false;
}
}
assertTrue($allNonced, "$file : tous les scripts inline portent un nonce CSP");
// Le gabarit doit être syntaxiquement valide
$lint = shell_exec(escapeshellarg(PHP_BINARY) . ' -l ' . escapeshellarg($path) . ' 2>&1');
assertContains('No syntax errors', $lint, "$file passe php -l");
}
// --- Blocage des artefacts de documentation (SEC-6) --------------------------
$nginxConf = file_get_contents($root . '/conf/nginx.conf.sample');
assertContains(
'^/[^/]+\.(adoc|html|pdf)$',
$nginxConf,
'nginx.conf.sample bloque les .adoc/.html/.pdf à la racine web'
);
$htaccess = file_get_contents($root . '/conf/.htaccess.sample');
assertContains(
'RewriteRule ^[^/]+\.(adoc|html|pdf)$ - [F,L,NC]',
$htaccess,
'.htaccess.sample bloque les .adoc/.html/.pdf à la racine web'
);
// --- dons.php : chemins d'erreur 404 et 500 (exécution réelle) ---------------
// dons.php inclut 404.php / 500.php (copies d'instance, non versionnées) :
// on les crée à partir des .sample si elles sont absentes, puis on nettoie.
$createdCopies = [];
foreach (['404.php', '500.php'] as $copy) {
$copyPath = $root . '/' . $copy;
if (!file_exists($copyPath)) {
copy($root . '/' . $copy . '.sample', $copyPath);
$createdCopies[] = $copyPath;
}
}
/**
* Exécute dons.php dans un sous-processus PHP avec des constantes prédéfinies
* (PEERTUBE_URL privée : aucun appel réseau, cf. bootstrap.php).
*
* @param array $defines Constantes définies avant le chargement de dons.php
* @return string Sortie complète du sous-processus (stdout + stderr)
*/
$runDonsPage = function (array $defines) use ($root): string {
$lines = ['<?php'];
foreach ($defines as $name => $value) {
$lines[] = 'define(' . var_export($name, true) . ', ' . var_export($value, true) . ');';
}
$lines[] = 'chdir(' . var_export($root, true) . ');';
$lines[] = 'require ' . var_export($root . '/dons.php', true) . ';';
$runner = tempnam(sys_get_temp_dir(), 'dons-runner-') . '.php';
file_put_contents($runner, implode("\n", $lines) . "\n");
$output = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($runner) . ' 2>&1');
unlink($runner);
return $output ?? '';
};
$noPlatform = [
'PEERTUBE_URL' => 'http://127.0.0.1', // URL privée volontaire : bloque tout appel API réel
'APP_HOST_NAME' => 'test.local',
'DONATIONS_ENABLED' => true,
'LIBERAPAY_URL' => '',
'KOFI_URL' => '',
'STRIPE_ENABLED' => false,
];
$phpErrorNeedles = ['PHP Warning', 'PHP Notice', 'PHP Deprecated', 'PHP Fatal error',
'Fatal error', 'Warning:', 'Notice:', 'Deprecated:', 'Uncaught'];
// Dons activés mais aucune plateforme configurée -> page 500 propre
$output500 = $runDonsPage($noPlatform);
assertContains(
'<p class="error-code" aria-hidden="true">500</p>',
$output500,
'dons.php sans plateforme configurée affiche la page 500'
);
foreach ($phpErrorNeedles as $needle) {
assertNotContains($needle, $output500, "page 500 sans erreur PHP ($needle)");
}
// Dons désactivés -> page 404 propre
$output404 = $runDonsPage(['DONATIONS_ENABLED' => false] + $noPlatform);
assertContains(
'<p class="error-code" aria-hidden="true">404</p>',
$output404,
'dons.php avec dons désactivés affiche la page 404'
);
foreach ($phpErrorNeedles as $needle) {
assertNotContains($needle, $output404, "page 404 sans erreur PHP ($needle)");
}
foreach ($createdCopies as $copyPath) {
unlink($copyPath);
}