<?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/CLegacyWebTest.php'; require_once dirname(__FILE__).'/../behaviors/CMessageBehavior.php'; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverKeys; /** * @backup triggers * * @onBefore prepareTriggerData */ class testFormTrigger extends CLegacyWebTest { const HOST = 'Host for Triggers test'; protected static $long_key_hostid; /** * Attach MessageBehavior to the test. * * @return array */ public function getBehaviors() { return ['class' => CMessageBehavior::class]; } public function prepareTriggerData() { // Create host group for hosts with items triggers. $hostgroups = CDataHelper::call('hostgroup.create', [['name' => 'Group for triggers test']]); $this->assertArrayHasKey('groupids', $hostgroups); $groupid = $hostgroups['groupids'][0]; // Create hosts for various tests. $hosts = CDataHelper::call('host.create', [ [ 'host' => self::HOST, 'groups' => [['groupid' => $groupid]] ], [ 'host' => STRING_128, 'groups' => [['groupid' => $groupid]] ] ]); $this->assertArrayHasKey('hostids', $hosts); $hostid = $hosts['hostids'][0]; self::$long_key_hostid = $hosts['hostids'][1]; // Create items. $items_data = []; $value_types = [ 'Float' => ITEM_VALUE_TYPE_FLOAT, 'Character' => ITEM_VALUE_TYPE_STR, 'Unsigned' => ITEM_VALUE_TYPE_UINT64, 'Text' => ITEM_VALUE_TYPE_TEXT ]; foreach ($value_types as $name => $type) { $items_data[] = [ 'hostid' => $hostid, 'name' => $name.' item', 'key_' => $name, 'type' => ITEM_TYPE_TRAPPER, 'value_type' => $type ]; } // An additional item with a long key. $items_data[] = [ 'name' => 'test', 'key_' => STRING_2048, 'hostid' => self::$long_key_hostid, 'type' => ITEM_TYPE_TRAPPER, 'value_type' => ITEM_VALUE_TYPE_FLOAT ]; CDataHelper::call('item.create', $items_data); // Create triggers for various tests. CDataHelper::call('trigger.create', [ [ 'description' => 'testFormTrigger1', 'expression' => 'last(/'.self::HOST.'/Float,#1)=0', 'priority' => 0 ], [ 'description' => 'Trigger with long expression for simple update', 'expression' => 'last(/'.STRING_128.'/'.STRING_2048.')=0' ], [ 'description' => 'Trigger with long expression for update', 'expression' => 'last(/'.STRING_128.'/'.STRING_2048.')>0' ] ]); } // Returns layout data public static function layout() { return [ // #0. [ ['constructor' => 'open', 'host' => self::HOST ] ], // #1. [ ['constructor' => 'open_close', 'host' => self::HOST ] ], // #2. [ ['constructor' => 'open', 'severity' => 'Warning', 'host' => self::HOST ] ], // #3. [ ['constructor' => 'open_close', 'severity' => 'Disaster', 'host' => self::HOST ] ], // #4. [ ['severity' => 'Not classified', 'host' => self::HOST ] ], // #5. [ ['severity' => 'Information', 'host' => self::HOST ] ], // #6. [ ['severity' => 'Warning', 'host' => self::HOST ] ], // #7. [ ['severity' => 'Average', 'host' => self::HOST ] ], // #8. [ ['severity' => 'High', 'host' => self::HOST ] ], // #9. [ ['severity' => 'Disaster', 'host' => self::HOST ] ], // #10. [ ['constructor' => 'open', 'template' => 'Inheritance test template' ] ], // #11. [ ['constructor' => 'open_close', 'template' => 'Inheritance test template' ] ], // #12. [ ['constructor' => 'open', 'severity' => 'Warning', 'template' => 'Inheritance test template' ] ], // #13. [ [ 'constructor' => 'open_close', 'severity' => 'Disaster', 'template' => 'Inheritance test template' ] ], // #14. [ ['severity' => 'Not classified', 'template' => 'Inheritance test template' ] ], // #15. [ ['severity' => 'Information', 'template' => 'Inheritance test template' ] ], // #16. [ ['severity' => 'Warning', 'template' => 'Inheritance test template' ] ], // #17. [ ['severity' => 'Average', 'template' => 'Inheritance test template' ] ], // #18. [ ['severity' => 'High', 'template' => 'Inheritance test template' ] ], // #19. [ ['severity' => 'Disaster', 'template' => 'Inheritance test template' ] ], // #20. [ ['host' => self::HOST, 'form' => 'testFormTrigger1' ] ], // #21. [ [ 'template' => 'Inheritance test template', 'form' => 'testInheritanceTrigger1' ] ], // #22. [ [ 'host' => 'Template inheritance test host', 'form' => 'testInheritanceTrigger1', 'templatedHost' => true, 'hostTemplate' => 'Inheritance test template' ] ], // #23. [ [ 'host' => 'Template inheritance test host', 'form' => 'testInheritanceTrigger2', 'templatedHost' => true, 'hostTemplate' => 'Inheritance test template' ] ], // #24. [ [ 'host' => self::HOST, 'form' => 'testFormTrigger1', 'constructor' => 'open' ] ], // #25. [ [ 'template' => 'Inheritance test template', 'form' => 'testInheritanceTrigger1', 'constructor' => 'open' ] ], // #26. [ [ 'host' => 'Template inheritance test host', 'form' => 'testInheritanceTrigger1', 'templatedHost' => true, 'constructor' => 'open' ] ] ]; } /** * @dataProvider layout */ public function testFormTrigger_CheckLayout($data) { if (isset($data['template'])) { $this->zbxTestLogin('zabbix.php?action=template.list'); $form = $this->query('name:zbx_filter')->asForm()->waitUntilReady()->one(); $this->filterEntriesAndOpenTriggers($data['template'], $form); } if (isset($data['host'])) { $this->zbxTestLogin(self::HOST_LIST_PAGE); $form = $this->query('name:zbx_filter')->asForm()->waitUntilReady()->one(); $this->filterEntriesAndOpenTriggers($data['host'], $form); } $this->zbxTestCheckTitle('Configuration of triggers'); $this->zbxTestCheckHeader('Triggers'); if (isset($data['form'])) { $this->zbxTestClickLinkTextWait($data['form']); } else { $this->zbxTestContentControlButtonClickTextWait('Create trigger'); } $dialog = COverlayDialogElement::find()->waitUntilReady()->one(); $form = $dialog->asForm(); $this->zbxTestCheckTitle('Configuration of triggers'); $this->assertEquals(isset($data['form']) ? 'Trigger' : 'New trigger', $dialog->getTitle()); if (isset($data['constructor'])) { switch ($data['constructor']) { case 'open': $form->query('button:Expression constructor')->waitUntilClickable()->one()->click(); $form->query('id:expression')->waitUntilNotVisible(); break; case 'open_close': $form->query('button:Expression constructor')->waitUntilClickable()->one()->click(); $form->query('id:expression')->waitUntilNotVisible(); $form->query('button:Close expression constructor')->waitUntilClickable()->one()->click(); $form->query('id:expression')->waitUntilVisible(); break; } } $this->assertEquals('Trigger', $form->getSelectedTab()); if (isset($data['templatedHost'])) { $this->zbxTestTextPresent('Parent triggers'); if (isset($data['hostTemplate'])) { $this->zbxTestAssertElementPresentXpath("//a[text()='".$data['hostTemplate']."']"); } } else { $this->zbxTestTextNotPresent('Parent triggers'); } $this->zbxTestTextPresent('Name'); $this->zbxTestAssertVisibleId('name'); $this->zbxTestAssertAttribute("//input[@id='name']", 'maxlength', 255); if (!isset($data['constructor']) || $data['constructor'] == 'open_close') { $this->zbxTestTextPresent(['Expression', 'Expression constructor']); $this->zbxTestAssertVisibleXpath("//textarea[@id='expression']"); $this->zbxTestAssertAttribute("//textarea[@id='expression']", 'rows', 7); if (isset($data['templatedHost'])) { $this->zbxTestAssertAttribute("//textarea[@id='expression']", 'readonly'); } $this->zbxTestAssertVisibleXpath("//button[@name='insert']"); $this->zbxTestAssertElementText("//button[@name='insert']", 'Add'); if (isset($data['templatedHost'])) { $this->zbxTestAssertAttribute("//button[@name='insert']", 'disabled'); } $this->assertEquals(0, $dialog->query('button', ['id:add_expression', 'Edit', 'id:insert-macro'])->all() ->filter(CElementFilter::CLICKABLE)->count() ); } else { $this->zbxTestTextPresent('Expression'); $this->zbxTestAssertVisibleId('expr_temp'); $this->zbxTestAssertAttribute("//textarea[@id='expr_temp']", 'rows', 7); $this->zbxTestAssertAttribute("//textarea[@id='expr_temp']", 'readonly'); $this->zbxTestTextPresent('Close expression constructor'); $this->zbxTestAssertNotVisibleXpath('//input[@name="expression"]'); if (!isset($data['form'])) { $this->zbxTestAssertVisibleXpath("//div[@id='expression-row']//button[@id='add_expression']"); } elseif (isset($data['templatedHost'])) { $this->assertEquals(0, $dialog->query('button', ['And', 'Or', 'Replace', 'Edit', 'Insert expression']) ->all()->filter(CElementFilter::CLICKABLE)->count() ); } else { $this->assertFalse($this->query("xpath://div[@id='expression-row']//button[@id='add_expression']") ->one()->isDisplayed() ); $this->assertEquals(2, $dialog->query('button', ['Edit', 'Insert expression']) ->all()->filter(CElementFilter::CLICKABLE)->count() ); $this->assertEquals(0, $dialog->query('button', ['And', 'Or', 'Replace'])->all() ->filter(CElementFilter::CLICKABLE)->count() ); } $this->zbxTestAssertVisibleXpath("//button[@name='insert']"); $this->zbxTestAssertElementText("//button[@name='insert']", 'Edit'); if (isset($data['templatedHost'])) { $this->zbxTestAssertElementPresentXpath("//button[@name='insert'][@disabled]"); } $this->zbxTestAssertVisibleId('insert-macro'); $this->zbxTestAssertElementText("//button[@id='insert-macro']", 'Insert expression'); if (isset($data['templatedHost'])) { $this->zbxTestAssertElementPresentXpath("//button[@id='insert-macro'][@disabled]"); } if (!isset($data['templatedHost'])) { $this->zbxTestTextPresent(['Target', 'Expression', 'Action', 'Info', 'Close expression constructor']); } else { $this->zbxTestTextPresent(['Expression', 'Info', 'Close expression constructor']); } $this->zbxTestTextPresent('Close expression constructor'); } $this->zbxTestTextPresent(['OK event generation', 'PROBLEM event generation mode']); $this->zbxTestTextPresent(['Expression', 'Recovery expression', 'None']); $this->zbxTestTextPresent(['Single', 'Multiple']); if (!isset($data['templatedHost'])) { $this->assertTrue($this->zbxTestCheckboxSelected('type_0')); } $this->zbxTestTextPresent('Description'); $this->zbxTestAssertVisibleId('description'); $this->zbxTestAssertAttribute("//textarea[@id='description']", 'rows', 7); $entry_name = $dialog->asForm()->getField('Menu entry name'); foreach (['placeholder' => 'Trigger URL', 'maxlength' => 64] as $attribute => $value) { $this->assertEquals($value, $entry_name->getAttribute($attribute)); } // Check hintbox. $this->query('class:zi-help-filled-small')->one()->click(); $hint = $this->query('xpath:.//div[@class="overlay-dialogue wordbreak"]')->waitUntilPresent()->one(); // Assert text. $this->assertEquals('Menu entry name is used as a label for the trigger URL in the event context menu.', $hint->getText() ); // Press Escape key to close hintbox. $this->page->pressKey(WebDriverKeys::ESCAPE); $hint->waitUntilNotVisible(); $this->zbxTestTextPresent('Menu entry URL'); $this->zbxTestAssertVisibleId('url'); $this->zbxTestAssertAttribute("//input[@id='url']", 'maxlength', 2048); $this->zbxTestAssertElementPresentId('priority_0'); $this->assertTrue($this->zbxTestCheckboxSelected('priority_0')); $this->zbxTestAssertElementText("//*[@id='priority_0']/../label", 'Not classified'); $this->zbxTestAssertElementPresentId('priority_1'); $this->zbxTestAssertElementText("//*[@id='priority_1']/../label", 'Information'); $this->zbxTestAssertElementPresentId('priority_2'); $this->zbxTestAssertElementText("//*[@id='priority_2']/../label", 'Warning'); $this->zbxTestAssertElementPresentId('priority_3'); $this->zbxTestAssertElementText("//*[@id='priority_3']/../label", 'Average'); $this->zbxTestAssertElementPresentId('priority_4'); $this->zbxTestAssertElementText("//*[@id='priority_4']/../label", 'High'); $this->zbxTestAssertElementPresentId('priority_5'); $this->zbxTestAssertElementText("//*[@id='priority_5']/../label", 'Disaster'); if (isset($data['severity'])) { switch ($data['severity']) { case 'Not classified': $this->zbxTestClickXpathWait("//*[@id='priority_0']/../label"); break; case 'Information': $this->zbxTestClickXpathWait("//*[@id='priority_1']/../label"); break; case 'Warning': $this->zbxTestClickXpathWait("//*[@id='priority_2']/../label"); break; case 'Average': $this->zbxTestClickXpathWait("//*[@id='priority_3']/../label"); break; case 'High': $this->zbxTestClickXpathWait("//*[@id='priority_4']/../label"); break; case 'Disaster': $this->zbxTestClickXpathWait("//*[@id='priority_5']/../label"); break; } } $this->zbxTestTextPresent('Enabled'); $this->zbxTestAssertElementPresentId('status'); $this->zbxTestAssertAttribute("//input[@id='status']", 'type', 'checkbox'); $dialog_footer = $dialog->getFooter(); if (isset($data['form']) && !isset($data['templatedHost'])) { $this->assertEquals(4, $dialog_footer->query('button', ['Update', 'Clone', 'Delete', 'Cancel'])->all() ->filter(CElementFilter::CLICKABLE)->count() ); } elseif (isset($data['templatedHost'])) { $this->assertEquals(3, $dialog_footer->query('button', ['Update', 'Clone', 'Cancel'])->all() ->filter(CElementFilter::CLICKABLE)->count() ); $this->assertFalse($dialog_footer->query('button:Delete')->one()->isClickable()); $this->assertTrue($this->zbxTestCheckboxSelected('recovery_mode_0')); $this->zbxTestAssertElementPresentXpath("//input[@id='recovery_mode_0'][@readonly]"); } else { $this->assertEquals(2, $dialog_footer->query('button', ['Add', 'Cancel'])->all() ->filter(CElementFilter::CLICKABLE)->count() ); } $this->zbxTestTabSwitch('Dependencies'); $this->zbxTestTextPresent(['Dependencies', 'Name', 'Action']); if (!isset($data['template'])) { $this->zbxTestAssertElementText("//button[@id='add-dep-trigger']", 'Add'); } else { $this->zbxTestAssertElementText("//button[@id='add-dep-template-trigger']", 'Add'); $this->zbxTestAssertElementText("//button[@id='add-dep-host-trigger']", 'Add host trigger'); } COverlayDialogElement::find()->one()->close(); } public function testFormTrigger_SimpleUpdate() { $sqlTriggers = 'select * from triggers order by triggerid'; $sqlFunctions = 'select * from functions order by functionid'; $oldHashTriggers = CDBHelper::getHash($sqlTriggers); $oldHashFunctions = CDBHelper::getHash($sqlFunctions); $this->zbxTestLogin(self::HOST_LIST_PAGE); $form = $this->query('name:zbx_filter')->asForm()->waitUntilReady()->one(); $this->filterEntriesAndOpenTriggers(self::HOST, $form); $this->zbxTestClickLinkTextWait('testFormTrigger1'); COverlayDialogElement::find()->waitUntilReady()->one()->query('button:Update')->one()->click(); COverlayDialogElement::ensureNotPresent(); $this->zbxTestCheckTitle('Configuration of triggers'); $this->zbxTestWaitUntilMessageTextPresent('msg-good', 'Trigger updated'); $this->zbxTestTextPresent('testFormTrigger1'); $this->zbxTestCheckHeader('Triggers'); $this->assertEquals($oldHashTriggers, CDBHelper::getHash($sqlTriggers)); $this->assertEquals($oldHashFunctions, CDBHelper::getHash($sqlFunctions)); } // Returns create data public static function create() { return [ // #0. [ [ 'expected' => TEST_BAD, 'error_msg' => 'Cannot add trigger', 'errors' => [ 'Incorrect value for field "name": cannot be empty.', 'Incorrect value for field "expression": cannot be empty.' ] ] ], // #1. [ [ 'expected' => TEST_BAD, 'description' => 'MyTrigger', 'error_msg' => 'Cannot add trigger', 'errors' => [ 'Incorrect value for field "expression": cannot be empty.' ] ] ], // #2. [ [ 'expected' => TEST_BAD, 'expression' => '6 & 0 | 0', 'error_msg' => 'Cannot add trigger', 'errors' => [ 'Incorrect value for field "name": cannot be empty.' ] ] ], // #3. [ [ 'expected' => TEST_BAD, 'description' => 'MyTrigger', 'expression' => '6 and 0 or 0', 'error_msg' => 'Cannot add trigger', 'errors' => [ 'Invalid parameter "/1/expression": trigger expression must contain at least one /host/key reference.' ] ] ], // #4. [ [ 'expected' => TEST_BAD, 'description' => 'MyTrigger', 'expression' => '{self::HOST}', 'error_msg' => 'Cannot add trigger', 'errors' => [ 'Invalid parameter "/1/expression": incorrect expression starting from "{self::HOST}".' ] ] ], // #5. [ [ 'expected' => TEST_GOOD, 'description' => 'MyTrigger_simple', 'expression' => 'last(/'.self::HOST.'/Float,#1)<0', 'formCheck' => true ] ], // #6. [ [ 'expected' => TEST_GOOD, 'description' => 'HTML_symbols∀∀∞≠⊃ΗΩξπ —€◊', 'expression' => 'last(/'.self::HOST.'/Float,#1)<0', 'formCheck' => true ] ], // #7. [ [ 'expected' => TEST_GOOD, 'description' => 'ASCII_characters!(3e ', 'expression' => 'last(/'.self::HOST.'/Float,#1)<0', 'formCheck' => true ] ], // #8. [ [ 'expected' => TEST_GOOD, 'description' => 'MyTrigger_allFields', 'type' => true, 'comments' => 'MyTrigger_allFields -Description textbox for comments', 'url' => 'http://MyTrigger_allFields.com', 'severity' => 'Disaster', 'status' => false, 'expression' => 'last(/'.self::HOST.'/Float,#1)<0', 'formCheck' => true ] ], // #9. [ [ 'expected' => TEST_GOOD, 'description' => '1234567890', 'expression' => 'last(/'.self::HOST.'/Float,#1)<0', 'formCheck' => true ] ], // #10. [ [ 'expected' => TEST_GOOD, 'description' => '0', 'expression' => 'last(/'.self::HOST.'/Float,#1)<0', 'formCheck' => true ] ], // #11. [ [ 'expected' => TEST_GOOD, 'description' => 'a?aa+', 'expression' => 'last(/'.self::HOST.'/Float,#1)<0', 'formCheck' => true ] ], // #12. [ [ 'expected' => TEST_GOOD, 'description' => '}aa]a{', 'expression' => 'last(/'.self::HOST.'/Float,#1)<0', 'formCheck' => true ] ], // #13. [ [ 'expected' => TEST_GOOD, 'description' => '-aaa=%', 'expression' => 'last(/'.self::HOST.'/Unsigned,#1)<0', 'formCheck' => true ] ], // #14. [ [ 'expected' => TEST_GOOD, 'description' => 'aaa,;:', 'expression' => 'last(/'.self::HOST.'/Float,#1)<0', 'formCheck' => true ] ], // #15. [ [ 'expected' => TEST_GOOD, 'description' => 'aaa><.', 'expression' => 'last(/'.self::HOST.'/Float,#1)<0', 'formCheck' => true ] ], // #16. [ [ 'expected' => TEST_GOOD, 'description' => 'aaa*&_', 'expression' => 'last(/'.self::HOST.'/Unsigned,#1)<0', 'formCheck' => true ] ], // #17. [ [ 'expected' => TEST_GOOD, 'description' => 'aaa#@!', 'expression' => 'last(/'.self::HOST.'/Float,#1)<0', 'formCheck' => true ] ], // #18. [ [ 'expected' => TEST_GOOD, 'description' => '([)$^', 'expression' => 'last(/'.self::HOST.'/Float,#1)<0', 'formCheck' => true ] ], // #19. [ [ 'expected' => TEST_GOOD, 'description' => 'MyTrigger_generalCheck', 'expression' => 'last(/'.self::HOST.'/Float,#1)<5', 'type' => true, 'comments' => 'Trigger status (expression) is recalculated every time Zabbix server receives new'. ' value, if this value is part of this expression. If time based functions are used in the'. ' expression, it is recalculated every 30 seconds by a zabbix timer process.', 'url_name' => 'Trigger context menu name for trigger URL.', 'url' => 'https://www.zabbix.com', 'severity' => 'High', 'status' => false ] ], // #20. [ [ 'expected' => TEST_GOOD, 'description' => 'MyTrigger_CheckURL', 'expression' => 'last(/'.self::HOST.'/Float,#1)<4', 'url_name' => 'MyTrigger: menu name', 'url' => 'triggers.php' ] ], // #21. [ [ 'expected' => TEST_BAD, 'description' => 'MyTrigger_CheckUrl', 'expression' => 'last(/'.self::HOST.'/Unsigned,#1)<5', 'url' => 'javascript:alert(123);', 'error_msg' => 'Cannot add trigger', 'errors' => [ 'Invalid parameter "/1/url": unacceptable URL.' ] ] ], // #22. [ [ 'expected' => TEST_BAD, 'description' => 'MyTrigger', 'expression' => 'last(/Zabbix host/Unsigned,#1)<0', 'error_msg' => 'Cannot add trigger', 'errors' => [ 'Incorrect trigger expression. Host "Zabbix host" does not exist or you have no access to this host.' ] ] ], // #23. [ [ 'expected' => TEST_BAD, 'description' => 'MyTrigger', 'expression' => 'last(/'.self::HOST.'/someItem.uptime,#1)<0', 'error_msg' => 'Cannot add trigger', 'errors' => [ 'Incorrect item key "someItem.uptime" provided for trigger expression on '. CXPathHelper::escapeQuotes(self::HOST).'.' ] ] ], // #24. [ [ 'expected' => TEST_BAD, 'description' => 'MyTrigger', 'expression' => 'somefunc(/'.self::HOST.'/Float,#1)<0', 'error_msg' => 'Cannot add trigger', 'errors' => [ 'Invalid parameter "/1/expression": unknown function "somefunc".' ] ] ], // #25. [ [ 'expected' => TEST_BAD, 'description' => 'MyTrigger', 'expression' => 'last(/'.self::HOST.'/Float,#1) or {#MACRO}', 'error_msg' => 'Cannot add trigger', 'errors' => [ 'Invalid parameter "/1/expression": incorrect expression starting from "{#MACRO}".' ] ] ], // #26. [ [ 'expected' => TEST_BAD, 'description' => 'MyTrigger', 'expression' => 'last(/'.self::HOST.'/Float,#1) or {#MACRO}', 'constructor' => [ 'text' => ['A or B', 'A', 'B'], 'elements' => ['expr_0_37', 'expr_42_49'] ] ] ], // #27. [ [ 'expected' => TEST_BAD, 'description' => 'MyTrigger', 'expression' => 'last(/Zabbix host/Float,#1)<0 or 8 and 9', 'constructor' => [ 'text' => ['A or (B and C)', 'Or', 'And', 'A', 'B', 'C'], 'elements' => ['expr_0_28', 'expr_33_33', 'expr_39_39'], 'elementError' => true, 'element_count' => 2, 'errors' => [ 'last(/Zabbix host/Float,#1): Unknown host, no such host present in system' ] ] ] ], // #28. [ [ 'expected' => TEST_BAD, 'description' => 'MyTrigger', 'expression' => 'last(/'.self::HOST.'/someItem,#1)<0 or 8 and 9 + last(/'.self::HOST.'/Float,#1)', 'constructor' => [ 'text' => ['A or (B and C)', 'A', 'B', 'C'], 'elements' => ['expr_0_42', 'expr_47_47', 'expr_53_94'], 'elementError' => true, 'element_count' => 2, 'errors' => [ 'last(/'.self::HOST.'/someItem,#1): Unknown host item, no such item in selected host' ] ] ] ], // #29. [ [ 'expected' => TEST_BAD, 'description' => 'MyTrigger', 'expression' => 'lasta(/'.self::HOST.'/Float,#1)<0 or 8 and 9 + last(/'.self::HOST.'/Float2,#1)', 'constructor' => [ 'text' => ['A or (B and C)', 'A', 'B', 'C'], 'elements' => ['expr_0_40', 'expr_45_45', 'expr_51_93'], 'elementError' => true, 'element_count' => 4, 'errors' => [ 'lasta(/'.self::HOST.'/Float,#1): Incorrect function is used', 'last(/'.self::HOST.'/Float2,#1): Unknown host item, no such item in selected host' ] ] ] ], // #30. [ [ 'expected' => TEST_BAD, 'description' => 'MyTrigger', 'expression' => 'last(/'.self::HOST.'@/Float,#1)<0', 'constructor' => [ 'errors' => [ 'header' => 'Expression syntax error.', 'details' => 'Cannot build expression tree: incorrect expression starting from'. ' "last(/'.self::HOST.'@/Float,#1)<0".' ] ] ] ], // #31. [ [ 'expected' => TEST_BAD, 'description' => 'MyTrigger', 'expression' => 'last(/'.self::HOST.'/system .uptime,#1)<0', 'constructor' => [ 'errors' => [ 'header' => 'Expression syntax error.', 'details' => 'Cannot build expression tree: incorrect expression starting from '. '"last(/'.self::HOST.'/system .uptime,#1)<0".' ] ] ] ], // #32. [ [ 'expected' => TEST_BAD, 'description' => 'MyTrigger', 'expression' => 'lastA(/'.self::HOST.'/Float,#1)<0', 'constructor' => [ 'errors' => [ 'header' => 'Expression syntax error.', 'details' => 'Cannot build expression tree: incorrect expression starting from '. '"lastA(/'.self::HOST.'/Float,#1)<0".' ] ] ] ], // #33. [ [ 'expected' => TEST_GOOD, 'description' => 'MyTrigger_rate_good', 'expression' => 'rate(/'.self::HOST.'/Unsigned,2m:now-1h)>0.5' ] ], // #34. [ [ 'expected' => TEST_BAD, 'description' => 'MyTrigger_rate_bad_second_par', 'expression' => 'rate(/'.self::HOST.'/Float,test)>0.5', 'error_msg' => 'Cannot add trigger', 'errors' => [ "Invalid parameter \"/1/expression\": incorrect expression starting from ". "\"rate(/".self::HOST."/Float,test)>0.5\"." ] ] ], // #35. [ [ 'expected' => TEST_BAD, 'description' => 'MyTrigger_rate_no_slash', 'expression' => 'rate('.self::HOST.'/Float,1h)>0.5', 'error_msg' => 'Cannot add trigger', 'errors' => [ "Invalid parameter \"/1/expression\": incorrect expression starting from ". "\"rate(".self::HOST."/Float,1h)>0.5\"." ] ] ], // #36. [ [ 'expected' => TEST_BAD, 'description' => 'MyTrigger_rate_bad_key', 'expression' => 'rate(/'.self::HOST.'/test,1h)>0.5', 'error_msg' => 'Cannot add trigger', 'errors' => [ 'Incorrect item key "test" provided for trigger expression on '. CXPathHelper::escapeQuotes(self::HOST) ] ] ], // #37. [ [ 'expected' => TEST_GOOD, 'description' => 'jsonpath Trigger all fields', 'expression' => 'jsonpath(last(/'.self::HOST.'/Text,#10:now),"$.[0].last_name","LastName")="Penddreth"', 'formCheck' => true ] ], // #38. [ [ 'expected' => TEST_GOOD, 'description' => 'jsonpath Trigger min', 'expression' => 'jsonpath(last(/'.self::HOST.'/Text),"$.last_name")<>"Test"' ] ], // #39. [ [ 'expected' => TEST_BAD, 'description' => 'Trigger wrong json function', 'expression' => 'jsonpath(max(/'.self::HOST.'/Character,#1:now-5m),"$.[0].last_name","last_name")="Test"', 'error_msg' => 'Cannot add trigger', 'errors' => [ 'Incorrect item value type "Character" provided for trigger function "max".' ] ] ], // #40. [ [ 'expected' => TEST_BAD, 'description' => 'Missing json parameters', 'expression' => 'jsonpath(last(/'.self::HOST.'/Text,#1:now-5m))="Test"', 'error_msg' => 'Cannot add trigger', 'errors' => [ 'Invalid parameter "/1/expression": invalid number of parameters in function "jsonpath".' ] ] ], // #41. [ [ 'expected' => TEST_BAD, 'description' => 'Wrong json parameters', 'expression' => 'jsonpath(last(/'.self::HOST.'/Text,20),"$.[0].last_name")="Test"', 'error_msg' => 'Cannot add trigger', 'errors' => [ 'Invalid parameter "/1/expression": invalid second parameter in function "last".' ] ] ], // #42. [ [ 'expected' => TEST_BAD, 'description' => 'Incorrect json expression', 'expression' => 'jsonpath(last(/'.self::HOST.'/Character,#5-now),"$.[0].last_name","last")<"Test"', 'error_msg' => 'Cannot add trigger', 'errors' => [ 'Invalid parameter "/1/expression": incorrect expression starting from "jsonpath(last(/'. self::HOST.'/Character,#5-now),"$.[0].last_name","last")<"Test"".' ] ] ], // #43. [ [ 'expected' => TEST_GOOD, 'description' => 'xml xpath Trigger all fields', 'expression' => 'xmlxpath(last(/'.self::HOST.'/Text,#4:now-1m),"/zabbix_export/version/text()",5.0)=7.0', 'formCheck' => true ] ], // #44. [ [ 'expected' => TEST_GOOD, 'description' => 'xml xpath Trigger min fields', 'expression' => 'xmlxpath(last(/'.self::HOST.'/Character),"/zabbix_export/version")=1' ] ], // #45. [ [ 'expected' => TEST_BAD, 'description' => 'Trigger wrong xml function', 'expression' => 'xmlxpath(min(/'.self::HOST.'/Text,#4:now-1m),"/zabbix_export/version/text()",5.0)=7.0', 'error_msg' => 'Cannot add trigger', 'errors' => [ 'Incorrect item value type "Text" provided for trigger function "min".' ] ] ], // #46. [ [ 'expected' => TEST_BAD, 'description' => 'Missing xml parameters', 'expression' => 'xmlxpath(last(/'.self::HOST.'/Text,#1:now-5m))="Test"', 'error_msg' => 'Cannot add trigger', 'errors' => [ 'Invalid parameter "/1/expression": invalid number of parameters in function "xmlxpath".' ] ] ], // #47. [ [ 'expected' => TEST_BAD, 'description' => 'Wrong xml parameters', 'expression' => 'xmlxpath(last(/'.self::HOST.'/Text,4),5.0)=7.0', 'error_msg' => 'Cannot add trigger', 'errors' => [ 'Invalid parameter "/1/expression": invalid second parameter in function "last".' ] ] ], // #48. [ [ 'expected' => TEST_GOOD, 'description' => 'json and xmlpath expression', 'expression' => 'jsonpath(last(/testPageHistory_CheckLayout/character[item_testpagehistory_checklayout]),'. '"$.[0].last_name")="Test" or xmlxpath(last(/'.self::HOST.'/Text),"/zabbix_export/version/text()")="test"' ] ], // #49. [ [ 'expected' => TEST_GOOD, 'description' => 'Double json expression', 'expression' => 'jsonpath(last(/testPageHistory_CheckLayout/character[item_testpagehistory_checklayout]),'. '"$.[0].last_name")="Test" and jsonpath(last(/'.self::HOST.'/Text),"$.test.last")=4' ] ], // #50. [ [ 'expected' => TEST_BAD, 'description' => 'Incorrect json expression', 'expression' => 'xmlxpath(last(/'.self::HOST.'/Text,#3-now),"/zabbix_export/version/text()",5.0)=7.0', 'error_msg' => 'Cannot add trigger', 'errors' => [ 'Invalid parameter "/1/expression": incorrect expression starting from "xmlxpath(last(/'. self::HOST.'/Text,#3-now),"/zabbix_export/version/text()",5.0)=7.0".' ] ] ] ]; } /** * @dataProvider create */ public function testFormTrigger_SimpleCreate($data) { $this->zbxTestLogin(self::HOST_LIST_PAGE); $form = $this->query('name:zbx_filter')->asForm()->waitUntilReady()->one(); $this->filterEntriesAndOpenTriggers(self::HOST, $form); $this->zbxTestCheckTitle('Configuration of triggers'); $this->zbxTestCheckHeader('Triggers'); $this->zbxTestContentControlButtonClickTextWait('Create trigger'); $dialog = COverlayDialogElement::find()->waitUntilReady()->one(); $this->zbxTestCheckTitle('Configuration of triggers'); $this->assertEquals('New trigger', $dialog->getTitle()); if (isset($data['description'])) { $this->zbxTestInputTypeWait('name', $data['description']); } $description = $this->zbxTestGetValue("//input[@id='name']"); if (isset($data['expression'])) { $this->zbxTestInputType('expression', $data['expression']); } $expression = $this->zbxTestGetValue("//textarea[@id='expression']"); if (isset($data['type'])) { $this->zbxTestClickXpathWait("//label[@for='type_1']"); $type = 'checked'; } else { $type = 'unchecked'; } if (isset($data['comments'])) { $this->zbxTestInputType('description', $data['comments']); } $comments = $this->zbxTestGetValue("//textarea[@id='description']"); if (isset($data['url_name'])) { $this->zbxTestInputType('url_name', $data['url_name']); } $url_name = $this->zbxTestGetValue("//input[@id='url_name']"); if (isset($data['url'])) { $this->zbxTestInputType('url', $data['url']); } $url = $this->zbxTestGetValue("//input[@id='url']"); if (isset($data['severity'])) { switch ($data['severity']) { case 'Not classified': $this->zbxTestClickXpathWait("//*[@id='priority_0']/../label"); break; case 'Information': $this->zbxTestClickXpathWait("//*[@id='priority_1']/../label"); break; case 'Warning': $this->zbxTestClickXpathWait("//*[@id='priority_2']/../label"); break; case 'Average': $this->zbxTestClickXpathWait("//*[@id='priority_3']/../label"); break; case 'High': $this->zbxTestClickXpathWait("//*[@id='priority_4']/../label"); break; case 'Disaster': $this->zbxTestClickXpathWait("//*[@id='priority_5']/../label"); break; } $severity = $data['severity']; } else { $severity = 'Not classified'; } if (isset($data['status'])) { $this->zbxTestCheckboxSelect('status', false); $status = 'unchecked'; } else { $status = 'checked'; } if (isset($data['constructor'])) { $this->zbxTestClickButtonText('Expression constructor'); $constructor = $data['constructor']; if (isset($constructor['errors']) && !array_key_exists('elementError', $constructor)) { $this->assertMessage(TEST_BAD, $constructor['errors']['header'], $constructor['errors']['details']); COverlayDialogElement::find()->one()->close(); } else { $this->zbxTestAssertVisibleXpath("//div[@id='expression-constructor-buttons']//button[@id='and_expression']"); $this->zbxTestAssertVisibleXpath("//div[@id='expression-constructor-buttons']//button[@id='or_expression']"); $this->zbxTestAssertVisibleXpath("//div[@id='expression-constructor-buttons']//button[@id='replace_expression']"); if (isset($constructor['text'])) { foreach ($constructor['text'] as $txt) { $this->query('xpath://div[@id="expression-table"]/div[1]')->waitUntilVisible()->one(); $this->zbxTestTextPresent($txt); } } if (isset($constructor['elements'])) { foreach ($constructor['elements'] as $elem) { $this->zbxTestAssertElementPresentId($elem); } } if (isset($constructor['elementError'])) { $count = CTestArrayHelper::get($constructor, 'element_count', 1); $this->assertEquals($count, $this->query('xpath://button['.CXPathHelper::fromClass('zi-i-negative').']')->all()->count() ); $text = $this->query("xpath://tr[1]//button[@data-hintbox]")->one() ->getAttribute('data-hintbox-contents'); foreach ($constructor['errors'] as $error) { $this->assertStringContainsString($error, $text); } } else { $this->zbxTestAssertElementNotPresentXpath('//button['.CXPathHelper::fromClass('zi-i-negative').']'); } COverlayDialogElement::find()->one()->close(); } } if (!isset($data['constructor'])) { $dialog->getFooter()->query('button:Add')->one()->click(); $this->page->waitUntilReady(); switch ($data['expected']) { case TEST_GOOD: $this->zbxTestWaitUntilMessageTextPresent('msg-good', 'Trigger added'); $this->zbxTestCheckTitle('Configuration of triggers'); $this->zbxTestAssertElementText("//tbody//a[text()='$description']", $description); $this->zbxTestAssertElementText("//a[text()='$description']/ancestor::tr/td[6]", $expression); break; case TEST_BAD: $this->assertMessage(TEST_BAD, $data['error_msg'], $data['errors']); $this->zbxTestTextPresent('Name'); $this->zbxTestTextPresent('Expression'); $this->zbxTestTextPresent('Description'); COverlayDialogElement::find()->one()->close(); break; } if (isset($data['formCheck'])) { $this->zbxTestClickLinkTextWait($description); $this->zbxTestAssertElementValue('name', $description); $this->zbxTestAssertElementValue('expression', $expression); if ($type == 'checked') { $this->assertTrue($this->zbxTestCheckboxSelected('type_1')); } else { $this->assertTrue($this->zbxTestCheckboxSelected('type_0')); } $this->zbxTestAssertElementValue('description', $comments); $this->zbxTestAssertElementValue('url_name', $url_name); $this->zbxTestAssertElementValue('url', $url); switch ($severity) { case 'Not classified': $this->assertTrue($this->zbxTestCheckboxSelected('priority_0')); break; case 'Information': $this->assertTrue($this->zbxTestCheckboxSelected('priority_1')); break; case 'Warning': $this->assertTrue($this->zbxTestCheckboxSelected('priority_2')); break; case 'Average': $this->assertTrue($this->zbxTestCheckboxSelected('priority_3')); break; case 'High': $this->assertTrue($this->zbxTestCheckboxSelected('priority_4')); break; case 'Disaster': $this->assertTrue($this->zbxTestCheckboxSelected('priority_5')); break; } if ($status == 'checked') { $this->assertTrue($this->zbxTestCheckboxSelected('status')); } else { $this->assertFalse($this->zbxTestCheckboxSelected('status')); } COverlayDialogElement::find()->one()->close(); } } } public function getLongExpressionData() { return [ // Create trigger. [ [ 'form_data' => [ 'Name' => 'Created trigger', 'Expression' => 'last(/'.STRING_128.'/'.STRING_2048.')=0 and last(/'.STRING_128.'/'.STRING_2048.')=0' ], 'expected_db_expression' => '/^\{\d+\}=0 and \{\d+\}=0$/' ] ], // Simple update. [ [ 'update' => true, 'link_name' => 'Trigger with long expression for simple update', 'expected_db_expression' => '/^\{\d+\}=0$/' ] ], // Update. [ [ 'update' => true, 'link_name' => 'Trigger with long expression for update', 'form_data' => [ 'Name' => 'Updated trigger', 'Expression' => 'last(/'.STRING_128.'/'.STRING_2048.')>0 and last(/'.STRING_128.'/'.STRING_2048.')>0' ], 'expected_db_expression' => '/^\{\d+\}>0 and \{\d+\}>0$/' ] ] ]; } /** * Test a special case where the host's name and item's key are very long. * The trigger expression is saved using expression IDs instead of saving the whole text of the expression, * so no issues due to length of host name or item key should be present regardless of their length. * * @dataProvider getLongExpressionData */ public function testFormTrigger_LongExpression($data) { $this->page->login()->open('zabbix.php?action=trigger.list&filter_set=1&context=host&filter_hostids[0]='. self::$long_key_hostid ); $this->page->waitUntilReady(); // Open the correct form. $open_form_button = (CTestArrayHelper::get($data, 'update')) ? 'link:'.$data['link_name'] : 'button:Create trigger'; $this->page->query($open_form_button)->one()->click(); // Fill form data and save. $dialog = COverlayDialogElement::find()->one(); $dialog->asForm()->fill(CTestArrayHelper::get($data, 'form_data', [])); $button = CTestArrayHelper::get($data, 'update') ? 'Update' : 'Add'; $dialog->getFooter()->query('button', $button)->one()->click(); COverlayDialogElement::ensureNotPresent(); // Get the saved trigger's ID from UI. $link = (array_key_exists('form_data', $data)) ? $data['form_data']['Name'] : $data['link_name']; $triggerid = $this->page->query('link', $link)->one()->getAttribute('data-triggerid'); // Get the newly saved trigger's expression, as it is saved in the DB. $db_expression = CDBHelper::getValue('SELECT expression FROM triggers WHERE triggerid = '.$triggerid); // Assert by regex that the expression is saved in DB similar to this: "{100253}=0 and {100253}=0". $this->assertEquals(1, preg_match($data['expected_db_expression'], $db_expression)); } /** * Function for filtering necessary hosts and opening their Web scenarios. * * @param string $name name of a host or template where triggers are opened */ private function filterEntriesAndOpenTriggers($name, $form) { $table = $this->query('xpath://table[@class="list-table"]')->asTable()->one(); $this->query('button:Reset')->one()->click(); $form->fill(['Name' => $name]); $this->query('button:Apply')->one()->waitUntilClickable()->click(); $table->waitUntilReloaded(); $table->findRow('Name', $name)->getColumn('Triggers')->query('link:Triggers')->one()->click(); } }