2026-10-09 10:39:50 +02:00
|
|
|
|
<?php
|
|
|
|
|
|
|
2026-10-09 21:44:02 +02:00
|
|
|
|
use Illuminate\Support\Facades\Process;
|
2026-10-09 10:39:50 +02:00
|
|
|
|
use Illuminate\Support\Facades\Route;
|
|
|
|
|
|
|
|
|
|
|
|
// Toutes les pages publiques sans paramètre doivent s'afficher : la page
|
|
|
|
|
|
// /cours a été en erreur 500 de l'étape 8 à l'étape 12 sans que personne
|
|
|
|
|
|
// ne le voie.
|
|
|
|
|
|
test('chaque page publique s’affiche sans erreur', function () {
|
|
|
|
|
|
$pages = collect(Route::getRoutes())
|
|
|
|
|
|
->filter(fn ($r) => in_array('GET', $r->methods(), true)
|
|
|
|
|
|
&& $r->getName() !== null
|
|
|
|
|
|
&& ! str_contains($r->uri(), '{')
|
|
|
|
|
|
&& ! collect($r->gatherMiddleware())->contains(fn ($m) => is_string($m) && str_starts_with($m, 'auth')))
|
ajoute Moodle LTI 1.3 (V2), une 6e façon de rejoindre une classe
LTI authentifie l'élève et le fait rejoindre sa classe (même code que
le code/QR existant), pas lancer un lab précis - relecture du
CLAUDE.md qui liste LTI avec les autres façons d'ajouter des élèves.
packbackbooks/lti-1p3-tool (Apache-2.0, recommandé par le CLAUDE.md),
trois ponts vers Redis/cookies/MariaDB, élève retrouvé par identité
Moodle stable pour ne jamais créer de doublon. Enregistrement d'une
plateforme/d'un déploiement par commande Artisan (opération rare,
faite à la main par Adam), jamais une page d'administration.
Corrige au passage un vrai bug préexistant trouvé en vérifiant en réel
sur le serveur : withoutMiddleware(ValidateCsrfToken::class) sur une
route ne retire jamais le vrai middleware CSRF (ValidateCsrfToken est
un alias déprécié de PreventRequestForgery, pas la classe réellement
enregistrée) - /csp-rapport avait le même défaut depuis l'étape 14,
invisible faute de surveillance. Corrigé pour les deux par la bonne
méthode (validateCsrfTokens except, dans bootstrap/app.php).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-14 13:51:47 +02:00
|
|
|
|
->reject(fn ($r) => str_starts_with($r->uri(), 'horizon') || str_starts_with($r->uri(), '_') || str_starts_with($r->uri(), 'interne/') || str_starts_with($r->uri(), 'lti/connexion') || $r->uri() === 'up')
|
2026-10-09 22:18:53 +02:00
|
|
|
|
// Redirections voulues (ex. /statut vers le site de statut ; pages de
|
|
|
|
|
|
// suivi d'une demande pour rejoindre une classe, sans demande en cours).
|
|
|
|
|
|
->reject(fn ($r) => $r->getActionName() === '\\Illuminate\\Routing\\RedirectController'
|
|
|
|
|
|
|| in_array($r->getName(), ['rejoindre.attente', 'rejoindre.bienvenue'], true));
|
2026-10-09 10:39:50 +02:00
|
|
|
|
|
|
|
|
|
|
expect($pages)->not->toBeEmpty();
|
|
|
|
|
|
|
|
|
|
|
|
$echecs = $pages->mapWithKeys(fn ($r) => [$r->uri() => $this->get('/'.ltrim($r->uri(), '/'))->getStatusCode()])
|
|
|
|
|
|
->reject(fn ($code) => $code === 200);
|
|
|
|
|
|
|
|
|
|
|
|
expect($echecs->all())->toBe([]);
|
|
|
|
|
|
});
|
2026-10-09 21:07:25 +02:00
|
|
|
|
|
|
|
|
|
|
// Étape 14 : la politique de confidentialité affirme qu'aucune page ne fait
|
|
|
|
|
|
// partir l'adresse IP du visiteur chez un fournisseur de polices.
|
|
|
|
|
|
test('aucune page publique ne charge de police depuis un service externe', function () {
|
|
|
|
|
|
$pages = collect(Route::getRoutes())
|
|
|
|
|
|
->filter(fn ($r) => in_array('GET', $r->methods(), true) && $r->getName() !== null && ! str_contains($r->uri(), '{'))
|
2026-10-14 13:04:40 +02:00
|
|
|
|
->reject(fn ($r) => collect($r->gatherMiddleware())->contains(fn ($m) => is_string($m) && str_starts_with($m, 'auth')))
|
ajoute Moodle LTI 1.3 (V2), une 6e façon de rejoindre une classe
LTI authentifie l'élève et le fait rejoindre sa classe (même code que
le code/QR existant), pas lancer un lab précis - relecture du
CLAUDE.md qui liste LTI avec les autres façons d'ajouter des élèves.
packbackbooks/lti-1p3-tool (Apache-2.0, recommandé par le CLAUDE.md),
trois ponts vers Redis/cookies/MariaDB, élève retrouvé par identité
Moodle stable pour ne jamais créer de doublon. Enregistrement d'une
plateforme/d'un déploiement par commande Artisan (opération rare,
faite à la main par Adam), jamais une page d'administration.
Corrige au passage un vrai bug préexistant trouvé en vérifiant en réel
sur le serveur : withoutMiddleware(ValidateCsrfToken::class) sur une
route ne retire jamais le vrai middleware CSRF (ValidateCsrfToken est
un alias déprécié de PreventRequestForgery, pas la classe réellement
enregistrée) - /csp-rapport avait le même défaut depuis l'étape 14,
invisible faute de surveillance. Corrigé pour les deux par la bonne
méthode (validateCsrfTokens except, dans bootstrap/app.php).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-14 13:51:47 +02:00
|
|
|
|
->reject(fn ($r) => str_starts_with($r->uri(), 'interne/') || str_starts_with($r->uri(), 'lti/connexion'));
|
2026-10-09 21:07:25 +02:00
|
|
|
|
|
|
|
|
|
|
foreach ($pages as $route) {
|
|
|
|
|
|
$html = $this->get('/'.ltrim($route->uri(), '/'))->getContent();
|
|
|
|
|
|
expect($html)->not->toMatch('#fonts\.(googleapis|gstatic|bunny)\.(com|net)#', "page /{$route->uri()}");
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-10-09 21:44:02 +02:00
|
|
|
|
|
|
|
|
|
|
test('aucune feuille de style publique n’importe de ressource externe', function () {
|
|
|
|
|
|
foreach (glob(public_path('css/*.css')) as $fichier) {
|
|
|
|
|
|
expect(file_get_contents($fichier))->not->toMatch('#@import\s+url\(\s*["\']?https?://#', basename($fichier));
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// /ide a eu une erreur de syntaxe JavaScript de l'étape 8 à l'étape 14 (des
|
|
|
|
|
|
// « !! » devenus « . » dans la maquette) : chaque script en ligne de chaque
|
|
|
|
|
|
// page publique est maintenant analysé par Node.
|
|
|
|
|
|
test('les scripts en ligne des pages publiques sont du JavaScript valide', function () {
|
|
|
|
|
|
if (Process::run('node --version')->failed()) {
|
|
|
|
|
|
$this->markTestSkipped('Node absent');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$pages = collect(Route::getRoutes())
|
|
|
|
|
|
->filter(fn ($r) => in_array('GET', $r->methods(), true) && $r->getName() !== null && ! str_contains($r->uri(), '{'))
|
|
|
|
|
|
->reject(fn ($r) => collect($r->gatherMiddleware())->contains(fn ($m) => is_string($m) && str_starts_with($m, 'auth')))
|
2026-10-14 13:04:40 +02:00
|
|
|
|
->reject(fn ($r) => $r->getActionName() === '\Illuminate\Routing\RedirectController')
|
ajoute Moodle LTI 1.3 (V2), une 6e façon de rejoindre une classe
LTI authentifie l'élève et le fait rejoindre sa classe (même code que
le code/QR existant), pas lancer un lab précis - relecture du
CLAUDE.md qui liste LTI avec les autres façons d'ajouter des élèves.
packbackbooks/lti-1p3-tool (Apache-2.0, recommandé par le CLAUDE.md),
trois ponts vers Redis/cookies/MariaDB, élève retrouvé par identité
Moodle stable pour ne jamais créer de doublon. Enregistrement d'une
plateforme/d'un déploiement par commande Artisan (opération rare,
faite à la main par Adam), jamais une page d'administration.
Corrige au passage un vrai bug préexistant trouvé en vérifiant en réel
sur le serveur : withoutMiddleware(ValidateCsrfToken::class) sur une
route ne retire jamais le vrai middleware CSRF (ValidateCsrfToken est
un alias déprécié de PreventRequestForgery, pas la classe réellement
enregistrée) - /csp-rapport avait le même défaut depuis l'étape 14,
invisible faute de surveillance. Corrigé pour les deux par la bonne
méthode (validateCsrfTokens except, dans bootstrap/app.php).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-14 13:51:47 +02:00
|
|
|
|
->reject(fn ($r) => str_starts_with($r->uri(), 'interne/') || str_starts_with($r->uri(), 'lti/connexion'));
|
2026-10-09 21:44:02 +02:00
|
|
|
|
|
|
|
|
|
|
$erreurs = [];
|
|
|
|
|
|
foreach ($pages as $route) {
|
|
|
|
|
|
preg_match_all('#<script(?![^>]*\bsrc=)[^>]*>(.*?)</script>#s', $this->get('/'.ltrim($route->uri(), '/'))->getContent(), $scripts);
|
|
|
|
|
|
foreach ($scripts[1] as $i => $code) {
|
|
|
|
|
|
$fichier = tempnam(sys_get_temp_dir(), 'js').'.js';
|
|
|
|
|
|
file_put_contents($fichier, $code);
|
|
|
|
|
|
$resultat = Process::run(['node', '--check', $fichier]);
|
|
|
|
|
|
unlink($fichier);
|
|
|
|
|
|
if ($resultat->failed()) {
|
|
|
|
|
|
$erreurs[] = "/{$route->uri()} script {$i} : ".strtok($resultat->errorOutput(), "\n");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
expect($erreurs)->toBe([]);
|
|
|
|
|
|
});
|