mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-29 16:17:14 +09:00
* refactor(tests): reorganize unit tests into domain category folders - Consolidate per-advisory GHSA regression tests into Security/ subfolders by vuln class - Group all 186 test files into domain folders: DataCollection/, Ui/, Core/, Database/, Installer/, Scripts/, Security/, Plugin/ - Strip GitHub issue/GHSA numbers from filenames; keep IDs in test descriptions - Fix all __DIR__ relative paths to match new folder nesting depth - Update CI workflow path triggers and phpunit XML configs for new file locations - Update .github/copilot-instructions.md with unit test organization guidelines - Add tests/vendor/ to .gitignore * add gaurds * fix up guard for unit tests * fix redeclaration of cacti_log in tests * fix updates to unit tests * fix test patjhs * fix: update test __DIR__ paths for reorganized test structure * fix: conditionally define read_user_setting to prevent redeclaration in tests * fix: conditionally define functions to prevent redeclaration in tests - Wrap clean_up_lines, raise_message, cacti_debug_backtrace, and get_debug_prefix with function_exists() checks to prevent conflicts when test stubs are loaded before lib/functions.php in the same PHP process * fix: add unit test bootstrap to prevent function redeclaration errors - Create tests/bootstrap-unit.php to load core libraries before tests - Ensures lib/functions.php loads before test stubs define stub functions - Test stubs use function_exists() checks, so they detect pre-loaded functions - Update phpunit.xml to use bootstrap-unit.php for Unit testsuite - Fixes 'Cannot redeclare read_user_setting()' errors when tests run in same process * fix: load test stubs before global.php in unit test bootstrap - Reorder bootstrap to load CactiStubs.php and UnitStubs.php first - Ensures stub functions like __() are available when global.php loads - global.php calls get_cacti_version_text() which needs __() for translations - Fixes 'Call to undefined function __()' error during bootstrap * fix: remove function conflicts from test stubs - Remove stub definitions for functions already in lib/functions.php: cacti_sizeof, cacti_log, cacti_strtolower, read_config_option, read_user_setting, cacti_count - Keep translation function stubs (__(), __esc()) needed by global.php - Update bootstrap to load lib/functions.php after stubs - This prevents function redeclaration errors while maintaining test isolation * fix: prevent constant and variable redeclaration in bootstrap - Remove explicit PHP_TESTING and CACTI_TEST_BOOTSTRAP defines from bootstrap (CactiStubs.php now handles this with guards) - Add guards to CactiStubs.php to prevent redefinition of PHP_TESTING - Only set CACTI_TEST_BOOTSTRAP env vars if not already set - Fixes PHP warnings about 'Constant X already defined' * fix: remove redundant lib/functions.php load from bootstrap include/global.php already loads lib/functions.php at lines 287-288. The bootstrap was trying to load it again after global.php, but global.php needs format_cacti_version() to be available on lines 293-294 to define CACTI_VERSION and CACTI_VERSION_FULL constants. Solution: Let global.php handle loading lib/functions.php itself, and don't load it separately in the bootstrap. * fix: remove more conflicting function stubs from test helpers - Remove stubs for functions also defined in lib/functions.php: raise_message, clean_up_lines, cacti_debug_backtrace, get_debug_prefix - Remove POLLER_ID constant stub (defined unconditionally by global.php) - Keep only translation functions and test-specific helpers - This prevents function/constant redeclaration errors * fix: minimize test stubs to only translation functions Remove ALL helper functions and constants from UnitStubs.php that are: - Defined in lib/functions.php (raise_message, clean_up_lines, etc.) - Defined in lib/html_utility.php (srv, set_request_var) - Defined in global_constants.php (POLLER_VERBOSITY_*, MESSAGE_LEVEL_*) - Defined in global.php (POLLER_ID) Keep ONLY translation functions (__(), __esc()) that are: - Called by global.php during initialization - Not defined elsewhere - Required for proper bootstrap sequence This eliminates all constant and function redeclaration errors. * fix: remove __esc stub - already defined in global_languages.php Keep only __() translation function stub, which is needed by global.php and not defined elsewhere in Cacti core. * fix: handle no-arg case in __() translation stub The __() function may be called with just a string and no format arguments. Previously, vsprintf() would fail with 'arguments array must contain 2 items'. Now, if no args are provided, return the text as-is.
246 lines
9.0 KiB
PHP
246 lines
9.0 KiB
PHP
<?php
|
|
/*
|
|
+-------------------------------------------------------------------------+
|
|
| Copyright (C) 2004-2026 The Cacti Group |
|
|
+-------------------------------------------------------------------------+
|
|
| Cacti: The Complete RRDtool-based Graphing Solution |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
/*
|
|
* Unit tests for OID trailing-zero stripping in data query index parsing.
|
|
* See GitHub issue #6108: Juniper Netscreen devices pad OID indexes with
|
|
* trailing .0 octets, causing all indexes to resolve to 0.
|
|
*
|
|
* Tests call oid_index_strip_trailing_zero_padding() directly so they
|
|
* exercise the actual production code path, not a re-implementation.
|
|
*/
|
|
|
|
beforeAll(function () {
|
|
require_once dirname(__DIR__, 4) . '/lib/functions.php';
|
|
require_once dirname(__DIR__, 4) . '/lib/data_query.php';
|
|
});
|
|
|
|
describe('OID trailing zero stripping', function () {
|
|
$defaultRegex = '/.*\.([0-9]+)$/';
|
|
|
|
describe('detection', function () use ($defaultRegex) {
|
|
dataset('trailing zero OIDs', [
|
|
'juniper netscreen pattern' => [
|
|
[
|
|
'.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.84.114.117.115.116.0.0.0' => 'Trust',
|
|
'.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.85.110.116.114.117.115.117.0.0.0' => 'Untrust',
|
|
'.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.68.77.90.0.0.0' => 'DMZ',
|
|
],
|
|
],
|
|
]);
|
|
|
|
it('detects all indexes resolving to zero', function (array $oids) use ($defaultRegex) {
|
|
$indexes = [];
|
|
|
|
foreach ($oids as $oid => $value) {
|
|
if (preg_match($defaultRegex, $oid, $matches)) {
|
|
$indexes[$oid] = $matches[1];
|
|
}
|
|
}
|
|
|
|
$unique = array_unique(array_values($indexes));
|
|
|
|
expect($unique)->toHaveCount(1)
|
|
->and($unique[0])->toBe('0');
|
|
})->with('trailing zero OIDs');
|
|
|
|
it('enables stripping when multi-zero padding resolves to unique indexes', function () use ($defaultRegex) {
|
|
$oids = [
|
|
'.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.84.114.117.115.116.0.0.0' => 'Trust',
|
|
'.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.85.110.116.114.117.115.117.0.0.0' => 'Untrust',
|
|
];
|
|
|
|
expect(oid_index_should_strip_trailing_zero_padding($oids, $defaultRegex))->toBeTrue();
|
|
});
|
|
|
|
it('enables stripping when single-zero padding resolves to unique indexes', function () use ($defaultRegex) {
|
|
$oids = [
|
|
'.1.2.3.10.0' => 'a',
|
|
'.1.2.3.20.0' => 'b',
|
|
];
|
|
|
|
expect(oid_index_should_strip_trailing_zero_padding($oids, $defaultRegex))->toBeTrue();
|
|
});
|
|
|
|
it('does not enable stripping when stripped indexes collide', function () use ($defaultRegex) {
|
|
$oids = [
|
|
'.1.2.3.0' => 'first',
|
|
'.1.2.3.0.0' => 'second',
|
|
];
|
|
|
|
expect(oid_index_should_strip_trailing_zero_padding($oids, $defaultRegex))->toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('stripping via oid_index_strip_trailing_zero_padding()', function () use ($defaultRegex) {
|
|
it('strips trailing .0 octets from a padded multi-row walk', function () use ($defaultRegex) {
|
|
$oids = [
|
|
'.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.84.114.117.115.116.0.0.0' => 'Trust',
|
|
'.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.85.110.116.114.117.115.117.0.0.0' => 'Untrust',
|
|
'.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.68.77.90.0.0.0' => 'DMZ',
|
|
];
|
|
|
|
$stripped = oid_index_strip_trailing_zero_padding($oids);
|
|
|
|
// Keys must no longer end in .0
|
|
foreach (array_keys($stripped) as $oid) {
|
|
expect($oid)->not->toEndWith('.0');
|
|
}
|
|
|
|
// Values are preserved
|
|
expect(array_values($stripped))->toBe(['Trust', 'Untrust', 'DMZ']);
|
|
});
|
|
|
|
it('produces unique last-octet indexes after stripping', function () use ($defaultRegex) {
|
|
// Trust -> ASCII T,r,u,s,t (84,114,117,115,116) -> last octet 116
|
|
// Untrust -> ASCII U,n,t,r,u,s,t (85,110,116,114,117,115,117) -> last octet 117
|
|
// DMZ -> ASCII D,M,Z (68,77,90) -> last octet 90
|
|
$oids = [
|
|
'.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.84.114.117.115.116.0.0.0' => 'Trust',
|
|
'.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.85.110.116.114.117.115.117.0.0.0' => 'Untrust',
|
|
'.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.68.77.90.0.0.0' => 'DMZ',
|
|
];
|
|
|
|
$stripped = oid_index_strip_trailing_zero_padding($oids);
|
|
|
|
// Sanity: stripping must preserve row count so no row is lost.
|
|
expect($stripped)->toHaveCount(count($oids));
|
|
|
|
// Extract last-octet indexes directly from the stripped keys.
|
|
$indexes = array_map(function ($oid) use ($defaultRegex) {
|
|
return preg_match($defaultRegex, $oid, $matches) ? $matches[1] : null;
|
|
}, array_keys($stripped));
|
|
|
|
expect($indexes)->toBe(['116', '117', '90'])
|
|
->and(array_unique($indexes))->toHaveCount(3);
|
|
});
|
|
|
|
/* These cases exercise oid_index_strip_trailing_zero_padding() in
|
|
isolation. Real callers gate it behind oid_index_should_strip_trailing_zero_padding(),
|
|
which also requires the stripped OIDs to parse to unique indexes. */
|
|
dataset('padded strip cases', [
|
|
'helper strips single .0' => [
|
|
['.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.84.0' => 'a',
|
|
'.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.85.0' => 'b'],
|
|
['.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.84' => 'a',
|
|
'.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.85' => 'b'],
|
|
],
|
|
'multiple trailing zeros' => [
|
|
['.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.84.114.0.0.0' => 'a',
|
|
'.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.85.110.0.0.0' => 'b'],
|
|
['.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.84.114' => 'a',
|
|
'.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.85.110' => 'b'],
|
|
],
|
|
]);
|
|
|
|
it('strips correctly for padded multi-row dataset', function (array $input, array $expected) {
|
|
expect(oid_index_strip_trailing_zero_padding($input))->toBe($expected);
|
|
})->with('padded strip cases');
|
|
});
|
|
|
|
describe('no false positives', function () use ($defaultRegex) {
|
|
it('does not strip when indexes are already unique', function () use ($defaultRegex) {
|
|
$oids = [
|
|
'.1.3.6.1.2.1.2.2.1.1.1' => 'eth0',
|
|
'.1.3.6.1.2.1.2.2.1.1.2' => 'eth1',
|
|
'.1.3.6.1.2.1.2.2.1.1.3' => 'lo0',
|
|
];
|
|
|
|
// No .0 padding detected; function returns originals unchanged.
|
|
$result = oid_index_strip_trailing_zero_padding($oids);
|
|
|
|
expect($result)->toBe($oids);
|
|
});
|
|
|
|
it('does not strip when only one index exists', function () use ($defaultRegex) {
|
|
// The cacti_sizeof($indexes) <= 1 guard in the helper short-circuits
|
|
// for single-row walks. The detection helper must also report false
|
|
// so query_snmp_host never invokes the strip helper. Both the strip
|
|
// helper and the detection helper are exercised here so the guard
|
|
// cannot regress unnoticed.
|
|
$oids = [
|
|
'.1.3.6.1.4.1.2636.3.39.1.8.1.1.1.1.1.84.0.0.0' => 'Trust',
|
|
];
|
|
|
|
$result = oid_index_strip_trailing_zero_padding($oids);
|
|
|
|
// Strip helper is identity for a single-row input, even one
|
|
// padded with multiple .0 octets.
|
|
expect($result)
|
|
->toBe($oids)
|
|
->and(array_keys($result))->toBe(array_keys($oids));
|
|
|
|
// Detection helper rejects single-row walks regardless of padding.
|
|
expect(oid_index_should_strip_trailing_zero_padding($oids, $defaultRegex))->toBeFalse();
|
|
});
|
|
|
|
it('does not strip when a zero index is legitimate (scalar OID)', function () use ($defaultRegex) {
|
|
// A single scalar OID like .1.3.6.1.2.1.1.1.0 legitimately ends
|
|
// in .0. The > 1 guard means this never reaches the helper, but
|
|
// confirm the helper is identity-safe for a single-element array.
|
|
$oids = [
|
|
'.1.3.6.1.2.1.1.1.0' => 'sysDescr',
|
|
];
|
|
|
|
$result = oid_index_strip_trailing_zero_padding($oids);
|
|
|
|
// Input equals output; the .0 is preserved.
|
|
expect($result)->toBe($oids);
|
|
|
|
$indexes = [];
|
|
|
|
foreach ($result as $oid => $value) {
|
|
if (preg_match($defaultRegex, $oid, $matches)) {
|
|
$indexes[] = $matches[1];
|
|
}
|
|
}
|
|
|
|
expect($indexes[0])->toBe('0');
|
|
});
|
|
|
|
it('collision detection preserves original indexes', function () {
|
|
// .1.2.3.0 stripped -> .1.2.3
|
|
// .1.2.3.0.0 stripped -> .1.2.3 (collision)
|
|
$oids = [
|
|
'.1.2.3.0' => 'first',
|
|
'.1.2.3.0.0' => 'second',
|
|
];
|
|
|
|
$result = oid_index_strip_trailing_zero_padding($oids);
|
|
|
|
// Collision detected; originals returned unchanged.
|
|
expect($result)->toBe($oids);
|
|
});
|
|
|
|
it('does not strip when only some rows end in .0', function () use ($defaultRegex) {
|
|
// Mixed padding: the $all_end_with_zero false branch must reject
|
|
// the strip so partial padding never reshapes a legitimate walk.
|
|
$oids = [
|
|
'.1.3.6.1.2.1.2.2.1.1.1.0' => 'eth0',
|
|
'.1.3.6.1.2.1.2.2.1.1.2' => 'eth1',
|
|
];
|
|
|
|
expect(oid_index_should_strip_trailing_zero_padding($oids, $defaultRegex))->toBeFalse();
|
|
expect(oid_index_strip_trailing_zero_padding($oids))->toBe($oids);
|
|
});
|
|
|
|
it('one-row guard: single row ending in .0 is preserved by the helper', function () {
|
|
// Companion to the > 1 guard in query_snmp_host. Even if the
|
|
// helper were called with one entry, it returns input unchanged.
|
|
$oids = [
|
|
'.1.3.6.1.2.1.47.1.1.1.1.2.0' => 'entPhysicalDescr',
|
|
];
|
|
|
|
$result = oid_index_strip_trailing_zero_padding($oids);
|
|
|
|
expect($result)->toBe($oids);
|
|
});
|
|
});
|
|
});
|