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.
149 lines
6.9 KiB
PHP
149 lines
6.9 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. |
|
|
+-------------------------------------------------------------------------+
|
|
| Cacti: The Complete RRDtool-based Graphing Solution |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
/*
|
|
* Source-level guards for the data_input_data column-expansion bugs in
|
|
* change_data_template() (lib/template.php) and api_data_source_duplicate()
|
|
* (lib/api_data_source.php).
|
|
*
|
|
* Background. The data_input_data table on develop carries five identity
|
|
* columns (data_input_field_id, data_template_data_id, data_template_id,
|
|
* local_data_id, host_id) plus the value pair (t_value, value). Two
|
|
* write paths copy rows from the template (parent) row into the child:
|
|
*
|
|
* - change_data_template() in lib/template.php
|
|
* - api_data_source_duplicate() in lib/api_data_source.php
|
|
*
|
|
* Two regressions were live in the develop branch:
|
|
*
|
|
* (1) change_data_template() copied $item['data_template_id'],
|
|
* $item['local_data_id'], $item['host_id'] from the parent row
|
|
* verbatim. For template rows those are 0, so child data_input_data
|
|
* rows ended up with local_data_id = 0 and host_id = 0, orphaned
|
|
* from the new data source. The fix reads host_id from
|
|
* data_local for the new $local_data_id and stamps the child row
|
|
* with the function's own ($data_template_id, $local_data_id,
|
|
* $host_id) instead of the parent's.
|
|
*
|
|
* (2) api_data_source_duplicate() expanded the INSERT column list to
|
|
* seven (data_input_field_id, data_template_data_id,
|
|
* data_template_id, local_data_id, host_id, t_value, value) but
|
|
* left the placeholder list at four (?, ?, ?, ?), while still
|
|
* binding seven values. db_execute_prepared() rejects the
|
|
* mismatch, so duplicating a data source or template silently
|
|
* produced no data_input_data rows.
|
|
*
|
|
* Both bugs cascade into the same downstream symptom TheWitness reported
|
|
* for 1.2.31: data sources created from a template (or duplicated from
|
|
* an existing one) end up without populated data_input_data, which
|
|
* breaks update_poller_cache() and leaves poller_item empty.
|
|
*
|
|
* The 1.2.x branch is unaffected. Its data_input_data schema only
|
|
* carries the four columns (data_input_field_id, data_template_data_id,
|
|
* t_value, value) and both call sites already match.
|
|
*/
|
|
|
|
$templateSource = file_get_contents(__DIR__ . '/../../../lib/template.php');
|
|
$apiDataSourceSource = file_get_contents(__DIR__ . '/../../../lib/api_data_source.php');
|
|
|
|
/**
|
|
* Extract the body of one PHP function definition out of a source blob.
|
|
* Caller supplies the function name; returns the matched body or '' if
|
|
* the definition is not found.
|
|
*/
|
|
function _extract_function_body(string $source, string $name): string {
|
|
$pattern = '/^function\s+' . preg_quote($name, '/') . '\b[^{]*\{.*?^\}/sm';
|
|
if (!preg_match($pattern, $source, $m)) {
|
|
return '';
|
|
}
|
|
return $m[0];
|
|
}
|
|
|
|
test('change_data_template stamps child data_input_data rows with the new identity tuple', function () use ($templateSource) {
|
|
$body = _extract_function_body($templateSource, 'change_data_template');
|
|
expect($body)->not->toBe('');
|
|
|
|
/* The function must look up the child host_id from data_local using
|
|
* the new $local_data_id. The pre-fix code did not do this and
|
|
* relied on $item['host_id'] from the parent row, which is 0 on a
|
|
* template. */
|
|
expect($body)
|
|
->toContain('SELECT host_id')
|
|
->toContain('FROM data_local')
|
|
->toContain('[$local_data_id]');
|
|
|
|
/* The REPLACE INTO must use the function's own identity tuple
|
|
* ($data_template_id, $local_data_id, $host_id) for the three
|
|
* identity columns that previously came from the parent row. */
|
|
$replacePos = strpos($body, 'REPLACE INTO data_input_data');
|
|
expect($replacePos)->not->toBeFalse('REPLACE INTO data_input_data must remain present');
|
|
|
|
$insertSlice = substr($body, $replacePos, 800);
|
|
|
|
/* Column count and placeholder count must match. */
|
|
expect(substr_count($insertSlice, '?'))
|
|
->toBe(7);
|
|
|
|
/* The seven bound values must include the function's own variables,
|
|
* not just $item[...] readbacks of the parent row. */
|
|
expect($insertSlice)
|
|
->toContain('$data_template_id,')
|
|
->toContain('$local_data_id,')
|
|
->toContain('$host_id,');
|
|
|
|
/* The pre-fix bind list pulled these from $item; that pattern must
|
|
* not survive in the fixed code for the identity columns. */
|
|
expect(strpos($insertSlice, "\$item['local_data_id']"))->toBeFalse(
|
|
'must not bind $item[local_data_id] for the child row (template default is 0)'
|
|
);
|
|
expect(strpos($insertSlice, "\$item['host_id']"))->toBeFalse(
|
|
'must not bind $item[host_id] for the child row (template default is 0)'
|
|
);
|
|
});
|
|
|
|
test('api_data_source_duplicate INSERT column count matches placeholder count', function () use ($apiDataSourceSource) {
|
|
$body = _extract_function_body($apiDataSourceSource, 'api_data_source_duplicate');
|
|
expect($body)->not->toBe('');
|
|
|
|
$insertPos = strpos($body, 'INSERT IGNORE INTO data_input_data');
|
|
expect($insertPos)->not->toBeFalse('INSERT IGNORE INTO data_input_data must remain present');
|
|
|
|
$insertSlice = substr($body, $insertPos, 800);
|
|
|
|
/* Placeholder count must match the seven-column form the caller
|
|
* already binds. The pre-fix code had four placeholders against
|
|
* seven bound values; db_execute_prepared() rejects that. */
|
|
expect(substr_count($insertSlice, '?'))
|
|
->toBe(7);
|
|
|
|
/* All seven columns must be named in the INSERT list. */
|
|
foreach (['data_input_field_id', 'data_template_data_id', 'data_template_id', 'local_data_id', 'host_id', 't_value', 'value'] as $col) {
|
|
expect($insertSlice)->toContain($col);
|
|
}
|
|
});
|
|
|
|
test('1.2.x schema regression check: develop INSERT must not silently drop columns', function () use ($apiDataSourceSource, $templateSource) {
|
|
/* If anyone re-narrows the develop INSERT/REPLACE column lists back
|
|
* to the 1.2.x four-column form (data_input_field_id,
|
|
* data_template_data_id, t_value, value) the column / placeholder
|
|
* count check above will still pass on length but the cross-source
|
|
* grammar drifts. Pin the seven-column shape explicitly so a
|
|
* narrowing change has to delete the columns from the SQL string
|
|
* rather than just re-shrink the bind array. */
|
|
foreach ([$templateSource, $apiDataSourceSource] as $source) {
|
|
$pos = strpos($source, '(data_input_field_id, data_template_data_id, data_template_id, local_data_id, host_id, t_value, value)');
|
|
expect($pos)->not->toBeFalse('seven-column data_input_data write must remain present');
|
|
}
|
|
});
|