Files
xamxam/tests/TestDatabase.php
T
Pontoporeia a31e4ab307 Recherche insensible aux accents sur toutes les barres de recherche (publique + backoffice).
Ajoute une fonction SQLite UDF accfold() (app/src/AccentFolding.php) qui plie les caractères accentués latin vers leur base ASCII (é→e, ç→c, …), et l'applique à chaque condition de recherche côté colonne ET côté terme (accfold(column) LIKE accfold(:term)). Comme le runtime n'a ni intl (Normalizer) ni iconv, le repli passe par une map de translitération manuelle (Western Latin-1 + Latin Extended-A, NULL-safe).

Couvert :
- recherche publique (searchTheses / countSearchResults) : titre, sous-titre, synopsis, auteurs, promoteurs, tags ;
- popover étudiant (getThesesByAuthorName / getThesesForAuthors) : a.name ;
- recherche backoffice (getThesesList / getThesesListCount) : titre, sous-titre, identifiant, auteur.

L'UDF est enregistré dans Database::registerSqliteFunctions() (appelé par le constructeur et par le harnais de test TestDatabaseInstance). Évite aussi la dépréciation PHP 8.5 de PDO::sqliteCreateFunction() : la connexion est créée via \Pdo\Sqlite quand disponible (createFunction()) avec repli sur PDO (sqliteCreateFunction()) sur les anciens runtimes ; les tests passent de @dataProvider docblock à l'attribut #[DataProvider] (fin de la dépréciation PHPUnit). Détache .php-cs-fixer.cache (gitignoré) pour ne plus polluer le working copy.

294 tests PHPUnit verts sans dépréciation, phpstan niveau 5 OK.
2026-09-18 16:26:36 +02:00

152 lines
5.1 KiB
PHP

<?php
/**
* TestDatabase — helper for integration tests that need a real SQLite database.
*
* Creates an in-memory SQLite database, loads the full schema + seed data,
* and provides a Database instance connected to it. Teardown discards the DB.
*/
/**
* Thin Database subclass that accepts a pre-built PDO connection.
* Bypasses the normal path-based constructor.
*/
class TestDatabaseInstance extends Database
{
public function __construct(PDO $pdo)
{
// Inject PDO directly via reflection, then flag as ready.
// Because we bypass the parent constructor, we must also register the
// custom SQLite functions (e.g. accfold) it would otherwise install.
Database::registerSqliteFunctions($pdo);
$ref = new ReflectionProperty(Database::class, 'pdo');
$ref->setValue($this, $pdo);
$pathRef = new ReflectionProperty(Database::class, 'dbPath');
$pathRef->setValue($this, ':memory:');
}
}
class TestDatabase
{
private static ?PDO $pdo = null;
private static ?Database $db = null;
/**
* Get or create the shared test Database instance.
* Uses an in-memory SQLite DB so tests are fast and isolated.
*/
public static function getInstance(): Database
{
if (self::$db === null) {
// Since PHP 8.5, `PDO::sqliteCreateFunction()` is deprecated and the
// registration in Database::registerSqliteFunctions() uses
// `Pdo\Sqlite::createFunction()` when the connection is a `Pdo\Sqlite`.
// Build the same connection type here so the new API path is exercised.
self::$pdo = class_exists('Pdo\Sqlite')
? new Pdo\Sqlite('sqlite::memory:')
: new PDO('sqlite::memory:');
self::$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
self::$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
self::$pdo->exec('PRAGMA foreign_keys = ON');
self::$pdo->exec('PRAGMA journal_mode = MEMORY');
// Load schema
$schema = file_get_contents(APP_ROOT . '/storage/schema.sql');
if ($schema === false) {
throw new RuntimeException('Failed to read schema.sql');
}
self::$pdo->exec($schema);
// Create a Database wrapper injecting our PDO
self::$db = new TestDatabaseInstance(self::$pdo);
}
return self::$db;
}
/**
* Get the raw PDO connection (for queries that bypass the Database class).
*/
public static function getPDO(): PDO
{
// Ensure the DB is booted
self::getInstance();
return self::$pdo;
}
/**
* Reset all test data between tests.
* Preserves seed data (orientations, access types, etc.) but removes
* any theses, authors, tags, share links etc. created during a test.
*/
public static function resetData(): void
{
$pdo = self::getPDO();
// Order matters due to FK constraints
$tables = [
'one_time_tokens',
'file_access_audit',
'file_access_sessions',
'file_access_tokens',
'file_access_requests',
'thesis_files',
'thesis_tags',
'thesis_formats',
'thesis_languages',
'thesis_supervisors',
'thesis_authors',
'theses',
'share_links',
'tags',
'authors',
'supervisors',
'admin_audit_log',
'audit_log',
];
foreach ($tables as $table) {
$pdo->exec("DELETE FROM $table");
}
// Re-seed tags (some tests rely on tags existing)
try {
$pdo->exec('DELETE FROM tags WHERE deleted_at IS NOT NULL');
} catch (Exception $e) {
// tags table already empty
}
}
/**
* Seed some basic test data: an author, a thesis.
* Returns [authorId, thesisId].
*
* @return array{0: int, 1: int}
*/
public static function seedBasicThesis(string $title = 'Test Thesis', string $authorName = 'Test Author', int $year = 2024): array
{
$pdo = self::getPDO();
// Insert author
$pdo->prepare('INSERT INTO authors (name) VALUES (?)')->execute([$authorName]);
$authorId = (int)$pdo->lastInsertId();
// Insert thesis
$pdo->prepare(
"INSERT INTO theses (title, year, identifier, is_published, objet) VALUES (?, ?, ?, 1, 'tfe')"
)->execute([$title, $year, "$year-001"]);
$thesisId = (int)$pdo->lastInsertId();
// Link author
$pdo->prepare('INSERT INTO thesis_authors (thesis_id, author_id) VALUES (?, ?)')
->execute([$thesisId, $authorId]);
// Insert a cover file
$pdo->prepare(
"INSERT INTO thesis_files (thesis_id, file_type, file_path, file_name, file_size, mime_type) VALUES (?, 'cover', ?, ?, 0, 'image/jpeg')"
)->execute([$thesisId, "documents/$year-001/cover.jpg", 'cover.jpg']);
return [$authorId, $thesisId];
}
}