Files
cacti/tests/Unit/Plugin/PluginInstallResultTest.php
Thomas Vincent cf70ba4cf2 fix(plugins): [develop] restore lifecycle diagnostics, compatibility and error attribution (#8297)
* fix(plugins): honor version range boundaries and compatibility status

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(plugins): preserve null hook diagnostics and enforce dependency versions

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(plugins): report config failures and confine error attribution

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(plugins): guard entrypoint frames and bind realm filenames

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(plugins): propagate install hook failures on develop

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(plugins): match realm filenames literally

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* docs(changelog): defer release entries to final backlog PR

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test(plugins): add standard GPL headers

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* ci(coverage): require 80 percent of changed PHP statements

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* ci: share changed-code coverage contracts and enforce shell policy

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(ci): run request coverage with portable path matching

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* ci: retain bootstrap coverage and validate request dispatch

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test: synchronize native coverage fixture dispatch

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test: measure coverage at the exact PR revision

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-10-14 22:53:44 -04:00

84 lines
4.4 KiB
PHP

<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
| |
| This program is free software; you can redistribute it and/or |
| modify it under the terms of the GNU General Public License |
| as published by the Free Software Foundation; either version 2 |
| of the License, or (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
*/
require_once dirname(__DIR__, 2) . '/Helpers/CactiStubs.php';
require_once dirname(__DIR__, 3) . '/include/global.php';
require_once dirname(__DIR__, 2) . '/Helpers/FakeMySQLPDO.php';
test('failed install hooks stop setup and disable hooks while retaining recovery metadata (#8002)', function ($body, $expected) {
$keys = ['database_hostname', 'database_port', 'database_default', 'database_sessions', 'database_total_queries', 'config', 'database_log', 'database_last_error', 'affected_rows', 'error_logged', '_SESSION'];
$saved = [];
foreach ($keys as $key) {
$saved[$key] = [array_key_exists($key, $GLOBALS), $GLOBALS[$key] ?? null];
}
$name = 'cfg_' . bin2hex(random_bytes(5));
$directory = CACTI_PATH_PLUGINS . '/' . $name;
mkdir($directory);
try {
$db = new class extends FakeMySQLPDO {
public function prepare(string $query, array $options = []): PDOStatement|false {
return parent::prepare(str_replace('NOW()', 'CURRENT_TIMESTAMP', $query), $options);
}
};
$db->exec('CREATE TABLE user_auth (id INTEGER, username TEXT)');
$db->exec('CREATE TABLE settings (name TEXT PRIMARY KEY, value TEXT)');
$db->exec('CREATE TABLE plugin_config (id INTEGER PRIMARY KEY, directory TEXT, name TEXT, author TEXT, webpage TEXT, version TEXT, last_updated TEXT, status INTEGER DEFAULT 0)');
$db->exec('CREATE TABLE plugin_hooks (name TEXT, status INTEGER)');
$db->exec('CREATE TABLE poller (id INTEGER, last_status TEXT, disabled TEXT)');
$db->prepare('INSERT INTO plugin_hooks VALUES (?, 1)')->execute([$name]);
$GLOBALS['database_hostname'] = 'config-lifecycle-test';
$GLOBALS['database_port'] = 0;
$GLOBALS['database_default'] = 'config-lifecycle-test';
$GLOBALS['database_sessions'] = ['config-lifecycle-test:0:config-lifecycle-test' => $db];
$GLOBALS['database_total_queries'] = 0;
$_SESSION[SESS_MESSAGES] = [];
file_put_contents($directory . '/INFO', "[info]\nname = $name\nversion = 1.0\n");
$check = $expected ? 'return true;' : 'throw new RuntimeException("Configuration must not run after failed installation");';
file_put_contents($directory . '/setup.php', '<?php function plugin_' . $name . '_version() { return ["longname" => "Configuration fixture", "author" => "Cacti tests", "version" => "1.0"]; } function plugin_' . $name . '_install() {' . $body . '} function plugin_' . $name . '_check_config() {' . $check . '}');
expect(api_plugin_install($name))->toBe($expected);
expect((int) $db->query('SELECT status FROM plugin_config')->fetchColumn())->toBe($expected ? 4 : 2);
expect((int) $db->query('SELECT status FROM plugin_hooks')->fetchColumn())->toBe($expected ? 1 : 0);
if (!$expected) {
expect($_SESSION[SESS_MESSAGES]['install_error']['message'])->toContain($name);
expect($_SESSION[SESS_MESSAGES]['install_error']['level'])->toBe(MESSAGE_LEVEL_ERROR);
}
} finally {
foreach ($saved as $key => [$exists, $value]) {
if ($exists) {
$GLOBALS[$key] = $value;
} else {
unset($GLOBALS[$key]);
}
}
unlink($directory . '/setup.php');
unlink($directory . '/INFO');
rmdir($directory);
}
})->with([
['return false;', false],
['', true],
['return true;', true],
['throw new RuntimeException("Fixture install error");', false],
['throw new Error("Fixture install error");', false],
]);