<?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/CWebTest.php'; /** * @backup widget * * @onBefore prepareDiscoveryStatusWidgetData * * @dataSource Proxies */ class testDashboardDiscoveryStatusWidget extends CWebTest { /** * Attach MessageBehavior and TableBehavior to the test. * * @return array */ public function getBehaviors() { return [ CMessageBehavior::class, CTableBehavior::class ]; } /** * Id of the dashboard with widgets. * * @var integer */ protected static $dashboardid; protected static $druleids; protected static $update_widget = 'Update widget'; const DISCOVERY_RULE_1 = 'A discovery rule to be displayed first'; const DISCOVERY_RULE_2 = 'Discovery rule with 0 discovered hosts'; const DISCOVERY_RULE_3 = 'Discovery rule with active discovered hosts'; const DISCOVERY_RULE_4 = 'Discovery rule with both type of hosts'; const DISCOVERY_RULE_5 = 'XYZ - discovery rule to be displayed last (Inactive hosts)'; const DELETE_WIDGET = 'Discovery status widget to delete'; const DATA_WIDGET = 'Widget for data check'; const CANCEL_WIDGET = 'Widget for testing cancel button'; /** * SQL query to get widget and widget_field tables to compare hash values, but without widget_fieldid * because it can change. */ const SQL = 'SELECT wf.widgetid, wf.type, wf.name, wf.value_int, wf.value_str, wf.value_groupid, wf.value_hostid,'. ' wf.value_itemid, wf.value_graphid, wf.value_sysmapid, w.widgetid, w.dashboard_pageid, w.type, w.name, w.x, w.y,'. ' w.width, w.height'. ' FROM widget_field wf'. ' INNER JOIN widget w'. ' ON w.widgetid=wf.widgetid '. ' ORDER BY wf.widgetid, wf.name, wf.value_int, wf.value_str, wf.value_groupid, wf.value_itemid, wf.value_graphid'; /** * Create test data. * * @return array */ public static function prepareDiscoveryStatusWidgetData() { CDataHelper::call('drule.create', [ [ 'name' => self::DISCOVERY_RULE_1, 'iprange' => '192.168.1.1-255', 'dchecks' => [ [ 'type' => SVC_HTTP ] ] ], [ 'name' => self::DISCOVERY_RULE_2, 'iprange' => '192.168.1.1-255', 'dchecks' => [ [ 'type' => SVC_HTTP ] ] ], [ 'name' => self::DISCOVERY_RULE_3, 'iprange' => '192.168.1.1-255', 'dchecks' => [ [ 'type' => SVC_HTTP ] ] ], [ 'name' => self::DISCOVERY_RULE_4, 'iprange' => '192.168.1.1-255', 'dchecks' => [ [ 'type' => SVC_HTTP ] ] ], [ 'name' => self::DISCOVERY_RULE_5, 'iprange' => '192.168.1.1-255', 'dchecks' => [ [ 'type' => SVC_HTTP ] ] ] ]); self::$druleids = CDataHelper::getIds('name'); $id = CDBHelper::getValue('SELECT druleid FROM drules WHERE name='.zbx_dbstr('Discovery rule for proxy delete test')); self::$druleids += ['Discovery rule for proxy delete test' => $id]; CDataHelper::call('dashboard.create', [ [ 'name' => 'Dashboard for testing layout of discovery status widget', 'auto_start' => 0, 'pages' => [ [ 'name' => 'First page' ] ] ], [ 'name' => 'Dashboard for testing actions with discovery status widget', 'auto_start' => 0, 'pages' => [ [ 'name' => 'First page', 'widgets' => [ [ 'type' => 'discovery', 'name' => self::$update_widget, 'x' => 0, 'y' => 0, 'width' => 12, 'height' => 4 ], [ 'type' => 'discovery', 'name' => self::DELETE_WIDGET, 'x' => 12, 'y' => 0, 'width' => 8, 'height' => 4 ] ] ] ] ], [ 'name' => 'Dashboard for testing cancel button for discovery status widget', 'auto_start' => 0, 'pages' => [ [ 'name' => 'First page', 'widgets' => [ [ 'type' => 'discovery', 'name' => self::CANCEL_WIDGET, 'x' => 0, 'y' => 0, 'width' => 12, 'height' => 5 ] ] ] ] ], [ 'name' => 'Dashboard for testing widgets table data', 'auto_start' => 0, 'pages' => [ [ 'name' => 'First page', 'widgets' => [ [ 'type' => 'discovery', 'name' => self::DATA_WIDGET, 'x' => 0, 'y' => 0, 'width' => 12, 'height' => 5 ] ] ] ] ] ]); self::$dashboardid = CDataHelper::getIds('name'); // Insert data into the database (dhosts table), to imitate the host discovery. for ($i = 0; $i < 5; $i++) { DBexecute('INSERT INTO dhosts (dhostid, druleid, status) VALUES ('.get_dbid('dhosts', 'dhostid').', '. zbx_dbstr(self::$druleids[self::DISCOVERY_RULE_3]).', '.DHOST_STATUS_ACTIVE.')'); DBexecute('INSERT INTO dhosts (dhostid, druleid, status) VALUES ('.get_dbid('dhosts', 'dhostid').', '. zbx_dbstr(self::$druleids[self::DISCOVERY_RULE_5]).', '.DHOST_STATUS_DISABLED.')'); DBexecute('INSERT INTO dhosts (dhostid, druleid, status) VALUES ('.get_dbid('dhosts', 'dhostid').', '. zbx_dbstr(self::$druleids[self::DISCOVERY_RULE_4]).', '.DHOST_STATUS_ACTIVE.')'); DBexecute('INSERT INTO dhosts (dhostid, druleid, status) VALUES ('.get_dbid('dhosts', 'dhostid').', '. zbx_dbstr(self::$druleids[self::DISCOVERY_RULE_4]).', '.DHOST_STATUS_DISABLED.')'); } } /** * Check discovery status widget layout. */ public function testDashboardDiscoveryStatusWidget_Layout() { $this->page->login()->open('zabbix.php?action=dashboard.view&dashboardid='. self::$dashboardid['Dashboard for testing layout of discovery status widget']); $dialog = CDashboardElement::find()->one()->edit()->addWidget(); $form = $dialog->asForm(); $this->assertEquals('Add widget', $dialog->getTitle()); $form->fill(['Type' => CFormElement::RELOADABLE_FILL('Discovery status')]); $form->checkValue([ 'Name' => '', 'Refresh interval' => 'Default (1 minute)', 'Show header' => true ]); $this->assertTrue($form->getField('Name')->isAttributePresent(['maxlength' => '255', 'placeholder' => 'default'])); $this->assertEquals($form->getField('Refresh interval')->getOptions()->asText(), [ 'Default (1 minute)', 'No refresh', '10 seconds', '30 seconds', '1 minute', '2 minutes', '10 minutes', '15 minutes' ]); // Check that close button is present and clickable $this->assertTrue($dialog->query('class:btn-overlay-close')->one()->isClickable()); // Check if footer buttons are present and clickable. $this->assertEquals(['Add', 'Cancel'], $dialog->getFooter()->query('button')->all() ->filter(CElementFilter::CLICKABLE)->asText() ); $dialog->close(); } public static function getDiscoveryStatusWidgetData() { return [ // #0 With default values. [ [ 'expected' => TEST_GOOD ] ], // #1 Name with special symbols. [ [ 'expected' => TEST_GOOD, 'fields' => [ 'Name' => 'Test ⭐㖵㖶 🙃 㓈㓋', 'Show header' => true, 'Refresh interval' => 'No refresh' ] ] ], // #2 Name with leading spaces. [ [ 'expected' => TEST_GOOD, 'fields' => [ 'Name' => ' Trimmed name 1', 'Show header' => true, 'Refresh interval' => '10 seconds' ], 'trim' => true ] ], // #3 Name with trailing spaces. [ [ 'expected' => TEST_GOOD, 'fields' => [ 'Name' => 'Trimmed name 2 ', 'Show header' => true, 'Refresh interval' => '30 seconds' ], 'trim' => true ] ], // #4 Name with leading and trailing spaces. [ [ 'expected' => TEST_GOOD, 'fields' => [ 'Name' => ' Name with leading and trailing spaces ', 'Show header' => true, 'Refresh interval' => '1 minute' ], 'trim' => true ] ], // #5 No visible header. [ [ 'expected' => TEST_GOOD, 'fields' => [ 'Name' => 'No visible header', 'Show header' => false, 'Refresh interval' => '2 minutes' ] ] ], // #6 10 minutes refresh interval. [ [ 'expected' => TEST_GOOD, 'fields' => [ 'Name' => '10 minutes update interval', 'Show header' => true, 'Refresh interval' => '10 minutes' ] ] ], // #7 Maximum refresh interval. [ [ 'expected' => TEST_GOOD, 'fields' => [ 'Name' => 'Max update interval', 'Show header' => true, 'Refresh interval' => '15 minutes' ] ] ] ]; } /** * @dataProvider getDiscoveryStatusWidgetData */ public function testDashboardDiscoveryStatusWidget_Create($data) { $this->checkWidgetForm($data); } /** * @dataProvider getDiscoveryStatusWidgetData */ public function testDashboardDiscoveryStatusWidget_Update($data) { $this->checkWidgetForm($data, true); } public function testDashboardDiscoveryStatusWidget_SimpleUpdate() { $old_hash = CDBHelper::getHash(self::SQL); $this->page->login()->open('zabbix.php?action=dashboard.view&dashboardid='. self::$dashboardid['Dashboard for testing actions with discovery status widget'])->waitUntilReady(); $dashboard = CDashboardElement::find()->one(); $dashboard->getWidget(self::$update_widget)->edit()->submit(); $dashboard->save(); $this->page->waitUntilReady(); $this->assertMessage(TEST_GOOD, 'Dashboard updated'); $this->assertEquals($old_hash, CDBHelper::getHash(self::SQL)); } public function testDashboardDiscoveryStatusWidget_Delete() { $this->page->login()->open('zabbix.php?action=dashboard.view&dashboardid='. self::$dashboardid['Dashboard for testing actions with discovery status widget'])->waitUntilReady(); $dashboard = CDashboardElement::find()->one()->edit(); $widget = $dashboard->getWidget(self::DELETE_WIDGET); $dashboard->deleteWidget(self::DELETE_WIDGET); $widget->waitUntilNotPresent(); $dashboard->save(); $this->assertMessage(TEST_GOOD, 'Dashboard updated'); // Check that widget is not present on dashboard. $this->assertFalse($dashboard->getWidget(self::DELETE_WIDGET, false)->isValid()); $this->assertEquals(0, CDBHelper::getCount('SELECT * FROM widget_field wf'. ' LEFT JOIN widget w'. ' ON w.widgetid=wf.widgetid'. ' WHERE w.name='.zbx_dbstr(self::DELETE_WIDGET) )); } public static function getCancelData() { return [ // Cancel update widget. [ [ 'update' => true, 'save_widget' => true, 'save_dashboard' => false ] ], [ [ 'update' => true, 'save_widget' => false, 'save_dashboard' => true ] ], // Cancel create widget. [ [ 'save_widget' => true, 'save_dashboard' => false ] ], [ [ 'save_widget' => false, 'save_dashboard' => true ] ] ]; } /** * @dataProvider getCancelData */ public function testDashboardDiscoveryStatusWidget_Cancel($data) { $old_hash = CDBHelper::getHash(self::SQL); $new_name = 'Cancel test'; $this->page->login()->open('zabbix.php?action=dashboard.view&dashboardid='. self::$dashboardid['Dashboard for testing cancel button for discovery status widget'])->waitUntilReady(); $dashboard = CDashboardElement::find()->one()->edit(); $old_widget_count = $dashboard->getWidgets()->count(); // Start updating or creating a widget. if (CTestArrayHelper::get($data, 'update', false)) { $form = $dashboard->getWidget(self::CANCEL_WIDGET)->edit(); } else { $form = $dashboard->addWidget()->asForm(); $form->fill(['Type' => CFormElement::RELOADABLE_FILL('Discovery status')]); } $form->fill([ 'Name' => $new_name, 'Refresh interval' => '15 minutes' ]); // Save or cancel widget. if (CTestArrayHelper::get($data, 'save_widget', false)) { $form->submit(); // Check that changes took place on the unsaved dashboard. $this->assertTrue($dashboard->getWidget($new_name)->isVisible()); } else { COverlayDialogElement::find()->one()->close(true); if (CTestArrayHelper::get($data, 'update', false)) { foreach ([self::CANCEL_WIDGET => true, $new_name => false] as $name => $valid) { $dashboard->getWidget($name, false)->isValid($valid); } } $this->assertEquals($old_widget_count, $dashboard->getWidgets()->count()); } // Save or cancel dashboard update. if (CTestArrayHelper::get($data, 'save_dashboard', false)) { $dashboard->save(); } else { $dashboard->cancelEditing(); } $this->assertEquals($old_hash, CDBHelper::getHash(self::SQL)); } public static function getWidgetTableData() { return [ [ [ [ 'Discovery rule' => self::DISCOVERY_RULE_1, 'Up' => '', 'Down' => '' ], [ 'Discovery rule' => 'Discovery rule for proxy delete test', 'Up' => '', 'Down' => '' ], [ 'Discovery rule' => self::DISCOVERY_RULE_2, 'Up' => '', 'Down' => '' ], [ 'Discovery rule' => self::DISCOVERY_RULE_3, 'Up' => ['text' => '5', 'selector' => 'class:green'], 'Down' => '' ], [ 'Discovery rule' => self::DISCOVERY_RULE_4, 'Up' => ['text' => '5', 'selector' => 'class:green'], 'Down' => ['text' => '5', 'selector' => 'class:red'] ], [ 'Discovery rule' => self::DISCOVERY_RULE_5, 'Up' => '', 'Down' => ['text' => '5', 'selector' => 'class:red'] ] ] ] ]; } /** * @dataProvider getWidgetTableData */ public function testDashboardDiscoveryStatusWidget_checkWidgetTableData($data) { $this->page->login()->open('zabbix.php?action=dashboard.view&dashboardid='. self::$dashboardid['Dashboard for testing widgets table data']); $dashboard = CDashboardElement::find()->one(); $widget_data = $dashboard->getWidget(self::DATA_WIDGET)->getContent()->asTable(); // Check the table content. $this->assertEquals($widget_data->getHeadersText(), ['Discovery rule', 'Up', 'Down']); $this->assertTableData($data); // Check links for the discovery rules. foreach (self::$druleids as $name => $id) { $this->assertEquals('zabbix.php?action=discovery.view&filter_set=1&filter_druleids%5B0%5D='.$id, $widget_data->query('link', $name)->one()->getAttribute('href') ); } } public function testDashboardDiscoveryStatusWidget_checkEmptyWidget() { // Disable discovery rules to check the content of the empty widget. $drule_data = []; foreach (self::$druleids as $id) { $drule_data[] = [ 'druleid' => $id, 'status' => DRULE_STATUS_DISABLED ]; } CDataHelper::call('drule.update', $drule_data); $this->page->login()->open('zabbix.php?action=dashboard.view&dashboardid='. self::$dashboardid['Dashboard for testing widgets table data']); $dashboard = CDashboardElement::find()->one(); $widget_data = $dashboard->getWidget(self::DATA_WIDGET)->getContent()->asTable(); // Check the table content. $this->assertEquals($widget_data->getHeadersText(), ['Discovery rule', 'Up', 'Down']); $this->assertEquals('No data found', $widget_data->query('xpath:.//div[@class="no-data-message zi-search-large"]') ->one()->getText() ); } protected function checkWidgetForm($data, $update = false) { $expected = CTestArrayHelper::get($data, 'expected', TEST_GOOD); $default_values = [ 'Name' => '', 'Show header' => true, 'Refresh interval' => 'Default (1 minute)' ]; $data['fields'] = $update ? CTestArrayHelper::get($data, 'fields', $default_values) : CTestArrayHelper::get($data, 'fields', []); $this->page->login()->open('zabbix.php?action=dashboard.view&dashboardid='. self::$dashboardid['Dashboard for testing actions with discovery status widget']); $dashboard = CDashboardElement::find()->one(); $old_widget_count = $dashboard->getWidgets()->count(); $form = ($update) ? $dashboard->getWidget(self::$update_widget)->edit()->asForm() : $dashboard->edit()->addWidget()->asForm(); $form->fill(['Type' => CFormElement::RELOADABLE_FILL('Discovery status')]); if (!$data['fields']) { $data['fields'] = $default_values; } $form->fill($data['fields']); if ($expected === TEST_GOOD) { $values = $form->getFields()->filter(CElementFilter::VISIBLE)->asValues(); } $form->submit(); // Trim leading and trailing spaces from expected results if necessary. if (array_key_exists('trim', $data)) { $data['fields']['Name'] = trim($data['fields']['Name']); } // If name is empty string it is replaced by default name "Discovery status". $header = (CTestArrayHelper::get($data, 'fields.Name', '') === '') ? 'Discovery status' : $data['fields']['Name']; if ($update) { self::$update_widget = $header; } COverlayDialogElement::ensureNotPresent(); $widget = $dashboard->getWidget($header); // Save Dashboard to ensure that widget is correctly saved. $dashboard->save(); $this->page->waitUntilReady(); $this->assertMessage(TEST_GOOD, 'Dashboard updated'); // Check widgets count. $this->assertEquals($old_widget_count + ($update ? 0 : 1), $dashboard->getWidgets()->count()); // Check new widget update interval. $refresh = (CTestArrayHelper::get($data['fields'], 'Refresh interval') === 'Default (1 minute)') ? '1 minute' : CTestArrayHelper::get($data['fields'], 'Refresh interval'); $this->assertEquals($refresh, $widget->getRefreshInterval()); // Check new widget form fields and values in frontend. $saved_form = $widget->edit(); $this->assertEquals($values, $saved_form->getFields()->filter(CElementFilter::VISIBLE)->asValues()); $saved_form->checkValue($data['fields']); COverlayDialogElement::find()->one()->close(); $dashboard->save(); $this->page->waitUntilReady(); $this->assertMessage(TEST_GOOD, 'Dashboard updated'); } }