<?php /* ** Copyright (C) 2001-2025 Zabbix SIA ** ** This program is free software: you can redistribute it and/or modify it under the terms of ** the GNU Affero General Public License as published by the Free Software Foundation, version 3. ** ** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; ** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ** See the GNU Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see <https://www.gnu.org/licenses/>. **/ require_once dirname(__FILE__).'/../include/CIntegrationTest.php'; /** * Test suite for Script Manual Input * * @required-components server * @configurationDataProvider serverConfigurationProvider * @backup hosts,scripts * * @onAfter deleteData */ class testScriptManualInput extends CIntegrationTest { private static $hostid; private static $hostgroupid; private static $scriptids; public function createTestHostData(): bool { static $initialized = false; /* * First, create host group and a host in that group * * Calling for a script execution requires a host context, * regardless of where the script will eventually execute. * * Although a default Zabbix installation already defines a host, the * server itself, we want the tests to be self-contained. */ if ($initialized) return true; global $DB; if (!isset($DB['DB'])) DBconnect($error); $response = CDataHelper::call('hostgroup.create', ['name' => 'Test Hosts']); self::$hostgroupid = $response['groupids'][0]; $response = CDataHelper::call('host.create', [ 'host' => 'Test Host', 'groups' => [ [ 'groupid' => self::$hostgroupid ] ] ]); self::$hostid = $response['hostids'][0]; $initialized = true; return true; } /** * @depends prepareTestHostData */ public function createTestScriptData(): bool { static $initialized = false; if ($initialized) return true; global $DB; if (!isset($DB['DB'])) DBconnect($error); // Create scripts with and without manual input support. $scripts = [ /* * See [1] for a reference of these script object descriptions. * * Required fields for script creation are: * name, type, scope * Required fields when the 'type' is set to 'Script': * command * Required fields when 'manualinput' is set to 'Enabled': * manualinput_validator, manualinput_validator_type, manualinput_prompt * Required fields when 'manualinput_validator_type' is set to 'String': * manualinput_default_value * * Note: * When a script is created and is set to take manual input, * the API will also verify that 'manualinput_default_value' * passes the specified 'manualinput_validator', when the * validator is a regular expression * ('manualinput_validator_type' is ZBX_SCRIPT_MANUALINPUT_TYPE_STRING). * * If no value for 'manualinput_default_value' is provided, * the API will fall back to setting it to an empty string, * which may not pass the validator, and thus fail the script * creation. * * We explicitly set 'manualinput_prompt' and 'manualinput_default_value' * values here to appease the API, but they're not actually * relied upon in these tests. * * [1]: https://www.zabbix.com/documentation/current/en/manual/api/reference/script/object */ [ // Script that does not take any additional input 'name' => 'Manual Input Test Script #1', 'type' => ZBX_SCRIPT_TYPE_CUSTOM_SCRIPT, 'command' => "echo 'Your mindmacro has been expanded'", 'scope' => ZBX_SCRIPT_SCOPE_HOST, 'manualinput' => ZBX_SCRIPT_MANUALINPUT_DISABLED ], [ // Script accepts one or more lowercase characters for {MANUALINPUT} 'name' => 'Manual Input Test Script #2', 'type' => ZBX_SCRIPT_TYPE_CUSTOM_SCRIPT, 'command' => "echo 'Your {MANUALINPUT} has been expanded'", 'scope' => ZBX_SCRIPT_SCOPE_HOST, 'manualinput' => ZBX_SCRIPT_MANUALINPUT_ENABLED, 'manualinput_validator_type' => ZBX_SCRIPT_MANUALINPUT_TYPE_STRING, 'manualinput_validator' => '^[a-z]+$', 'manualinput_prompt' => 'Tis but a prompt', 'manualinput_default_value' => 'abcdefghijklmnopqrstuvwxyz' ], [ // Script accept one of a set of options for {MANUALINPUT} 'name' => 'Manual Input Test Script #3', 'type' => ZBX_SCRIPT_TYPE_CUSTOM_SCRIPT, 'command' => "echo 'Your {MANUALINPUT} has been expanded'", 'scope' => ZBX_SCRIPT_SCOPE_HOST, 'manualinput' => ZBX_SCRIPT_MANUALINPUT_ENABLED, 'manualinput_validator_type' => ZBX_SCRIPT_MANUALINPUT_TYPE_LIST, 'manualinput_validator' => 'macro,mind', 'manualinput_prompt' => "T'is but a prompt" ] ]; $response = CDataHelper::call('script.create', $scripts); self::$scriptids = $response['scriptids']; $initialized = true; return true; } /** * Component configuration provider for server related tests. * * @return array */ public function serverConfigurationProvider(): array { return [ self::COMPONENT_SERVER => [ 'StartTrappers' => 1, 'EnableGlobalScripts' => 1 ] ]; } /** * Component configuration provider for server related tests. * * @return array */ public function disabledScriptsConfigurationProvider(): array { return [ self::COMPONENT_SERVER => [ 'EnableGlobalScripts' => 0 ] ]; } /** * @dataProvider validRequestDataProvider * * @param array $request_params Parameters to the script.execute API call * @param string $expected_result String matching a successful API call return value */ public function testScriptManualInput_ValidRequests(array $request_params, string $expected_result): void { $response = $this->call('script.execute', $request_params); $this->assertArrayHasKey('result', $response); $this->assertEquals('success', $response['result']['response']); $this->assertEquals($expected_result, $response['result']['value']); } /** * @dataProvider invalidRequestDataProvider * * @param array $request_params Parameters to the script.execute API call * @param string $expected_result String matching a failed API call error value */ public function testScriptManualInput_InvalidRequests(array $request_params, string $expected_result): void { $response = $this->call('script.execute', $request_params, $expected_result); } /** * Test functionality of EnableGlobalScripts option * * @configurationDataProvider disabledScriptsConfigurationProvider * @backup scripts * */ public function testScriptManualInput_DisabledGlobalScripts() { // CAPITest::call() has assertions for 'result' key in a response, these will throw an exception $this->expectException(\PHPUnit\Framework\ExpectationFailedException::class); $this->call('script.execute', [ 'scriptid' => self::$scriptids[0], 'hostid' => self::$hostid ]); $response = $this->call('script.update', [ 'scriptid' => self::$scriptids[0], 'execute_on' => ZBX_SCRIPT_EXECUTE_ON_PROXY ]); $this->assertArrayHasKey("scriptids", $response['result']); $this->reloadConfigurationCache(self::COMPONENT_SERVER); sleep(2); $this->expectException(\PHPUnit\Framework\ExpectationFailedException::class); $this->call('script.execute', [ 'scriptid' => self::$scriptids[0], 'hostid' => self::$hostid ]); } /** * @return array<int, array> */ public function invalidRequestDataProvider(): array { $this->createTestHostData(); $this->createTestScriptData(); return [ /* * In general, there are only two scenarios in which a * request will fail in regards to manual input: * (1): there is no manual input provided at all * (2): the input didn't pass validation */ [ 'request_params' => [ 'hostid' => self::$hostid, 'scriptid' => self::$scriptids[1], 'manualinput' => '5h0uld n0t m4tch a ^[a-z]+$ pattern' ], 'expected_result' => 'Provided script user input failed validation.' ], [ 'request_params' => [ 'hostid' => self::$hostid, 'scriptid' => self::$scriptids[2], 'manualinput' => 'orcam' ], 'expected_result' => 'Provided script user input failed validation.' ], [ 'request_params' => [ 'hostid' => self::$hostid, 'scriptid' => self::$scriptids[2] ], 'expected_result' => 'Script takes user input, but none was provided.' ] ]; } /** * @return array<int, array> */ public function validRequestDataProvider(): array { $this->createTestHostData(); $this->createTestScriptData(); return [ [ /* * This case tests the invocation of a script that doesn't take additional input but provides it in the * request anyway. In such a case, the script should still execute, and, upon success, the response * should still contain a value from the script execution, but the server should log a warning message * indicating this. */ 'request_params' => [ 'hostid' => self::$hostid, 'scriptid' => self::$scriptids[0], 'manualinput' => 'abcdefghijklmnopqrstuvwxyz' ], 'expected_result' => "Your mindmacro has been expanded\n" ], [ 'request_params' => [ 'hostid' => self::$hostid, 'scriptid' => self::$scriptids[1], 'manualinput' => 'abcdefghijklmnopqrstuvwxyz' ], 'expected_result' => "Your abcdefghijklmnopqrstuvwxyz has been expanded\n" ], [ 'request_params' => [ 'hostid' => self::$hostid, 'scriptid' => self::$scriptids[2], 'manualinput' => 'macro' ], 'expected_result' => "Your macro has been expanded\n" ] ]; } /** * Delete data objects created for this test suite */ public static function deleteData(): void { CDataHelper::call('script.delete', self::$scriptids); CDataHelper::call('host.delete', [self::$hostid]); CDataHelper::call('hostgroup.delete', [self::$hostgroupid]); } }