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) { self::$pdo = 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 = [ '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]; } }