<?php /* ** Zabbix ** 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 General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** 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 General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ require_once dirname(__FILE__).'/../../include/CWebTest.php'; require_once dirname(__FILE__).'/../../include/helpers/CDataHelper.php'; /** * @backup hosts * * @onBefore prepareProblemsData */ class testFormUpdateProblem extends CWebTest { /** * Id of the host with problems. * * @var integer */ protected static $hostid; /** * Time when acknowledge was created. * * @var string */ protected static $acktime; /** * Attach MessageBehavior to the test. * * @return array */ public function getBehaviors() { return [CMessageBehavior::class]; } /** * Get all events related tables hash values. */ public static function getHash() { return CDBHelper::getHash('SELECT * FROM events'). CDBHelper::getHash('SELECT * FROM problem'). CDBHelper::getHash('SELECT * FROM triggers'). CDBHelper::getHash('SELECT * FROM acknowledges'). CDBHelper::getHash('SELECT * FROM event_suppress'); } public function prepareProblemsData() { // Create hostgroup for hosts with items triggers. $hostgroups = CDataHelper::call('hostgroup.create', [['name' => 'Group for Problems Update']]); $this->assertArrayHasKey('groupids', $hostgroups); $groupid = $hostgroups['groupids'][0]; // Create host for items and triggers. $hosts = CDataHelper::call('host.create', [ 'host' => 'Host for Problems Update', 'groups' => [['groupid' => $groupid]] ]); $this->assertArrayHasKey('hostids', $hosts); self::$hostid = $hosts['hostids'][0]; // Create items on previously created host. $item_names = ['float', 'char', 'log', 'unsigned', 'text']; $items_data = []; foreach ($item_names as $i => $item) { $items_data[] = [ 'hostid' => self::$hostid, 'name' => $item, 'key_' => $item, 'type' => 2, 'value_type' => $i ]; } $items = CDataHelper::call('item.create', $items_data); $this->assertArrayHasKey('itemids', $items); // Create triggers based on items. $triggers = CDataHelper::call('trigger.create', [ [ 'description' => 'Trigger for float', 'expression' => 'last(/Host for Problems Update/float)=0', 'priority' => TRIGGER_SEVERITY_NOT_CLASSIFIED ], [ 'description' => 'Trigger for char', 'expression' => 'last(/Host for Problems Update/char)=0', 'priority' => TRIGGER_SEVERITY_INFORMATION, 'manual_close' => 1 ], [ 'description' => 'Trigger for log', 'expression' => 'last(/Host for Problems Update/log)=0', 'priority' => TRIGGER_SEVERITY_WARNING ], [ 'description' => 'Trigger for unsigned', 'expression' => 'last(/Host for Problems Update/unsigned)=0', 'priority' => TRIGGER_SEVERITY_AVERAGE ], [ 'description' => 'Trigger for text', 'expression' => 'last(/Host for Problems Update/text)=0', 'priority' => TRIGGER_SEVERITY_HIGH ] ]); $this->assertArrayHasKey('triggerids', $triggers); // Create problems and events. $time = time(); foreach (CDataHelper::getIds('description') as $name => $id) { CDBHelper::setTriggerProblem($name, TRIGGER_VALUE_TRUE, $time); } $eventids = []; foreach (['Trigger for text', 'Trigger for unsigned'] as $event_name) { $eventids[$event_name] = CDBHelper::getValue('SELECT eventid FROM events WHERE name='.zbx_dbstr($event_name)); } // Suppress the problem: 'Trigger for text'. DBexecute('INSERT INTO event_suppress (event_suppressid, eventid, maintenanceid, suppress_until) VALUES (10050, '. $eventids['Trigger for text'].', NULL, 0)' ); // Acknowledge the problem: 'Trigger for unsigned' and get acknowledge time. CDataHelper::call('event.acknowledge', [ 'eventids' => $eventids['Trigger for unsigned'], 'action' => 6, 'message' => 'Acknowledged event' ]); $event = CDataHelper::call('event.get', [ 'eventids' => $eventids['Trigger for unsigned'], 'select_acknowledges' => ['clock'] ]); self::$acktime = CTestArrayHelper::get($event, '0.acknowledges.0.clock'); } public function getLayoutData() { return [ [ [ 'problems' => ['Trigger for float'], 'hintboxes' => [ 'Suppress' => 'Manual problem suppression. Date-time input accepts relative and absolute time format.', 'Unsuppress' => 'Deactivates manual suppression.', 'Acknowledge' => 'Confirms the problem is noticed (acknowledging user will be recorded). '. 'Status change triggers action update operation.' ], 'history' => [], 'Acknowledge' => true, 'check_suppress' => true ] ], [ [ 'problems' => ['Trigger for char'], 'close_enabled' => true, 'history' => [], 'Acknowledge' => true ] ], [ [ 'problems' => ['Trigger for text'], 'unsuppress_enabled' => true, 'history' => [], 'Acknowledge' => true ] ], [ [ 'problems' => ['Trigger for unsigned'], // If problem is Acknowledged - label is changed to Unacknowledge. 'labels' => ['Problem', 'Message', 'History', 'Scope', 'Change severity', 'Unacknowledge', 'Close problem', ''], 'message' => 'Acknowledged event', 'Unacknowledge' => true, 'history' => [' Admin (Zabbix Administrator) Acknowledged event'], 'hintboxes' => [ 'Suppress' => 'Manual problem suppression. Date-time input accepts relative and absolute time format.', 'Unsuppress' => 'Deactivates manual suppression.', 'Unacknowledge' => 'Undo problem acknowledgement.' ] ] ], [ [ 'problems' => ['Trigger for log'], 'history' => [], 'Acknowledge' => true ] ], // Two problems. [ [ 'problems' => ['Trigger for float', 'Trigger for char'], // If more than one problem selected - History label is absent. 'labels' => ['Problem', 'Message', 'Scope', 'Change severity', 'Acknowledge', 'Close problem', ''], 'close_enabled' => true, 'Acknowledge' => true, 'hintboxes' => [ 'Suppress' => 'Manual problem suppression. Date-time input accepts relative and absolute time format.', 'Unsuppress' => 'Deactivates manual suppression.', 'Acknowledge' => 'Confirms the problem is noticed (acknowledging user will be recorded). '. 'Status change triggers action update operation.' ] ] ], // Five problems. [ [ 'problems' => ['Trigger for float', 'Trigger for char', 'Trigger for log', 'Trigger for unsigned', 'Trigger for text'], // If more than one problems selected - History label is absent. 'labels' => ['Problem', 'Message', 'Scope', 'Change severity', 'Acknowledge', 'Unacknowledge', 'Close problem', ''], 'hintboxes' => [ 'Suppress' => 'Manual problem suppression. Date-time input accepts relative and absolute time format.', 'Unsuppress' => 'Deactivates manual suppression.', 'Acknowledge' => 'Confirms the problem is noticed (acknowledging user will be recorded). '. 'Status change triggers action update operation.', 'Unacknowledge' => 'Undo problem acknowledgement.' ], 'close_enabled' => true, 'unsuppress_enabled' => true, 'Acknowledge' => true, 'Unacknowledge' => true ] ] ]; } /** * @dataProvider getLayoutData */ public function testFormUpdateProblem_Layout($data) { // Open filtered Problems list. $this->page->login()->open('zabbix.php?&action=problem.view&show_suppressed=1&hostids%5B%5D='.self::$hostid)->waitUntilReady(); $table = $this->query('class:list-table')->asTable()->one(); $table->findRows('Problem', $data['problems'])->select(); $this->query('button:Mass update')->waitUntilClickable()->one()->click(); $dialog = COverlayDialogElement::find()->one()->waitUntilReady(); $this->assertEquals('Update problem', $dialog->getTitle()); $form = $dialog->query('id:acknowledge_form')->asForm()->one(); // Check form labels. $count = count($data['problems']); $default_labels = ['Problem', 'Message', 'History', 'Scope', 'Change severity', 'Acknowledge', 'Close problem', '']; $this->assertEquals(CTestArrayHelper::get($data, 'labels', $default_labels), $form->getLabels()->asText()); // Check "Problem" field value. $problem = $count > 1 ? $count.' problems selected.' : $data['problems'][0]; $this->assertTrue($form->query('xpath:.//div[@class="wordbreak" and text()='. CXPathHelper::escapeQuotes($problem).']')->exists() ); // Check first label in Scope field. $scope_field = $form->getField('Scope'); $scope_label_query = $count > 1 ? 'xpath:.//label[text()="Only selected problems"]/sup[text()='.CXPathHelper::escapeQuotes($count.' events').']' : 'xpath:.//label[text()="Only selected problem"]'; $this->assertTrue($scope_field->query($scope_label_query)->exists()); // Check second label in Scope field. $this->assertTrue($scope_field->query("xpath:.//label[text()=". "\"Selected and all other problems of related triggers\"]/sup[text()=". CXPathHelper::escapeQuotes($count > 1 ? $count.' events' : '1 event')."]")->exists() ); // Check History field. if (array_key_exists('history', $data)) { $history = ($data['history'] === []) ? $data['history'] : [date('Y-m-d H:i:s', self::$acktime).$data['history'][0]]; $history_table = $form->getField('History')->asTable(); $this->assertEquals(['Time', 'User', 'User action', 'Message'], $history_table->getHeadersText()); $this->assertEquals($history, $history_table->getRows()->asText()); if ($data['problems'] === ['Trigger for unsigned']) { foreach (['Acknowledged', 'Message'] as $icon) { $this->assertTrue($history_table->query("xpath:.//span[@class=".CXPathHelper::fromClass('icon-action'). " and @title=".CXPathHelper::escapeQuotes($icon)."]")->exists() ); } } } // Check fields' default values and attributes. $fields = [ 'id:message' => ['value' => '', 'maxlength' => 2048, 'enabled' => true], 'id:scope_0' => ['value' => true, 'enabled' => true], // Only selected problem. 'id:scope_1' => ['value' => false, 'enabled' => true], // Selected and all other problems of related triggers. 'id:change_severity' => ['value' => false, 'enabled' => true], 'id:severity' => ['value' => 'Not classified', 'enabled' => false], 'Close problem' => ['value' => false, 'enabled' => CTestArrayHelper::get($data, 'close_enabled', false)] ]; foreach ($fields as $field => $attributes) { $this->assertEquals($attributes['value'], $form->getField($field)->getValue()); $this->assertTrue($form->getField($field)->isEnabled($attributes['enabled'])); if (array_key_exists('maxlength', $attributes)) { $this->assertEquals($attributes['maxlength'], $form->getField($field)->getAttribute('maxlength')); } } // Check default values for 'Acknowledge' and 'Unacknowledge' fields. foreach (['Acknowledge', 'Unacknowledge'] as $label) { if (array_key_exists($label, $data)) { $field = $form->getField($label); $this->assertEquals(false, $field->getValue()); $this->assertTrue($field->isEnabled()); } } // Check other buttons in overlay. $button_queries = [ 'xpath:.//button[@title="Close"]' => true, 'button:Update' => true, 'button:Cancel' => true ]; foreach ($button_queries as $query => $clickable) { $this->assertEquals($clickable, $dialog->query($query)->one()->isClickable()); } // Check asterisk text. $this->assertTrue($form->query('xpath:.//label[@class="form-label-asterisk" and '. 'text()="At least one update operation or message must exist."]')->exists() ); $dialog->close(); } public function getFormData() { return [ [ [ 'problems' => ['Trigger for log', 'Trigger for char', 'Trigger for float'], 'fields' => [ 'id:scope_1' => true, 'id:change_severity' => true, 'id:severity' => 'Information' ], 'db_check' => [ [ 'name' => 'Trigger for log', 'db_fields' => ['message' => '', 'action' => 8, 'new_severity' => 1] ], [ 'name' => 'Trigger for char', 'db_fields' => false ], [ 'name' => 'Trigger for float', 'db_fields' => ['message' => '', 'action' => 8, 'new_severity' => 1] ] ] ] ], [ [ 'problems' => ['Trigger for float'], 'fields' => [ 'id:message' => 'test message text', 'id:change_severity' => true, 'id:severity' => 'Warning', 'Acknowledge' => true ], 'db_check' => [ [ 'name' => 'Trigger for float', 'db_fields' => ['message' => 'test message text', 'action' => 14, 'new_severity' => 2] ] ] ] ], [ [ 'problems' => ['Trigger for text', 'Trigger for log'], 'fields' => [ 'id:change_severity' => true, 'id:severity' => 'Not classified' ], 'db_check' => [ [ 'name' => 'Trigger for text', 'db_fields' => ['message' => '', 'action' => 8, 'new_severity' => 0] ], [ 'name' => 'Trigger for text', 'db_fields' => ['message' => '', 'action' => 8, 'new_severity' => 0] ] ] ] ], [ [ 'problems' => ['Trigger for char'], 'fields' => [ 'id:change_severity' => true, 'id:severity' => 'Average' ], 'db_check' => [ [ 'name' => 'Trigger for char', 'db_fields' => ['message' => '', 'action' => 8, 'new_severity' => 3] ] ] ] ], [ [ 'problems' => ['Trigger for unsigned'], 'fields' => [ 'id:change_severity' => true, 'id:severity' => 'High' ], 'db_check' => [ [ 'name' => 'Trigger for unsigned', 'db_fields' => ['message' => '', 'action' => 8, 'new_severity' => 4] ] ] ] ], [ [ 'problems' => ['Trigger for log'], 'fields' => [ 'id:change_severity' => true, 'id:severity' => 'Disaster' ], 'db_check' => [ [ 'name' => 'Trigger for log', 'db_fields' => ['message' => '', 'action' => 8, 'new_severity' => 5] ] ] ] ], [ [ 'problems' => ['Trigger for log', 'Trigger for char', 'Trigger for float', 'Trigger for text', 'Trigger for unsigned'], 'fields' => [ 'id:message' => 'Update all 5 problems', 'id:change_severity' => true, 'id:severity' => 'High' ], 'db_check' => [ [ 'name' => 'Trigger for log', 'db_fields' => ['message' => 'Update all 5 problems', 'action' => 12, 'new_severity' => 4] ], [ 'name' => 'Trigger for char', 'db_fields' => ['message' => 'Update all 5 problems', 'action' => 12, 'new_severity' => 4] ], [ 'name' => 'Trigger for float', 'db_fields' => ['message' => 'Update all 5 problems', 'action' => 12, 'new_severity' => 4] ], [ 'name' => 'Trigger for text', 'db_fields' => ['message' => 'Update all 5 problems', 'action' => 12, 'new_severity' => 4] ], [ 'name' => 'Trigger for unsigned', 'db_fields' => ['message' => 'Update all 5 problems', 'action' => 4, 'new_severity' => 0] ] ] ] ], [ [ 'problems' => ['Trigger for unsigned'], 'fields' => [ 'Unacknowledge' => true ], 'db_check' => [ [ 'name' => 'Trigger for unsigned', 'db_fields' => ['message' => '', 'action' => 16, 'new_severity' => 0] ] ] ] ], [ [ 'problems' => ['Trigger for char'], 'fields' => [ 'Close problem' => true ], 'db_check' => [ [ 'name' => 'Trigger for char', 'db_fields' => ['message' => '', 'action' => 1, 'new_severity' => 0] ] ] ] ] ]; } /** * @dataProvider getFormData */ public function testFormUpdateProblem_Form($data) { // Open filtered Problems list. $this->page->login()->open('zabbix.php?&action=problem.view&filter_set=1&show_suppressed=1&hostids%5B%5D='.self::$hostid)->waitUntilReady(); $table = $this->query('class:list-table')->asTable()->one(); $count = count($data['problems']); $table->findRows('Problem', $data['problems']); if ($count > 1) { $table->findRows('Problem', $data['problems'])->select(); $this->query('button:Mass update')->waitUntilClickable()->one()->click(); } else { $table->findRow('Problem', $data['problems'][0])->getColumn('Ack')->query('tag:a')->waitUntilClickable()->one()->click(); } $dialog = COverlayDialogElement::find()->one()->waitUntilReady(); $form = $dialog->query('id:acknowledge_form')->asForm()->one(); $form->fill($data['fields']); $form->submit(); $dialog->ensureNotPresent(); $this->page->waitUntilReady(); $this->page->assertHeader('Problems'); $message = ($count > 1) ? 'Events updated' : 'Event updated'; $this->assertMessage(TEST_GOOD, $message); // Check db change. foreach ($data['db_check'] as $event) { $sql = CDBHelper::getRow('SELECT message, action, new_severity'. ' FROM acknowledges'. ' WHERE eventid=('. 'SELECT eventid'. ' FROM events'. ' WHERE name='.zbx_dbstr($event['name']). ') ORDER BY acknowledgeid DESC' ); $this->assertEquals($event['db_fields'], $sql); } } public function getCancelData() { return [ [ [ 'case' => 'Cancel' ] ], [ [ 'case' => 'Close' ] ] ]; } /** * @dataProvider getCancelData */ public function testFormUpdateProblem_Cancel($data) { $old_hash = $this->getHash(); // Open filtered Problems list. $this->page->login()->open('zabbix.php?&action=problem.view&show_suppressed=1&hostids%5B%5D='.self::$hostid)->waitUntilReady(); $this->query('class:list-table')->asTable()->one()->findRow('Problem', 'Trigger for char')->getColumn('Ack') ->query('tag:a')->waitUntilClickable()->one()->click(); $dialog = COverlayDialogElement::find()->one()->waitUntilReady(); $dialog->query('id:acknowledge_form')->asForm()->one()->fill([ 'id:scope_1' => true, 'id:change_severity' => true, 'id:severity' => 'Disaster', 'Acknowledge' => true ]); $dialog->query(($data['case'] === 'Close') ? 'xpath:.//button[@title="Close"]' : 'button:Cancel')->one() ->waitUntilClickable()->click(); $dialog->ensureNotPresent(); $this->page->assertHeader('Problems'); $this->assertEquals($old_hash, $this->getHash()); } }