Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion includes/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -3414,7 +3414,13 @@ function thold_check_threshold(&$thold_data) {

// don't alert for this host if it's selected for maintenance
if (api_plugin_is_enabled('maint') || in_array('maint', $plugins, true)) {
include_once($config['base_path'] . '/plugins/maint/functions.php');
if (file_exists($config['base_path'] . '/plugins/maint/functions.php')) {
require_once($config['base_path'] . '/plugins/maint/functions.php');
} else if (file_exists($config['base_path'] . '/plugins/maint/includes/functions.php')) {
require_once($config['base_path'] . '/plugins/maint/includes/functions.php');
} else {
cacti_log('ERROR: cannot include maint functions', false, 'THOLD');
}
}

if (api_plugin_is_enabled('maint') && plugin_maint_check_cacti_host($thold_data['host_id'])) {
Expand Down
8 changes: 7 additions & 1 deletion includes/polling.php
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,13 @@ function thold_update_host_status() {
include_once($config['library_path'] . '/snmp.php');

if (api_plugin_is_enabled('maint')) {
include_once($config['base_path'] . '/plugins/maint/functions.php');
if (file_exists($config['base_path'] . '/plugins/maint/functions.php')) {
require_once($config['base_path'] . '/plugins/maint/functions.php');
} else if (file_exists($config['base_path'] . '/plugins/maint/includes/functions.php')) {
require_once($config['base_path'] . '/plugins/maint/includes/functions.php');
} else {
cacti_log('ERROR: cannot include maint functions', false, 'THOLD');
}
}

$ping_failure_count = read_config_option('ping_failure_count');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php
// Empty maint plugin fixture using the includes/ layout: the function it would
// provide is stubbed, so this file only needs to exist at this path.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/* Fixture stylesheet so thold_page_head()'s file_exists() theme-css branch is
reachable under the 'modern' theme returned by the test bootstrap. */
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php
// Load the plugin arrays through the same path shape as a Cacti installation.
require dirname(__DIR__, 6) . '/includes/arrays.php';
3 changes: 3 additions & 0 deletions tests/Fixtures/cacti-root-no-maint/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Cacti root fixture with no maint plugin installed, so neither
# plugins/maint/functions.php nor plugins/maint/includes/functions.php exists.
# This drives the "cannot include maint functions" error branch.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/* Fixture stylesheet so thold_page_head()'s file_exists() theme-css branch is
reachable under the 'modern' theme returned by the test bootstrap. */
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php
// Load the plugin arrays through the same path shape as a Cacti installation.
require dirname(__DIR__, 6) . '/includes/arrays.php';
8 changes: 6 additions & 2 deletions tests/Helpers/ThresholdScenario.php
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,13 @@ public function referenceStatistics($average, $max, $min, $last, $dsname = 'traf
/**
* Put the device into a maintenance window.
*
* @param string|null $cactiRoot Cacti root fixture to resolve the maint
* include against; defaults to the layout that ships functions.php at the
* plugin root.
*
* @return self
*/
public function inMaintenance() {
public function inMaintenance($cactiRoot = null) {
// Asked more than once per poll, so a queued value would run out.
CactiStubs::willAlwaysReturn('api_plugin_is_enabled', true);
CactiStubs::willAlwaysReturn('plugin_maint_check_cacti_host', true);
Expand All @@ -264,7 +268,7 @@ public function inMaintenance() {
* base_path at a tracked, repository-contained Cacti root fixture so a
* unit test never writes into the caller's Cacti checkout.
*/
$GLOBALS['config']['base_path'] = dirname(__DIR__) . '/Fixtures/cacti-root';
$GLOBALS['config']['base_path'] = $cactiRoot ?? dirname(__DIR__) . '/Fixtures/cacti-root';

return $this;
}
Expand Down
143 changes: 143 additions & 0 deletions tests/Unit/MaintIncludeResolutionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?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 |
+-------------------------------------------------------------------------+
| http://www.cacti.net/ |
+-------------------------------------------------------------------------+
*/

/**
* Both maintenance-aware entry points resolve the maint plugin the same way:
* the functions.php that ships at the plugin root, the includes/functions.php
* of the newer layout, or an error log when neither is present. These tests
* drive all three outcomes for thold_check_threshold() and
* thold_update_host_status() so a future change to the lookup is caught.
*/
final class MaintIncludeResolutionTest extends TestCase {
/**
* @return void
*/
public static function setUpBeforeClass(): void {
self::loadPluginSource('includes/functions.php');
self::loadPluginSource('includes/polling.php');
self::loadPluginSourceAlways('includes/arrays.php');
self::loadPluginConstants();
}

/**
* @param array<string, mixed> $overrides
*
* @return ThresholdScenario
*/
private function baseline(array $overrides = []) {
return ThresholdScenario::threshold($overrides + [
'thold_type' => 1,
'bl_type' => 0,
'bl_pct_up' => 10,
'bl_pct_down' => 10,
'bl_ref_time_range' => 3600,
'bl_fail_trigger' => 1,
])
->alertRecipient('ops@example.org')
->referenceStatistics(100, 100, 100, 100);
}

/**
* @param string $name
*
* @return string
*/
private function fixtureRoot($name) {
return dirname(__DIR__) . '/Fixtures/' . $name;
}

/**
* The newer maint layout keeps functions.php under includes/; a device in a
* maintenance window still resolves it and suppresses the notification.
*
* @return void
*/
public function testCheckThresholdResolvesMaintFromIncludesLayout(): void {
$outcome = $this->baseline(['lastread' => 500])
->inMaintenance($this->fixtureRoot('cacti-root-maint-includes'))
->poll();

$this->assertSame(0, $outcome->mailCount());
}

/**
* With neither maint layout present the include is skipped and the failure
* is logged rather than fatal.
*
* @return void
*/
public function testCheckThresholdLogsWhenMaintFunctionsMissing(): void {
$this->baseline(['lastread' => 500])
->inMaintenance($this->fixtureRoot('cacti-root-no-maint'))
->poll();

$this->assertContains('ERROR: cannot include maint functions', CactiStubs::$log);
}

/**
* Drive thold_update_host_status() far enough to run the maint include
* block. Downstream work needs a live Cacti, so engine messages are
* swallowed and any throw past the block is ignored.
*
* @param string $cactiRoot
*
* @return void
*/
private function runHostStatus($cactiRoot) {
CactiStubs::willAlwaysReturn('api_plugin_is_enabled', true);
CactiStubs::$configOptions['alert_deadnotify'] = 'on';
$GLOBALS['config']['base_path'] = $cactiRoot;

set_error_handler(static fn (): bool => true);
ob_start();

try {
thold_update_host_status();
} catch (\Throwable $e) {
// Reaching the maint include block is the point.
} finally {
ob_end_clean();
restore_error_handler();
}
}

/**
* @return void
*/
public function testHostStatusResolvesMaintFromPluginRoot(): void {
$this->runHostStatus($this->fixtureRoot('cacti-root'));

$this->assertNotContains('ERROR: cannot include maint functions', CactiStubs::$log);
}

/**
* @return void
*/
public function testHostStatusResolvesMaintFromIncludesLayout(): void {
$this->runHostStatus($this->fixtureRoot('cacti-root-maint-includes'));

$this->assertNotContains('ERROR: cannot include maint functions', CactiStubs::$log);
}

/**
* @return void
*/
public function testHostStatusLogsWhenMaintFunctionsMissing(): void {
$this->runHostStatus($this->fixtureRoot('cacti-root-no-maint'));

$this->assertContains('ERROR: cannot include maint functions', CactiStubs::$log);
}
}
Loading