-
Notifications
You must be signed in to change notification settings - Fork 29
test: add Pest v1 security test infrastructure #95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <?php | ||
| /* | ||
| +-------------------------------------------------------------------------+ | ||
| | Copyright (C) 2004-2026 The Cacti Group | | ||
| +-------------------------------------------------------------------------+ | ||
| | Cacti: The Complete RRDtool-based Graphing Solution | | ||
| +-------------------------------------------------------------------------+ | ||
| */ | ||
|
|
||
| /* | ||
| * Pest configuration file. | ||
| */ | ||
|
|
||
| require_once __DIR__ . '/bootstrap.php'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| <?php | ||
| /* | ||
| +-------------------------------------------------------------------------+ | ||
| | Copyright (C) 2004-2026 The Cacti Group | | ||
| +-------------------------------------------------------------------------+ | ||
| | Cacti: The Complete RRDtool-based Graphing Solution | | ||
| +-------------------------------------------------------------------------+ | ||
| */ | ||
|
|
||
| /* | ||
| * Verify plugin source files do not use PHP 8.0+ syntax. | ||
| * Cacti 1.2.x plugins must remain compatible with PHP 7.4. | ||
| */ | ||
|
|
||
| describe('PHP 7.4 compatibility in mikrotik', function () { | ||
| $files = array( | ||
| 'mikrotik.php', | ||
| 'mikrotik_users.php', | ||
| 'poller_graphs.php', | ||
| 'poller_mikrotik.php', | ||
| 'setup.php', | ||
| ); | ||
|
|
||
| it('does not use str_contains (PHP 8.0)', function () use ($files) { | ||
| foreach ($files as $relativeFile) { | ||
| $path = realpath(__DIR__ . '/../../' . $relativeFile); | ||
|
|
||
| if ($path === false) { | ||
| continue; | ||
| } | ||
|
|
||
| $contents = file_get_contents($path); | ||
|
|
||
| if ($contents === false) { | ||
| continue; | ||
| } | ||
|
|
||
| expect(preg_match('/\bstr_contains\s*\(/', $contents))->toBe(0, | ||
| "{$relativeFile} uses str_contains() which requires PHP 8.0" | ||
| ); | ||
| } | ||
| }); | ||
|
|
||
| it('does not use str_starts_with (PHP 8.0)', function () use ($files) { | ||
| foreach ($files as $relativeFile) { | ||
| $path = realpath(__DIR__ . '/../../' . $relativeFile); | ||
|
|
||
| if ($path === false) { | ||
| continue; | ||
| } | ||
|
|
||
| $contents = file_get_contents($path); | ||
|
|
||
| if ($contents === false) { | ||
| continue; | ||
| } | ||
|
|
||
| expect(preg_match('/\bstr_starts_with\s*\(/', $contents))->toBe(0, | ||
| "{$relativeFile} uses str_starts_with() which requires PHP 8.0" | ||
| ); | ||
| } | ||
| }); | ||
|
|
||
| it('does not use str_ends_with (PHP 8.0)', function () use ($files) { | ||
| foreach ($files as $relativeFile) { | ||
| $path = realpath(__DIR__ . '/../../' . $relativeFile); | ||
|
|
||
| if ($path === false) { | ||
| continue; | ||
| } | ||
|
|
||
| $contents = file_get_contents($path); | ||
|
|
||
| if ($contents === false) { | ||
| continue; | ||
| } | ||
|
|
||
| expect(preg_match('/\bstr_ends_with\s*\(/', $contents))->toBe(0, | ||
| "{$relativeFile} uses str_ends_with() which requires PHP 8.0" | ||
| ); | ||
| } | ||
| }); | ||
|
|
||
| it('does not use nullsafe operator (PHP 8.0)', function () use ($files) { | ||
| foreach ($files as $relativeFile) { | ||
| $path = realpath(__DIR__ . '/../../' . $relativeFile); | ||
|
|
||
| if ($path === false) { | ||
| continue; | ||
| } | ||
|
|
||
| $contents = file_get_contents($path); | ||
|
|
||
| if ($contents === false) { | ||
| continue; | ||
| } | ||
|
|
||
| expect(preg_match('/\?->/', $contents))->toBe(0, | ||
| "{$relativeFile} uses nullsafe operator which requires PHP 8.0" | ||
| ); | ||
| } | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,61 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <?php | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /* | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| +-------------------------------------------------------------------------+ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | Copyright (C) 2004-2026 The Cacti Group | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| +-------------------------------------------------------------------------+ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | Cacti: The Complete RRDtool-based Graphing Solution | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| +-------------------------------------------------------------------------+ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /* | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Verify migrated files use prepared DB helpers exclusively. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Catches regressions where raw db_execute/db_fetch_* calls creep back in. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| describe('prepared statement consistency in mikrotik', function () { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it('uses prepared DB helpers in all plugin files', function () { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $targetFiles = array( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'mikrotik.php', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'mikrotik_users.php', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'poller_graphs.php', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'poller_mikrotik.php', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'setup.php', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $rawPattern = '/\bdb_(?:execute|fetch_row|fetch_assoc|fetch_cell)\s*\(/'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $preparedPattern = '/\bdb_(?:execute|fetch_row|fetch_assoc|fetch_cell)_prepared\s*\(/'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach ($targetFiles as $relativeFile) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+16
to
+28
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it('uses prepared DB helpers in all plugin files', function () { | |
| $targetFiles = array( | |
| 'mikrotik.php', | |
| 'mikrotik_users.php', | |
| 'poller_graphs.php', | |
| 'poller_mikrotik.php', | |
| 'setup.php', | |
| ); | |
| $rawPattern = '/\bdb_(?:execute|fetch_row|fetch_assoc|fetch_cell)\s*\(/'; | |
| $preparedPattern = '/\bdb_(?:execute|fetch_row|fetch_assoc|fetch_cell)_prepared\s*\(/'; | |
| foreach ($targetFiles as $relativeFile) { | |
| it('uses prepared DB helpers in migrated plugin files', function () { | |
| $targetFiles = array( | |
| 'mikrotik.php', | |
| 'mikrotik_users.php', | |
| 'poller_graphs.php', | |
| ); | |
| $legacyFiles = array( | |
| 'poller_mikrotik.php', | |
| 'setup.php', | |
| ); | |
| $rawPattern = '/\bdb_(?:execute|fetch_row|fetch_assoc|fetch_cell)\s*\(/'; | |
| $preparedPattern = '/\bdb_(?:execute|fetch_row|fetch_assoc|fetch_cell)_prepared\s*\(/'; | |
| foreach ($targetFiles as $relativeFile) { | |
| if (in_array($relativeFile, $legacyFiles, true)) { | |
| continue; | |
| } |
Copilot
AI
Apr 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test silently continues when realpath() or file_get_contents() fails. That means missing/unreadable files will cause the test to pass and can hide regressions (false negatives). Prefer failing the test with a clear message when a target file can’t be found/read, since these are security consistency checks.
| if ($path === false) { | |
| continue; | |
| } | |
| $contents = file_get_contents($path); | |
| if ($contents === false) { | |
| continue; | |
| } | |
| expect($path)->not->toBeFalse( | |
| "Expected target file {$relativeFile} to exist and be resolvable for prepared statement consistency checks" | |
| ); | |
| $contents = file_get_contents($path); | |
| expect($contents)->not->toBeFalse( | |
| "Expected target file {$relativeFile} at {$path} to be readable for prepared statement consistency checks" | |
| ); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,36 @@ | ||||||||||||||||||||||||||
| <?php | ||||||||||||||||||||||||||
| /* | ||||||||||||||||||||||||||
| +-------------------------------------------------------------------------+ | ||||||||||||||||||||||||||
| | Copyright (C) 2004-2026 The Cacti Group | | ||||||||||||||||||||||||||
| +-------------------------------------------------------------------------+ | ||||||||||||||||||||||||||
| | Cacti: The Complete RRDtool-based Graphing Solution | | ||||||||||||||||||||||||||
| +-------------------------------------------------------------------------+ | ||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /* | ||||||||||||||||||||||||||
| * Verify setup.php defines required plugin hooks and info function. | ||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| describe('mikrotik setup.php structure', function () { | ||||||||||||||||||||||||||
| $source = file_get_contents(realpath(__DIR__ . '/../../setup.php')); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
Comment on lines
+15
to
+16
|
||||||||||||||||||||||||||
| $source = file_get_contents(realpath(__DIR__ . '/../../setup.php')); | |
| $setupPath = realpath(__DIR__ . '/../../setup.php'); | |
| if ($setupPath === false) { | |
| throw new RuntimeException('Failed to resolve path to setup.php'); | |
| } | |
| $source = file_get_contents($setupPath); | |
| if ($source === false) { | |
| throw new RuntimeException(sprintf('Failed to read setup.php at "%s"', $setupPath)); | |
| } |
Copilot
AI
Apr 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The “name/version key” assertions currently look for a literal associative array syntax ("'name' =>" / "'version' =>") inside setup.php. In this codebase, plugin_mikrotik_version() reads INFO via parse_ini_file and setup.php contains no "'version' =>" entries, so this test will fail even when the plugin version info is correct. Suggestion: validate INFO (e.g., parse_ini_file on INFO and assert required keys), or execute plugin_mikrotik_version() in a controlled stubbed environment and assert the returned array has the expected keys.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Each check silently
continues if realpath() or file_get_contents() fails, which can let the PHP-compatibility test pass even if a plugin file is missing/unreadable (false negative). Consider failing with an explicit expectation/message when a file can’t be resolved/read so the test suite remains meaningful.