setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Setup test database schema setupTestDatabaseSchema(self::$pdo); // Store original plugin-owned tables (if any) to clean up later self::$originalTables = self::listPluginTables(); // Set up App::db() for PluginManager (it now uses App API) App::set('db', self::$pdo); // Also set $GLOBALS['db'] for legacy fallback tests $GLOBALS['db'] = self::$pdo; // Initialize PluginManager catalog $pluginsDir = __DIR__ . '/../../../plugins'; PluginManager::load($pluginsDir); } public function testDatabasePluginStateManagement(): void { // Test initial state $initialState = PluginManager::isEnabled(self::$testPlugin); $this->assertIsBool($initialState, 'PluginManager::isEnabled should return boolean'); // Test enabling plugin via database $enableResult = PluginManager::setEnabled(self::$testPlugin, true); $this->assertTrue($enableResult, 'Should be able to enable plugin'); // Verify state is persisted in database $stmt = self::$pdo->prepare('SELECT `value` FROM settings WHERE `key` = :key LIMIT 1'); $key = 'plugin_enabled_' . self::$testPlugin; $stmt->execute([':key' => $key]); $result = $stmt->fetch(PDO::FETCH_ASSOC); $this->assertNotFalse($result, 'Plugin state should be stored in database'); $this->assertEquals('1', $result['value'], 'Enabled plugin should have value "1"'); // Test isEnabled reads from database $this->assertTrue(PluginManager::isEnabled(self::$testPlugin), 'Plugin should be enabled after database update'); // Test disabling plugin via database $disableResult = PluginManager::setEnabled(self::$testPlugin, false); $this->assertTrue($disableResult, 'Should be able to disable plugin'); // Verify state is updated in database $stmt->execute([':key' => $key]); $result = $stmt->fetch(PDO::FETCH_ASSOC); $this->assertNotFalse($result, 'Plugin state should still be in database'); $this->assertEquals('0', $result['value'], 'Disabled plugin should have value "0"'); // Test isEnabled reads disabled state $this->assertFalse(PluginManager::isEnabled(self::$testPlugin), 'Plugin should be disabled after database update'); } public function testPluginInstall(): void { // Ensure plugin is disabled first PluginManager::setEnabled(self::$testPlugin, false); $beforeTables = self::listPluginTables(); $installResult = PluginManager::install(self::$testPlugin); $this->assertTrue($installResult, 'Plugin installation should succeed'); $afterTables = self::listPluginTables(); $this->assertSame($beforeTables, $afterTables, 'Register plugin should not create dedicated tables'); } public function testPluginPurge(): void { // First install the plugin to have something to purge PluginManager::install(self::$testPlugin); // Test plugin purge $purgeResult = PluginManager::purge(self::$testPlugin); $this->assertTrue($purgeResult, 'Plugin purge should succeed'); // Mark plugin as enabled so purge has work to do PluginManager::setEnabled(self::$testPlugin, true); $stmt = self::$pdo->prepare('SELECT `value` FROM settings WHERE `key` = :key LIMIT 1'); $key = 'plugin_enabled_' . self::$testPlugin; $stmt->execute([':key' => $key]); $this->assertEquals('1', $stmt->fetchColumn(), 'Plugin should be enabled before purge'); // Verify plugin purge $purgeResult = PluginManager::purge(self::$testPlugin); $this->assertTrue($purgeResult, 'Plugin purge should succeed'); $stmt->execute([':key' => $key]); $this->assertFalse($stmt->fetch(), 'Plugin settings should be removed after purge'); $this->assertFalse(PluginManager::isEnabled(self::$testPlugin), 'Plugin should be disabled after purge'); $this->assertSame(self::$originalTables, self::listPluginTables(), 'Register plugin should not leave residual tables'); } public function testFallbackToManifestWhenDatabaseUnavailable(): void { // Temporarily unset both App::db() and global database connection $originalDb = $GLOBALS['db'] ?? null; unset($GLOBALS['db']); App::reset('db'); // Test fallback to manifest $result = PluginManager::isEnabled(self::$testPlugin); $this->assertIsBool($result, 'Should fallback to manifest when database unavailable'); // Restore database connection $GLOBALS['db'] = $originalDb; App::set('db', self::$pdo); } public static function tearDownAfterClass(): void { // Clean up test data if (self::$pdo) { // Remove plugin settings $stmt = self::$pdo->prepare('DELETE FROM settings WHERE `key` LIKE :pattern'); $stmt->execute([':pattern' => 'plugin_enabled_' . self::$testPlugin]); $currentTables = self::listPluginTables(); if (!empty($currentTables)) { self::$pdo->exec('SET FOREIGN_KEY_CHECKS=0'); foreach ($currentTables as $table) { if (!in_array($table, self::$originalTables, true)) { self::$pdo->exec("DROP TABLE IF EXISTS `$table`"); } } self::$pdo->exec('SET FOREIGN_KEY_CHECKS=1'); } // Clean up PDO connection to prevent serialization errors self::$pdo = null; } // Reset App state App::reset('db'); } private static function listPluginTables(): array { if (!self::$pdo) { return []; } $pattern = str_replace("'", "\\'", self::$tablePattern); $stmt = self::$pdo->query("SHOW TABLES LIKE '$pattern'"); return $stmt->fetchAll(PDO::FETCH_COLUMN, 0); } }