fix: Fixing Unit tests (#6790)

* fix: Fixing Unit tests

* fix: Include helper remove the defines

* Enable Pest tests for validation

* Update tests/Unit/ScriptsHardeningTest.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update tests/Helpers/CactiStubs.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update tests/Unit/SsHstatsTest.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update rector.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update tests/Unit/HtmlEscapeTest.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: Remove comments that are no longer required

* fix: Move xdebug to the correct location

* fix: Be consistent with includes

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
TheWitnessandCopilot authored and GitHub committed 2026-03-07 12:57:00 -05:00
1 parent 44c66664ed
commit 9d8bf959f5
27 files changed
+92 -429

No files matched your search

+3 -3
View File
@@ -108,9 +108,9 @@ jobs:
run: composer run-script phpstan
working-directory: .
# - name: Run Pest Unit Tests
# run: composer run-script test
# working-directory: .
- name: Run Pest Unit Tests
run: composer run-script test
working-directory: .
- name: Install github if not already installed
run: sudo apt-get -y install git
+1 -1
View File
@@ -1,3 +1,3 @@
<?php
define('PHP_STAN', true);
define('PHP_TESTING', true);
+1
View File
@@ -63,6 +63,7 @@
"phpstan/phpstan": "^2.1.21",
"pestphp/pest": "^2",
"pestphp/pest-plugin-drift": "^2.0",
"composer/xdebug-handler": "^3.0",
"rector/rector": "^2.1.2"
},
"scripts": {
+5 -5
View File
@@ -123,7 +123,7 @@ if (file_exists(__DIR__ . '/config.php')) {
include(__DIR__ . '/config.php');
}
if (defined('PHP_STAN') && file_exists(__DIR__ . '/config.php.dist')) {
if (defined('PHP_TESTING') && file_exists(__DIR__ . '/config.php.dist')) {
if (!is_readable(__DIR__ . '/config.php.dist')) {
die('Configuration file include/config.php is present, but unreadable.' . PHP_EOL);
}
@@ -313,7 +313,7 @@ $lu = $config['is_web'] ? '</ul>' : '';
$il = $config['is_web'] ? '</li>' : '';
if ($config['poller_id'] > 1 || isset($rdatabase_hostname)) {
if (!defined('PHP_STAN')) {
if (!defined('PHP_TESTING')) {
$local_db_cnn_id = db_connect_real($database_hostname, $database_username, $database_password, $database_default, $database_type, $database_port, $database_retries, $database_ssl, $database_ssl_key, $database_ssl_cert, $database_ssl_ca, $database_ssl_capath, $database_ssl_verify_server_cert);
}
@@ -363,7 +363,7 @@ if ($config['poller_id'] > 1 || isset($rdatabase_hostname)) {
* a remote poller, let's attempt to get back online.
*/
if ($conn_mode != 'offline') {
if (!defined('PHP_STAN')) {
if (!defined('PHP_TESTING')) {
$remote_db_cnn_id = db_connect_real($rdatabase_hostname, $rdatabase_username, $rdatabase_password, $rdatabase_default, $rdatabase_type, $rdatabase_port, $database_retries, $rdatabase_ssl, $rdatabase_ssl_key, $rdatabase_ssl_cert, $rdatabase_ssl_ca, $rdatabase_ssl_capath, $rdatabase_ssl_verify_server_cert);
}
}
@@ -389,7 +389,7 @@ if ($config['poller_id'] > 1 || isset($rdatabase_hostname)) {
$config['connection'] = 'offline';
}
} else {
if (!defined('PHP_STAN')) {
if (!defined('PHP_TESTING')) {
if (!db_connect_real($database_hostname, $database_username, $database_password, $database_default, $database_type, $database_port, $database_retries, $database_ssl, $database_ssl_key, $database_ssl_cert, $database_ssl_ca, $database_ssl_capath, $database_ssl_verify_server_cert)) {
print $ps . 'FATAL: Connection to Cacti database failed. Please ensure: ' . $ul;
print $li . 'the PHP MySQL module is installed and enabled.' . $il;
@@ -410,7 +410,7 @@ if ($config['poller_id'] > 1 || isset($rdatabase_hostname)) {
}
}
if (!defined('PHP_STAN')) {
if (!defined('PHP_TESTING')) {
if (!db_table_exists('settings') || !db_table_exists('version')) {
print $ps . 'FATAL: Connection to Cacti database succeeded but `settings` table not found. Please ensure: ' . $ul;
print $li . 'the PHP MySQL module is installed and enabled.' . $il;
+1 -1
View File
@@ -162,7 +162,7 @@ function db_connect_real(string $device, string $user, string $pass, string $db_
];
// test if cacti database is imported from SQL file
if (!defined('PHP_STAN')) {
if (!defined('PHP_TESTING')) {
$table_exists = db_fetch_cell("SELECT count(*) FROM information_schema.tables
WHERE table_schema = DATABASE() and TABLE_NAME = 'version'");
+3 -3
View File
@@ -2247,10 +2247,10 @@ function is_hex_string(string &$result) : bool {
* Hex- is considered due to the stripping of 'String:' in
* lib/snmp.php
*/
if (str_starts_with($compare, 'hex-')) {
$check = trim(str_ireplace('hex-', '', $result));
} elseif (str_starts_with($compare, 'hex-string:')) {
if (str_starts_with($compare, 'hex-string:')) {
$check = trim(str_ireplace('hex-string:', '', $result));
} elseif (str_starts_with($compare, 'hex-')) {
$check = trim(str_ireplace('hex-', '', $result));
} else {
return false;
}
+1 -1
View File
@@ -145,7 +145,7 @@ if (cacti_sizeof($parms)) {
}
// check for an invalid run location
if ($poller_id == 1 && !defined('PHP_STAN')) {
if ($poller_id == 1 && !defined('PHP_TESTING')) {
print "ERROR: This command is only to be run on remote Cacti Data Collectors\n";
exit(1);
+2
View File
@@ -2,6 +2,8 @@
declare(strict_types = 1);
include(__DIR__ . '/include/vendor/autoload.php');
use Rector\Config\RectorConfig;
use Rector\Php54\Rector\Array_\LongArrayToShortArrayRector;
+4 -4
View File
@@ -65,11 +65,11 @@ function ss_cpoller(string $cmd = 'index', string $arg1 = '', string $arg2 = '')
} elseif ($cmd == 'get') {
$arg = $arg1;
$index = $arg2;
$value = '0';
$value = 'U';
switch($arg) {
case 'recacheTime':
$value = '0';
$value = 'U';
$stats = explode(' ', db_fetch_cell_prepared('SELECT value FROM settings WHERE name = ?', ['stats_recache_' . $index]));
foreach ($stats as $_stat) {
@@ -81,7 +81,7 @@ function ss_cpoller(string $cmd = 'index', string $arg1 = '', string $arg2 = '')
break;
case 'recacheDevices':
$value = '0';
$value = 'U';
$stats = explode(' ', db_fetch_cell_prepared('SELECT value FROM settings WHERE name = ?', ['stats_recache_' . $index]));
foreach ($stats as $_stat) {
@@ -167,7 +167,7 @@ function ss_cpoller(string $cmd = 'index', string $arg1 = '', string $arg2 = '')
break;
}
return ($value == '' ? '0' : $value);
return ($value == '' ? 'U' : $value);
}
return 'U';
+3 -3
View File
@@ -62,13 +62,13 @@ function ss_hstats(int $host_id = 0, string $stat = '') : string {
$column = ss_hstats_map_stat_to_column($stat);
if ($column === null) {
return '0';
return 'U';
}
$allowed = ['polling_time', 'min_time', 'max_time', 'cur_time', 'avg_time', 'snmp_sysUpTimeInstance', 'failed_polls', 'availability', 'current_errors'];
if (!in_array($column, $allowed, true)) {
return '0';
return 'U';
}
if ($host_id > 0) {
@@ -82,5 +82,5 @@ function ss_hstats(int $host_id = 0, string $stat = '') : string {
return ($value == '' ? 'U' : $value);
}
return '0';
return 'U';
}
+2 -2
View File
@@ -31,11 +31,11 @@ if (!isset($called_by_script_server)) {
print call_user_func_array('ss_mikrotik_health', $_SERVER['argv']);
}
function ss_mikrotik_health(int $host_id = 0, string $column = 'no') : string {
function ss_mikrotik_health(mixed $host_id = 0, string $column = 'no') : string {
$allowed_columns = ['voltage', 'temperature', 'processorTemperature', 'current', 'power', 'fanSpeed', 'cpuFrequency'];
if (!in_array($column, $allowed_columns, true)) {
return '0';
return 'U';
}
$value = db_fetch_cell_prepared("SELECT $column
+3 -3
View File
@@ -59,7 +59,7 @@ function ss_mikrotik_interfaces(int $host_id, string $cmd = 'index', string $arg
function ss_mikrotik_interfaces_getvalue(int $host_id, string $index, string $column) : string {
if (!preg_match('/^[a-zA-Z0-9_]+$/', $column)) {
return '0';
return 'U';
}
$column = 'cur' . $column;
@@ -72,7 +72,7 @@ function ss_mikrotik_interfaces_getvalue(int $host_id, string $index, string $co
];
if (!in_array($column, $allowed_columns, true)) {
return '0';
return 'U';
}
$index2 = str_replace('_', ' ', $index);
@@ -85,7 +85,7 @@ function ss_mikrotik_interfaces_getvalue(int $host_id, string $index, string $co
[$index, $index2, $host_id]);
if ($value === false) {
return '0';
return 'U';
} else {
return $value;
}
+3 -3
View File
@@ -92,13 +92,13 @@ function ss_mikrotik_queues_getvalue(int $host_id, string $index, string $column
break;
default:
return '0';
return 'U';
}
$allowed_columns = ['curBytesIn', 'curBytesOut', 'curPacketsIn', 'curPacketsOut', 'curQueuesIn', 'curQueuesOut', 'curDroppedIn', 'curDroppedOut'];
if (!in_array($column, $allowed_columns, true)) {
return '0';
return 'U';
}
$index2 = str_replace('_', ' ', $index);
@@ -111,7 +111,7 @@ function ss_mikrotik_queues_getvalue(int $host_id, string $index, string $column
[$index, $index2, $host_id]);
if ($value === false) {
return '0';
return 'U';
} else {
return $value;
}
+5 -30
View File
@@ -13,35 +13,10 @@
*/
/*
* Shared stubs for Cacti helper functions used by production code.
* These replicate the production signatures so that test stubs
* can call them without loading the full Cacti environment.
* We set a testing variable such that Cacti will not attempt
* to connect to a database when the tests are called, or to call
* any function that will result in the failure due to the lack
* of a real database connection.
*/
function cacti_sizeof($value): int {
if (is_array($value)) {
return sizeof($value);
} elseif ($value instanceof Countable) {
return count($value);
}
return 0;
}
function cacti_count($value): int {
if (is_array($value)) {
return count($value);
} elseif ($value instanceof Countable) {
return count($value);
}
return 0;
}
function __($text) {
return $text;
}
function __esc($text) {
return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
}
define('PHP_TESTING', true);
+2 -2
View File
@@ -29,8 +29,8 @@
* 3. HRULE item pparts[3] replacement (aggregate_graphs_insert_graph_items)
*/
define('AGGREGATE_TOTAL_TYPE_SIMILAR', 1);
define('AGGREGATE_TOTAL_TYPE_ALL', 2);
require_once dirname(__DIR__) . '/Helpers/CactiStubs.php';
require_once dirname(__DIR__, 2) . '/include/global.php';
/**
* Mimics the text_format :current: / :max: replacement logic from
+3 -3
View File
@@ -16,15 +16,15 @@
* Tests for XSS fix in auth_resetpassword.php.
*
* The hash value in hidden form inputs was output without escaping. The fix
* wraps gnrv('hash') with htmle() to prevent XSS when the hash is reflected.
* wraps $_REQUEST['hash'] with htmlerv() to prevent XSS when the hash is reflected.
*/
$authPath = __DIR__ . '/../../auth_resetpassword.php';
// --- auth_resetpassword.php: hash escaped in output ---
test('auth_resetpassword.php escapes hash with htmle', function () use ($authPath) {
test('auth_resetpassword.php escapes hash with htmlerv', function () use ($authPath) {
$contents = file_get_contents($authPath);
expect($contents)->toContain("htmle(gnrv('hash'))");
expect($contents)->toContain("htmlerv('hash')");
});
+2 -26
View File
@@ -12,32 +12,8 @@
+-------------------------------------------------------------------------+
*/
/*
* Tests for exec_with_timeout() in lib/poller.php.
*
* Covers three bugs fixed in commit c58f8d940:
* 1. Operator precedence: (int) cast bound tighter than multiplication,
* truncating the elapsed-time float to 0 before scaling to microseconds.
* 2. stderr discard: any stderr output caused an early `return false`,
* throwing away valid stdout data.
* 3. Orphaned children: no process-group kill before proc_terminate.
*/
// Stub cacti_log so lib/poller.php can call it without the full framework.
if (!function_exists('cacti_log')) {
function cacti_log(string $msg, bool $output = false, string $facility = '', int $level = 0): void {
// no-op for tests
}
}
if (!defined('POLLER_VERBOSITY_MEDIUM')) {
define('POLLER_VERBOSITY_MEDIUM', 2);
}
if (!defined('CACTI_SERVER_OS')) {
define('CACTI_SERVER_OS', PHP_OS === 'WINNT' ? 'win32' : 'unix');
}
require_once dirname(__DIR__) . '/Helpers/CactiStubs.php';
require_once dirname(__DIR__, 2) . '/include/global.php';
require_once dirname(__DIR__, 2) . '/lib/poller.php';
// --- Bug 1: operator precedence in timeout microsecond conversion ---
+16 -1
View File
@@ -12,7 +12,8 @@
+-------------------------------------------------------------------------+
*/
require_once __DIR__ . '/../../lib/functions.php';
require_once dirname(__DIR__) . '/Helpers/CactiStubs.php';
require_once dirname(__DIR__, 2) . '/include/global.php';
beforeEach(function () {
unset(
@@ -27,6 +28,7 @@ beforeEach(function () {
test('returns REMOTE_ADDR when proxy_headers is false', function () {
global $config;
$config['proxy_headers'] = false;
$_SERVER['REMOTE_ADDR'] = '192.168.1.100';
@@ -36,6 +38,7 @@ test('returns REMOTE_ADDR when proxy_headers is false', function () {
test('returns false when no server variables are set', function () {
global $config;
$config['proxy_headers'] = false;
expect(get_client_addr())->toBeFalse();
@@ -43,6 +46,9 @@ test('returns false when no server variables are set', function () {
test('returns first valid IP from X-Forwarded-For when proxy_headers enabled', function () {
global $config, $allowed_proxy_headers;
require __DIR__ . '/../../include/global_arrays.php';
$config['proxy_headers'] = true;
$_SERVER['HTTP_X_FORWARDED_FOR'] = '10.0.0.1,10.0.0.2';
@@ -53,6 +59,9 @@ test('returns first valid IP from X-Forwarded-For when proxy_headers enabled', f
test('skips invalid IPs in forwarded header', function () {
global $config, $allowed_proxy_headers;
require __DIR__ . '/../../include/global_arrays.php';
$config['proxy_headers'] = true;
$_SERVER['HTTP_X_FORWARDED_FOR'] = 'not-an-ip,203.0.113.50';
@@ -63,6 +72,9 @@ test('skips invalid IPs in forwarded header', function () {
test('falls back to REMOTE_ADDR when forwarded header has only invalid IPs', function () {
global $config, $allowed_proxy_headers;
require __DIR__ . '/../../include/global_arrays.php';
$config['proxy_headers'] = true;
$_SERVER['HTTP_X_FORWARDED_FOR'] = 'garbage';
@@ -73,6 +85,7 @@ test('falls back to REMOTE_ADDR when forwarded header has only invalid IPs', fun
test('ignores proxy headers when proxy_headers is false', function () {
global $config;
$config['proxy_headers'] = false;
$_SERVER['HTTP_X_FORWARDED_FOR'] = '10.0.0.1';
@@ -83,6 +96,7 @@ test('ignores proxy headers when proxy_headers is false', function () {
test('supports IPv6 addresses', function () {
global $config;
$config['proxy_headers'] = false;
$_SERVER['REMOTE_ADDR'] = '::1';
@@ -92,6 +106,7 @@ test('supports IPv6 addresses', function () {
test('handles array proxy_headers config with specific headers allowed', function () {
global $config, $allowed_proxy_headers;
$config['proxy_headers'] = ['HTTP_CLIENT_IP'];
$_SERVER['HTTP_CLIENT_IP'] = '10.10.10.10';
+4 -10
View File
@@ -12,6 +12,9 @@
+-------------------------------------------------------------------------+
*/
require_once dirname(__DIR__) . '/Helpers/CactiStubs.php';
require_once dirname(__DIR__, 2) . '/include/global.php';
/**
* Test for PR #6702: Verify CACTI_PATH_MIBS constant is defined
*/
@@ -19,10 +22,6 @@
test('CACTI_PATH_MIBS constant is defined in global_path.php', function () {
// Simulate config array that would be set before including global_path.php
global $config;
$config['mibs_path'] = '/usr/share/snmp/mibs';
// Include the file that defines path constants
require_once __DIR__ . '/../../include/global_path.php';
expect(defined('CACTI_PATH_MIBS'))
->toBeTrue('CACTI_PATH_MIBS constant should be defined');
@@ -30,10 +29,8 @@ test('CACTI_PATH_MIBS constant is defined in global_path.php', function () {
test('CACTI_PATH_MIBS uses mibs_path from config', function () {
global $config;
$expectedPath = '/usr/share/snmp/mibs';
$config['mibs_path'] = $expectedPath;
require_once __DIR__ . '/../../include/global_path.php';
$expectedPath = CACTI_PATH_BASE . '/mibs';
expect(CACTI_PATH_MIBS)
->toBe($expectedPath, 'CACTI_PATH_MIBS should match config mibs_path');
@@ -41,9 +38,6 @@ test('CACTI_PATH_MIBS uses mibs_path from config', function () {
test('CACTI_PATH_MIBS constant matches other CACTI_PATH_ patterns', function () {
global $config;
$config['mibs_path'] = '/usr/share/snmp/mibs';
require_once __DIR__ . '/../../include/global_path.php';
// Verify it follows the same pattern as other path constants
expect(defined('CACTI_PATH_MIBS'))
+1 -1
View File
@@ -13,7 +13,7 @@
*/
require_once dirname(__DIR__) . '/Helpers/CactiStubs.php';
require_once dirname(__DIR__, 2) . '/lib/functions.php';
require_once dirname(__DIR__, 2) . '/include/global.php';
require_once dirname(__DIR__, 2) . '/lib/xml.php';
// --- rrdxport2array: valid xport XML produces correct structure ---
+2 -65
View File
@@ -12,71 +12,8 @@
+-------------------------------------------------------------------------+
*/
/*
* Tests for XSS defense functions from lib/html.php.
*
* Each function under test is replicated as an inline stub that mirrors
* the production logic, allowing verification without loading the full
* Cacti environment. Functions tested:
*
* html_escape() - L1355 - core output encoding
* html_split_string() - L1386 - long string splitting for display
*/
// --- Stub: html_escape (lib/html.php L1355) ---
function html_escape(mixed $string = ''): string {
static $charset;
if ($charset == '') {
$charset = ini_get('default_charset');
}
if ($charset == '') {
$charset = 'UTF-8';
}
if ($string != '') {
$string = str_replace('`', '&#96;', $string);
return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5, $charset, false);
} else {
return '';
}
}
// --- Stub: html_split_string (lib/html.php L1386) ---
function html_split_string(string $string, int $length = 90, int $forgiveness = 10): string {
$new_string = '';
$j = 0;
$done = false;
while (!$done) {
if (mb_strlen($string, 'UTF-8') > $length) {
for ($i = 0; $i < $forgiveness; $i++) {
if (substr($string, $length - $i, 1) == ' ') {
$new_string .= mb_substr($string, 0, $length - $i, 'UTF-8') . '<br>';
break;
}
}
$string = mb_substr($string, $length - $i, null, 'UTF-8');
} else {
$new_string .= $string;
$done = true;
}
$j++;
if ($j > 4) {
break;
}
}
return $new_string;
}
require_once dirname(__DIR__) . '/Helpers/CactiStubs.php';
require_once dirname(__DIR__, 2) . '/include/global.php';
// =====================================================================
// html_escape tests
+2 -1
View File
@@ -37,7 +37,8 @@
* guard_foreach_iterable() - array|false before foreach
*/
require_once __DIR__ . '/../Helpers/CactiStubs.php';
require_once dirname(__DIR__) . '/Helpers/CactiStubs.php';
require_once dirname(__DIR__, 2) . '/include/global.php';
// --- Stub: guard_db_result_access (pattern from multiple graph/data files) ---
// Mirrors: $row = db_fetch_row_prepared(...); returns array|bool.
+2
View File
@@ -12,6 +12,8 @@
+-------------------------------------------------------------------------+
*/
require_once dirname(__DIR__) . '/Helpers/CactiStubs.php';
require_once __DIR__ . '/../../include/global.php';
require_once __DIR__ . '/../../lib/functions.php';
// --- get_current_script_name ---
+13 -16
View File
@@ -15,44 +15,47 @@
/*
* Tests for column allow-list and SNMP retry parameter hardening.
* These scripts require DB + SNMP, so we test the allow-list guard
* by calling with host_id=0 (returns '0' early) and verifying that
* by calling with host_id=0 (returns 'U' early) and verifying that
* invalid columns are rejected before reaching any SQL.
*/
require_once __DIR__ . '/../../lib/functions.php';
$called_by_script_server = true;
require_once dirname(__DIR__) . '/Helpers/CactiStubs.php';
require_once dirname(__DIR__, 2) . '/include/global.php';
// ss_mikrotik_health: column allow-list
require_once __DIR__ . '/../../scripts/ss_mikrotik_health.php';
test('ss_mikrotik_health rejects invalid column', function () {
expect(ss_mikrotik_health(0, '1; DROP TABLE hosts; --'))->toBe('0');
expect(ss_mikrotik_health(0, '1; DROP TABLE hosts; --'))->toBe('U');
});
test('ss_mikrotik_health rejects empty column', function () {
expect(ss_mikrotik_health(0, ''))->toBe('0');
expect(ss_mikrotik_health(0, ''))->toBe('U');
});
test('ss_mikrotik_health rejects arbitrary column name', function () {
expect(ss_mikrotik_health(0, 'password'))->toBe('0');
expect(ss_mikrotik_health(0, 'password'))->toBe('U');
});
test('ss_mikrotik_health accepts valid column voltage', function () {
expect(ss_mikrotik_health(0, 'voltage'))->toBe('0');
expect(ss_mikrotik_health(0, 'voltage'))->toBe('U');
});
test('ss_mikrotik_health accepts valid column temperature', function () {
expect(ss_mikrotik_health(0, 'temperature'))->toBe('0');
expect(ss_mikrotik_health(0, 'temperature'))->toBe('U');
});
// ss_hstats: column mapping via ss_hstats_map_stat_to_column
require_once __DIR__ . '/../../scripts/ss_hstats.php';
test('ss_hstats rejects invalid stat with host_id 0', function () {
expect(ss_hstats(0, 'invalid_stat'))->toBe('0');
expect(ss_hstats(0, 'invalid_stat'))->toBe('U');
});
test('ss_hstats returns 0 for valid stat with host_id 0', function () {
expect(ss_hstats(0, 'polling_time'))->toBe('0');
test('ss_hstats returns U for valid stat with host_id 0', function () {
expect(ss_hstats(0, 'polling_time'))->toBe('U');
});
// Verify snmp_retries is referenced (not ping_retries)
@@ -80,12 +83,6 @@ test('disk io scripts use snmp_retries not ping_retries', function () {
expect($file)->not->toContain('ping_retries');
});
test('cpoller script uses snmp_retries not ping_retries', function () {
$file = file_get_contents(__DIR__ . '/../../scripts/ss_cpoller.php');
expect($file)->toContain('snmp_retries');
expect($file)->not->toContain('ping_retries');
});
test('lmsensors script uses snmp_retries not ping_retries', function () {
$file = file_get_contents(__DIR__ . '/../../scripts/ss_netsnmp_lmsensors.php');
expect($file)->toContain('snmp_retries');
+2 -126
View File
@@ -12,132 +12,8 @@
+-------------------------------------------------------------------------+
*/
/*
* Tests for deserialization guards and input validation from lib/functions.php.
*
* These functions defend against PHP object injection, rrdtool command
* injection, and malformed network addresses. Each is replicated as an
* inline stub mirroring production logic.
*
* sanitize_unserialize_selected_items() - L5002 - PHP object injection defense
* sanitize_cdef() - L4988 - rrdtool CDEF/VDEF injection
* is_ipaddress() - L7256 - IP address validation
* is_mac_address() - L2217 - MAC address validation
* is_hex_string() - L2239 - hex string validation
*/
require_once __DIR__ . '/../Helpers/CactiStubs.php';
// --- Stub: sanitize_unserialize_selected_items (lib/functions.php L5002) ---
function sanitize_unserialize_selected_items(mixed $items): mixed {
$return_items = false;
if (!empty($items) && is_string($items)) {
$unstripped = stripslashes($items);
if (preg_match('/^a:[0-9]+:{/', $unstripped) && !preg_match('/(^|;|{|})O:\+?[0-9]+:"/', $unstripped)) {
$items = unserialize($unstripped, ['allowed_classes' => false]);
if (is_array($items)) {
$return_items = $items;
foreach ($items as $item) {
if (is_array($item)) {
$return_items = false;
break;
}
if (!is_numeric($item) && ($item != '')) {
$return_items = false;
break;
}
}
}
}
}
return $return_items;
}
// --- Stub: sanitize_cdef (lib/functions.php L4988) ---
function sanitize_cdef(string $cdef): string {
static $drop_char_match = ['^', '$', '<', '>', '`', '\'', '"', '|', '[', ']', '{', '}', ';', '!'];
static $drop_char_replace = ['', '', '', '', '', '', '', '', '', '', '', '', '', ''];
return str_replace($drop_char_match, $drop_char_replace, $cdef);
}
// --- Stub: is_ipaddress (lib/functions.php L7256) ---
function is_ipaddress(string $ip_address = ''): bool {
if (function_exists('filter_var')) {
if (filter_var($ip_address, FILTER_VALIDATE_IP) !== false) {
return true;
} else {
return false;
}
} elseif (inet_pton($ip_address) !== false) {
return true;
} else {
return false;
}
}
// --- Stub: is_mac_address (lib/functions.php L2217) ---
function is_mac_address(string $result): mixed {
if (!defined('FILTER_VALIDATE_MAC')) {
if (preg_match('/^([0-9a-f]{1,2}[\.:-]) {5}([0-9a-f]{1,2})$/i', $result)) {
return true;
} else {
return false;
}
} else {
return filter_var($result, FILTER_VALIDATE_MAC);
}
}
// --- Stub: is_hex_string (lib/functions.php L2239) ---
function is_hex_string(string &$result): bool {
if ($result == '') {
return false;
}
$compare = strtolower($result);
if (str_starts_with($compare, 'hex-string:')) {
$check = trim(str_ireplace('hex-string:', '', $result));
} elseif (str_starts_with($compare, 'hex-')) {
$check = trim(str_ireplace('hex-', '', $result));
} else {
return false;
}
$parts = explode(' ', $check);
if (cacti_sizeof($parts) == 1) {
return false;
}
foreach ($parts as $part) {
if (strlen($part) != 2) {
return false;
}
if (ctype_xdigit($part) == false) {
return false;
}
}
$result = $check;
return true;
}
require_once dirname(__DIR__) . '/Helpers/CactiStubs.php';
require_once dirname(__DIR__, 2) . '/include/global.php';
// =====================================================================
// sanitize_unserialize_selected_items tests
+4 -4
View File
@@ -102,8 +102,8 @@ test('all identity-mapped stats return the stat name as column', function () {
});
// Test ss_hstats() wrapper behavior with host_id=0
test('ss_hstats returns zero when host_id is zero', function () {
expect(ss_hstats(0, 'polling_time'))->toBe('0')
->and(ss_hstats(0, 'uptime'))->toBe('0')
->and(ss_hstats(0, 'invalid'))->toBe('0');
test('ss_hstats returns U when host_id is zero', function () {
expect(ss_hstats(0, 'polling_time'))->toBe('U')
->and(ss_hstats(0, 'uptime'))->toBe('U')
->and(ss_hstats(0, 'invalid'))->toBe('U');
});
+2 -115
View File
@@ -12,121 +12,8 @@
+-------------------------------------------------------------------------+
*/
/*
* Tests for pure string functions from lib/functions.php.
*
* Each function under test is replicated as an inline stub that mirrors
* the production logic, allowing verification without loading the full
* Cacti environment. Functions tested:
*
* title_trim() - L37 - string truncation with ellipsis
* sanitize_search_string() - L4908 - XSS-relevant character stripping
* clean_up_lines() - L3105 - line ending normalization
* clean_up_name() - L3121 - name sanitization
* clean_up_file_name() - L3139 - filename sanitization
* is_valid_pathname() - L2372 - path traversal guard
* is_base64_encoded() - L4959 - encoding validation
* generate_hash() - L4779 - format validation (random output)
*/
require_once __DIR__ . '/../Helpers/CactiStubs.php';
// --- Stub: title_trim (lib/functions.php L37) ---
function title_trim(string $text, int $max_length): string {
if (strlen($text) > $max_length) {
return mb_substr($text, 0, $max_length) . '...';
} else {
return $text;
}
}
// --- Stub: sanitize_search_string (lib/functions.php L4908) ---
function sanitize_search_string(string $string): string {
static $drop_char_match = ['(', ')', '^', '$', '<', '>', '`', '\'', '"', '|', ',', '?', '+', '[', ']', '{', '}', '#', ';', '!', '=', '*'];
static $drop_char_replace = ['', '', ' ', ' ', ' ', ' ', '', '', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '];
$string = preg_replace('/[\n\r]/is', ' ', $string);
$string = preg_replace('/\b&[a-z]+;\b/', ' ', $string);
$string = preg_replace('/\b[a-z0-9]+:\/\/[a-z0-9\.\-]+(\/[a-z0-9\?\.%_\-\+=&\/]+)?/', ' ', $string);
for ($i = 0; $i < cacti_count($drop_char_match); $i++) {
$string = str_replace($drop_char_match[$i], $drop_char_replace[$i], $string);
}
return $string;
}
// --- Stub: clean_up_lines (lib/functions.php L3105) ---
function clean_up_lines(mixed $string): mixed {
if ($string !== null) {
$string = preg_replace('/\s*[\r\n]+\s*/', ' ', $string);
}
return $string;
}
// --- Stub: clean_up_name (lib/functions.php L3121) ---
function clean_up_name(mixed $string): mixed {
if ($string !== null) {
$string = preg_replace('/[\s\.]+/', '_', $string);
$string = preg_replace('/[^a-zA-Z0-9_]+/', '', $string);
$string = preg_replace('/_{2,}/', '_', $string);
}
return $string;
}
// --- Stub: clean_up_file_name (lib/functions.php L3139) ---
function clean_up_file_name(mixed $string): mixed {
if ($string !== null) {
$string = preg_replace('/[\s\.]+/', '_', $string);
$string = preg_replace('/[^a-zA-Z0-9_-]+/', '', $string);
$string = preg_replace('/_{2,}/', '_', $string);
}
return $string;
}
// --- Stub: is_valid_pathname (lib/functions.php L2372) ---
function is_valid_pathname($path) {
if (preg_match('/^([a-zA-Z0-9\_\.\-\\\:\/]+)$/', trim($path))) {
return true;
} else {
return false;
}
}
// --- Stub: is_base64_encoded (lib/functions.php L4959) ---
function is_base64_encoded($data) {
if (!preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $data)) {
return false;
}
$ndata = base64_decode($data, true);
if ($ndata === false) {
return false;
}
if (base64_encode($ndata) != $data) {
return false;
}
return true;
}
// --- Stub: generate_hash (lib/functions.php L4779) ---
function generate_hash(): string {
return bin2hex(random_bytes(16));
}
require_once dirname(__DIR__) . '/Helpers/CactiStubs.php';
require_once dirname(__DIR__, 2) . '/include/global.php';
// =====================================================================
// title_trim tests