not->toContain('VALUES ("spikekill_lastrun", ?\''); expect($src)->toContain('VALUES ("spikekill_lastrun", ?)'); }); // --------------------------------------------------------------------------- // Bug 3: Unescaped shell argument — RRD path passed raw to exec() // --------------------------------------------------------------------------- test('poller_spikekill.php escapes the RRD file path with cacti_escapeshellarg', function () use ($pollerPath) { $src = file_get_contents($pollerPath); // The path must be wrapped — bare concatenation is the bug. expect($src)->not->toMatch('/[\'"]--rrdfile=[\'"] \. \$f[^)]/'); expect($src)->toContain('cacti_escapeshellarg($f)'); }); test('poller_spikekill.php --rrdfile argument uses cacti_escapeshellarg not raw concat', function () use ($pollerPath) { $src = file_get_contents($pollerPath); // Ensure the full safe pattern is present. expect($src)->toContainPhp("' --rrdfile=' . cacti_escapeshellarg(\$f)"); }); // --------------------------------------------------------------------------- // Bug 4: Double-negation validation — !$this->numspike != '' // --------------------------------------------------------------------------- test('lib/spikekill.php numspike validation has no double-negation', function () use ($spikeLib) { $src = file_get_contents($spikeLib); // The buggy pattern: !$this->numspike != '' // The boolean cast of !$x compared to a string is meaningless. expect($src)->not->toContain('!$this->numspike !='); expect($src)->not->toMatch('/!\s*\$this->numspike\s*!=/'); }); test('lib/spikekill.php numspike validation uses simple not-empty check', function () use ($spikeLib) { $src = file_get_contents($spikeLib); // Confirm the correct form exists — numspike is checked directly. expect($src)->toContain("\$this->numspike != ''"); }); // --------------------------------------------------------------------------- // Bug 5: Inverted range validation — empty() guard applied to wrong branch // --------------------------------------------------------------------------- test('lib/spikekill.php validates time range only when both ends are non-empty', function () use ($spikeLib) { $src = file_get_contents($spikeLib); // The buggy form validated the range inside if (empty($this->out_start)) // — i.e., when no range was set, which is always a no-op. // The fix validates inside if (!empty($this->out_start)). expect($src)->not->toMatch('/if\s*\(\s*empty\s*\(\s*\$this->out_start\s*\)\s*\)\s*\{[^}]*out_start\s*>=\s*\$this->out_end/s'); // The correct guard validates when a range IS specified. expect($src)->toMatch('/if\s*\(\s*!\s*empty\s*\(\s*\$this->out_start\s*\)\s*\)\s*\{[^}]*out_start\s*>=\s*\$this->out_end/s'); }); // --------------------------------------------------------------------------- // Bug 6: Backup cleanup — second unlink checked $xmlfile instead of $bakfile // --------------------------------------------------------------------------- test('lib/spikekill.php backup cleanup checks $bakfile not $xmlfile for .bak deletion', function () use ($spikeLib) { $src = file_get_contents($spikeLib); // The fixed form: each variable guards its own deletion. // Verify both exist and that $xmlfile does not guard $bakfile. expect($src)->toContain('file_exists($xmlfile)'); expect($src)->toContain('unlink($xmlfile)'); expect($src)->toContain('file_exists($bakfile)'); expect($src)->toContain('unlink($bakfile)'); // Specifically: the line that unlinks $bakfile must guard with $bakfile. expect($src)->toMatch('/file_exists\s*\(\s*\$bakfile\s*\)\s*\)*\s*\{\s*unlink\s*\(\s*\$bakfile\s*\)/s'); }); // --------------------------------------------------------------------------- // Bug 7: Default method mismatch — default '2' (Float Range) vs dropdown '1' // --------------------------------------------------------------------------- test('global_settings.php spikekill_method default matches the only dropdown option', function () use ($globalSettings) { $src = file_get_contents($globalSettings); // Extract the spikekill_method block. preg_match("/'spikekill_method'\s*=>\s*\[(.*?)\],\s*'spikekill_/s", $src, $m); $block = $m[1] ?? ''; expect($block)->not->toBeEmpty('spikekill_method block not found'); // Default must be '1' (Standard Deviation) — not '2' (Float Range). expect($block)->toContain("'default' => '1'"); expect($block)->not->toContain("'default' => '2'"); }); test('global_settings.php spikekill_method dropdown contains Standard Deviation as option 1', function () use ($globalSettings) { $src = file_get_contents($globalSettings); preg_match("/'spikekill_method'\s*=>\s*\[(.*?)\],\s*'spikekill_/s", $src, $m); $block = $m[1] ?? ''; // Option 1 must exist so the default is selectable. expect($block)->toMatch('/1\s*=>/'); expect($block)->toContain('Standard Deviation'); });