<?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__).'/../behaviors/CMessageBehavior.php'; require_once dirname(__FILE__).'/../behaviors/CTableBehavior.php'; /** * Base class for Host group form. */ class testFormGroups extends CWebTest { /** * Attach MessageBehavior and TableBehavior to the test. * * @return array */ public function getBehaviors() { return [ CMessageBehavior::class, CTableBehavior::class ]; } /** * Objects created in dataSource DiscoveredHosts. */ const DISCOVERED_GROUP = 'Group created from host prototype 1'; const HOST_PROTOTYPE = 'Host created from host prototype {#KEY}'; const LLD = 'LLD for Discovered host tests'; /** * Objects created in dataSource HostGroups. */ const DELETE_GROUP = 'Group empty for Delete test'; const DELETE_ONE_HOST_GROUP = 'One group belongs to one host for Delete test'; const DELETE_ONE_TEMPLATE_GROUP = 'One group belongs to one template for Delete test'; const DELETE_GROUP2 = 'First group to one object for Delete test'; /** * Host and template subgroup name for clone test scenario. */ const SUBGROUP = 'Group1/Subgroup1/Subgroup2'; /** * SQL query to get groups to compare hash values. */ const GROUPS_SQL = 'SELECT * FROM hstgrp g INNER JOIN hosts_groups hg ON g.groupid=hg.groupid'. ' ORDER BY g.groupid, hg.hostgroupid'; /** * SQL query to get user group permissions for host groups to compare hash values. */ const PERMISSION_SQL = 'SELECT * FROM rights ORDER BY rightid'; /** * Link to page for opening group form. */ protected $link; /** * Group form check on search page. */ protected $search = false; /** * Host group name for update and delete test scenario. */ protected static $update_group; /** * User group ID for subgroup permissions scenario. */ protected static $user_groupid; public static function prepareGroupData() { // Prepare data for host groups. CDataHelper::call('hostgroup.create', [ [ 'name' => 'Group for Update test' ], [ 'name' => 'Group1/Subgroup1/Subgroup2' ] ]); } /** * Test for checking group form layout. * * @param string $name group name * @param boolean $discovered discovered host group or not */ public function layout($name, $discovered = false) { // Open group from groups list and check existing group form. $form = $this->openForm($name, $discovered, $list = true); $this->page->assertHeader('Host groups'); $this->page->assertTitle('Configuration of host groups'); $this->assertTrue($form->isRequired('Group name')); if ($discovered) { $this->assertEquals(['Discovered by', 'Group name', 'Apply permissions and tag filters to all subgroups'], $form->getLabels(CElementFilter::VISIBLE)->asText() ); $this->assertTrue($form->getField('Group name')->isAttributePresent('readonly')); $this->assertEquals(self::LLD, $form->getField('Discovered by')->query('tag:a')->one()->getText()); $form->query('link', self::LLD)->one()->click(); $this->page->assertHeader('Host prototypes'); $this->query('id:host')->one()->checkValue(self::HOST_PROTOTYPE); return; } $form->checkValue(['Group name' => $name]); $this->assertEquals(['Update', 'Clone', 'Delete','Cancel'], $form->query('button')->all() ->filter(CElementFilter::CLICKABLE)->asText() ); $this->assertEquals(['Group name', 'Apply permissions and tag filters to all subgroups'], $form->getLabels(CElementFilter::VISIBLE)->asText() ); // There is no group creation on the search page. if ($this->search) { return; } // Check new group form. $this->page->open('hostgroups.php')->waitUntilReady(); $this->query('button:Create host group')->one()->click(); $this->page->assertHeader('Host groups'); $form->invalidate(); $this->assertTrue($form->getField('Group name')->isAttributePresent(['maxlength' => '255', 'value' => ''])); $this->assertTrue($form->isRequired('Group name')); $this->assertEquals(['Group name'], $form->getLabels(CElementFilter::VISIBLE)->asText()); $this->assertEquals(['Add', 'Cancel'], $form->query('button')->all()->filter(CElementFilter::CLICKABLE)->asText()); $form->query('button:Cancel')->one()->click(); $form->waitUntilNotVisible(); $this->assertEquals(PHPUNIT_URL.'hostgroups.php?cancel=1', $this->page->getCurrentUrl()); } /** * Function for opening group form. * * @param string $name group name to open * @param boolean $discovered discovered host group or not * @param boolean $list open group by name from list or by direct link * * @return CForm */ public function openForm($name = null, $discovered= false, $list = false) { // Open group from groups list. if ($this->search || $list) { $this->page->login()->open($this->search? $this->link : 'hostgroups.php')->waitUntilReady(); $column_name = $this->search ? 'Host group' : 'Name'; $table_selector = $this->search ? 'xpath://div[@id="search_hostgroup"]//table' : 'class:list-table'; $table = $this->query($table_selector)->asTable()->one(); $table->findRow($column_name, ($discovered && !$this->search) ? self::LLD.': '.$name : $name) ->getColumn($column_name)->query('link', $name)->one()->click(); } else { // Open group form by direct link. if ($name) { $groupid = CDBHelper::getValue('SELECT groupid FROM hstgrp WHERE name='.zbx_dbstr($name)); } $this->page->login()->open($name ? $this->link.$groupid : 'hostgroups.php?form=create')->waitUntilReady(); } return $this->query('name:hostgroupForm')->asForm()->waitUntilVisible()->one(); } public static function getCreateData() { return [ [ [ 'expected' => TEST_BAD, 'fields' => [ 'Group name' => 'Zabbix servers' ], 'error' => 'Host group "Zabbix servers" already exists.' ] ], [ [ 'expected' => TEST_BAD, 'fields' => [ 'Group name' => 'Templates' ], 'error' => 'Host group "Templates" already exists.' ] ], [ [ 'expected' => TEST_BAD, 'fields' => [ 'Group name' => self::DISCOVERED_GROUP ], 'error' => 'Host group "'.self::DISCOVERED_GROUP.'" already exists.' ] ], [ [ 'expected' => TEST_BAD, 'message' => 'Page received incorrect data', 'error' => 'Incorrect value for field "Group name": cannot be empty.' ] ], [ [ 'expected' => TEST_BAD, 'fields' => [ 'Group name' => ' ' ], 'message' => 'Page received incorrect data', 'error' => 'Incorrect value for field "Group name": cannot be empty.' ] ], [ [ 'expected' => TEST_BAD, 'fields' => [ 'Group name' => 'Test/Test/' ], 'error' => 'Invalid parameter "/1/name": invalid host group name.' ] ], [ [ 'expected' => TEST_BAD, 'fields' => [ 'Group name' => 'Test/Test\/' ], 'error' => 'Invalid parameter "/1/name": invalid host group name.' ] ], [ [ 'expected' => TEST_GOOD, 'fields' => [ 'Group name' => '~!@#$%^&*()_+=[]{}null{$A}{#B}' ] ] ], [ [ 'expected' => TEST_GOOD, 'fields' => [ 'Group name' => 'æ㓴🙂' ] ] ], [ [ 'expected' => TEST_GOOD, 'fields' => [ 'Group name' => ' trim ' ], 'trim' => true ] ], [ [ 'expected' => TEST_GOOD, 'fields' => [ 'Group name' => 'Group/Subgroup1/Subgroup2' ] ] ] ]; } public function getUpdateData() { $data = []; // Add 'update' word to group name and change group name in test case with trim. foreach ($this->getCreateData() as $group) { if ($group[0]['expected'] === TEST_GOOD) { $group[0]['fields']['Group name'] = CTestArrayHelper::get($group[0], 'trim', false) ? ' trim update ' : $group[0]['fields']['Group name'].'update'; } $data[] = $group; } return $data; } /** * Test for checking group creation and update. * * @param array $data data provider * @param ctring $action create or update action */ protected function checkForm($data, $action) { $good_message = 'Group '.(($action === 'create') ? 'added' : 'updated'); $bad_message = CTestArrayHelper::get($data, 'message', 'Cannot '.(($action === 'create') ? 'add' : 'update').' group' ); if ($data['expected'] === TEST_BAD) { $old_hash = CDBHelper::getHash(self::GROUPS_SQL); $permission_old_hash = CDBHelper::getHash(self::PERMISSION_SQL); } $form = $this->openForm(($action === 'update') ? static::$update_group : null); $form->fill(CTestArrayHelper::get($data, 'fields', [])); // Clear name for update scenario. if ($action === 'update' && !CTestArrayHelper::get($data, 'fields', false)) { $form->getField('Group name')->clear(); } $form->submit(); if ($data['expected'] === TEST_GOOD) { $this->assertMessage(TEST_GOOD, $good_message); // Trim trailing and leading spaces in expected values before comparison. if (CTestArrayHelper::get($data, 'trim', false)) { $data['fields']['Group name'] = trim($data['fields']['Group name']); } $form = $this->openForm($data['fields']['Group name']); $form->checkValue($data['fields']['Group name']); // Change group name after successful update scenario. if ($action === 'update') { static::$update_group = $data['fields']['Group name']; } } else { $this->assertEquals($old_hash, CDBHelper::getHash(self::GROUPS_SQL)); $this->assertEquals($permission_old_hash, CDBHelper::getHash(self::PERMISSION_SQL)); $this->assertMessage(TEST_BAD, $bad_message, $data['error']); } } /** * Update group without changing data. * * @param string $name group name to be opened for check */ public function simpleUpdate($name) { $old_hash = CDBHelper::getHash(self::GROUPS_SQL); $form = $this->openForm($name); $values = $form->getValues(); $form->submit(); $this->assertMessage(TEST_GOOD, 'Group updated'); $this->assertEquals($old_hash, CDBHelper::getHash(self::GROUPS_SQL)); // Check form values. $this->openForm($name); $form->invalidate(); $this->assertEquals($values, $form->getValues()); } public static function getCloneData() { return [ [ [ 'expected' => TEST_BAD, 'name' => self::SUBGROUP, 'error' => 'Host group "'.self::SUBGROUP.'" already exists.' ] ], [ [ 'expected' => TEST_GOOD, 'name' => self::DELETE_GROUP, 'fields' => [ 'Group name' => microtime().' cloned group' ] ] ], [ [ 'expected' => TEST_GOOD, 'name' => self::SUBGROUP, 'fields' => [ 'Group name' => microtime().'/cloned/subgroup' ] ] ], [ [ 'expected' => TEST_GOOD, 'name' => self::DISCOVERED_GROUP, 'fields' => [ 'Group name' => self::DISCOVERED_GROUP.' cloned group' ], 'discovered' => true ] ] ]; } public function clone($data) { if ($data['expected'] === TEST_BAD) { $old_hash = CDBHelper::getHash(self::GROUPS_SQL); } $form = $this->openForm($data['name'], CTestArrayHelper::get($data, 'discovered', false)); $form->query('button:Clone')->one()->waitUntilClickable()->click(); // Check that the group creation form is open after cloning. $this->page->assertHeader('Host groups'); $this->assertEquals(PHPUNIT_URL.'hostgroups.php', $this->page->getCurrentUrl()); $form->invalidate(); $this->assertEquals(['Add', 'Cancel'], $form->query('button')->all()->filter(CElementFilter::CLICKABLE)->asText()); $form->fill(CTestArrayHelper::get($data, 'fields', [])); $form->submit(); if ($data['expected'] === TEST_GOOD) { $this->assertMessage(TEST_GOOD, 'Group added'); $this->assertEquals(PHPUNIT_URL.'hostgroups.php', $this->page->getCurrentUrl()); $form = $this->openForm($data['fields']['Group name']); $form->checkValue($data['fields']['Group name']); $this->assertEquals(2, CDBHelper::getCount('SELECT NULL FROM hstgrp WHERE name IN ('. zbx_dbstr($data['name']).', '.zbx_dbstr($data['fields']['Group name']).')') ); } else { $this->assertEquals($old_hash, CDBHelper::getHash(self::GROUPS_SQL)); $this->assertMessage(TEST_BAD, 'Cannot add group', $data['error']); } } public static function getCancelData() { return [ [ [ 'action' => 'Add' ] ], [ [ 'action' => 'Update' ] ], [ [ 'action' => 'Clone' ] ], [ [ 'action' => 'Delete' ] ] ]; } /** * Test for checking group actions cancelling. * * @param array $data data provider with fields values */ public function cancel($data) { // There is no group creation on the search page. if ($this->search && $data['action'] === 'Add') { return; } $old_hash = CDBHelper::getHash(self::GROUPS_SQL); $new_name = microtime(true).' Cancel '.self::DELETE_GROUP; $form = $this->openForm(($data['action'] === 'Add') ? null : self::DELETE_GROUP); // Change name. $form->fill(['Group name' => $new_name]); if (in_array($data['action'], ['Clone', 'Delete'])) { $form->query('button', $data['action'])->one()->click(); } if ($data['action'] === 'Delete') { $this->page->dismissAlert(); } $form->query('button:Cancel')->waitUntilClickable()->one()->click(); $this->page->assertHeader('Host groups'); $this->assertEquals(PHPUNIT_URL.'hostgroups.php?cancel=1', $this->page->getCurrentUrl()); $this->assertEquals($old_hash, CDBHelper::getHash(self::GROUPS_SQL)); } public static function getDeleteData() { return [ [ [ 'expected' => TEST_BAD, 'name' => self::DELETE_ONE_HOST_GROUP, 'error' => 'Host "Host for host group testing" cannot be without host group.' ] ], [ [ 'expected' => TEST_BAD, 'name' => self::DELETE_ONE_TEMPLATE_GROUP, 'error' => 'Template "Template for host group testing" cannot be without host group.' ] ], [ [ 'expected' => TEST_BAD, 'name' => 'Group for Maintenance', 'error' => 'Cannot delete host group "Group for Maintenance" because maintenance'. ' "Maintenance for host group testing" must contain at least one host or host group.' ] ], [ [ 'expected' => TEST_BAD, 'name' => 'Group for Correlation', 'error' => 'Group "Group for Correlation" cannot be deleted, because it is used in a correlation condition.' ] ], [ [ 'expected' => TEST_BAD, 'name' => 'Group for Script', 'error' => 'Host group "Group for Script" cannot be deleted, because it is used in a global script.' ] ], [ [ 'expected' => TEST_BAD, 'name' => 'Group for Host prototype', 'error' => 'Group "Group for Host prototype" cannot be deleted, because it is used by a host prototype.' ] ], [ [ 'expected' => TEST_BAD, 'name' => 'Discovered hosts', 'error' => 'Host group "Discovered hosts" is internal and cannot be deleted.' ] ], // Empty group without Host/Template. [ [ 'expected' => TEST_GOOD, 'name' => self::DELETE_GROUP ] ], // Host has two groups, one of them can be deleted. [ [ 'expected' => TEST_GOOD, 'name' => self::DELETE_GROUP2 ] ], [ [ 'expected' => TEST_GOOD, 'name' => 'Group for Action' ] ] ]; } /** * Test for checking group deletion. * * @param array $data data provider */ public function delete($data) { if ($data['expected'] === TEST_BAD) { $old_hash = CDBHelper::getHash(self::GROUPS_SQL); } $form = $this->openForm($data['name']); $form->query('button:Delete')->one()->waitUntilClickable()->click(); $this->assertEquals('Delete selected group?', $this->page->getAlertText()); $this->page->acceptAlert(); if ($data['expected'] === TEST_GOOD) { $this->assertMessage(TEST_GOOD, 'Group deleted'); $this->assertEquals(0, CDBHelper::getCount('SELECT NULL FROM hstgrp WHERE name='.zbx_dbstr($data['name']))); } else { $this->assertEquals($old_hash, CDBHelper::getHash(self::GROUPS_SQL)); $this->assertMessage(TEST_BAD, 'Cannot delete group', $data['error']); } } public static function prepareSubgroupData() { CDataHelper::call('hostgroup.create', [ [ 'name' => 'Europe' ], [ 'name' => 'Europe/Latvia' ], [ 'name' => 'Europe/Latvia/Riga/Zabbix' ], [ 'name' => 'Europe/Test' ], [ 'name' => 'Europe/Test/Zabbix' ], // Groups to check inherited permissions when creating a parent or subgroup. [ 'name' => 'Streets' ], [ 'name' => 'Cities/Cesis' ], [ 'name' => 'Europe group for test on search page' ] ]); $host_groupids = CDataHelper::getIds('name'); $response = CDataHelper::call('usergroup.create', [ [ 'name' => 'User group to check subgroup permissions', 'rights' => [ [ 'permission' => PERM_DENY, 'id' => $host_groupids['Europe'] ], [ 'permission' => PERM_READ, 'id' => $host_groupids['Europe/Latvia'] ], [ 'permission' => PERM_READ_WRITE, 'id' => $host_groupids['Europe/Test'] ], [ 'permission' => PERM_DENY, 'id' => $host_groupids['Streets'] ], [ 'permission' => PERM_READ, 'id' => $host_groupids['Cities/Cesis'] ] ], 'tag_filters' => [ [ 'groupid' => $host_groupids['Europe'], 'tag' => 'world', 'value' => '' ], [ 'groupid' => $host_groupids['Europe/Test'], 'tag' => 'country', 'value' => 'test' ], [ 'groupid' => $host_groupids['Streets'], 'tag' => 'street', 'value' => '' ], [ 'groupid' => $host_groupids['Cities/Cesis'], 'tag' => 'city', 'value' => 'Cesis' ] ] ] ]); self::$user_groupid = $response['usrgrpids'][0]; } public static function getSubgoupsData() { return [ [ [ 'apply_permissions' => 'Europe/Test', 'create' => 'Cities', // "groups_before" and "tags_before" parameters aren't used in test, but they are listed here for test clarity. 'groups_before' => [ 'All groups' => 'None', 'Cities/Cesis' => 'Read', 'Europe' => 'Deny', 'Europe/Latvia' => 'Read', 'Europe/Latvia/Riga/Zabbix' => 'None', 'Europe/Test' => 'Read-write', 'Europe/Test/Zabbix' => 'None', 'Streets' => 'Deny' ], 'tags_before' => [ ['Host group' => 'Cities/Cesis', 'Tags' => 'city: Cesis'], ['Host group' => 'Europe', 'Tags' => 'world'], ['Host group' => 'Europe/Test', 'Tags' => 'country: test'], ['Host group' => 'Streets', 'Tags' => 'street'] ], 'groups_after' => [ 'Cities/Cesis' => 'Read', 'Europe' => 'Deny', 'Europe/Latvia' => 'Read', 'Europe/Latvia/Riga/Zabbix' => 'None', 'Europe/Test (including subgroups)' => 'Read-write', 'Streets' => 'Deny' ], 'tags_after' => [ ['Host group' => 'Cities/Cesis', 'Tags' => 'city: Cesis'], ['Host group' => 'Europe' , 'Tags' => 'world'], ['Host group' => 'Europe/Test', 'Tags' => 'country: test'], ['Host group' => 'Europe/Test/Zabbix', 'Tags' => 'country: test'], ['Host group' => 'Streets', 'Tags' => 'street'] ] ] ], [ [ 'apply_permissions' => 'Europe', 'create' => 'Streets/Dzelzavas', 'groups_before' => [ 'All groups' => 'None', 'Cities/Cesis' => 'Read', 'Europe' => 'Deny', 'Europe/Latvia (including subgroups)' => 'Read', 'Europe/Test (including subgroups)' => 'Read-write', 'Streets' => 'Deny' ], 'tags_before' => [ ['Host group' => 'Cities/Cesis', 'Tags' => 'city: Cesis'], ['Host group' => 'Europe', 'Tags' => 'world'], ['Host group' => 'Europe/Test', 'Tags' => 'country: test'], ['Host group' => 'Europe/Test/Zabbix', 'Tags' => 'country: test'], ['Host group' => 'Streets', 'Tags' => 'street'] ], 'groups_after' => [ 'Cities/Cesis' => 'Read', 'Europe (including subgroups)' => 'Deny', 'Streets (including subgroups)' => 'Deny' ], 'tags_after' => [ ['Host group' => 'Cities/Cesis', 'Tags' => 'city: Cesis'], ['Host group' => 'Europe', 'Tags' => 'world'], ['Host group' => 'Europe/Latvia', 'Tags' => 'world'], ['Host group' => 'Europe/Latvia/Riga/Zabbix', 'Tags' => 'world'], ['Host group' => 'Europe/Test', 'Tags' => 'world'], ['Host group' => 'Europe/Test/Zabbix', 'Tags' => 'world'], ['Host group' => 'Streets', 'Tags' => 'street'], ['Host group' => 'Streets/Dzelzavas', 'Tags' => 'street'] ] ] ] ]; } /** * Apply the same level of permissions/tag filters to all nested host groups. * * @param array $data data provider */ public function checkSubgroupsPermissions($data) { // Prepare groups array according framework function assertTableData(). $selector = 'xpath:.//input[@checked]/following-sibling::label'; $groups = []; foreach ($data['groups_after'] as $group => $permissions) { $groups[] = [ 'Host group' => $group, 'Permissions' => [ 'text' => $permissions, 'selector' => $selector ] ]; } $data['groups_after'] = $groups; array_unshift($data['groups_after'], ['Host group' => 'All groups', 'Permissions' => 'None']); // Create new parent or subgroup to check nested permissions. if (array_key_exists('create', $data)) { $form = $this->openForm(CTestArrayHelper::get($data, 'open_form', null)); $form->fill(['Group name' => $data['create']]); $form->submit(); // Permission inheritance doesn't apply when changing the name of existing group, only when creating a new group. $this->assertMessage(TEST_GOOD, 'Group '.(array_key_exists('open_form', $data) ? 'updated' : 'added') ); } // Apply permissions to subgroups. $form = $this->openForm($data['apply_permissions']); $form->fill(['Apply permissions and tag filters to all subgroups' => true]); $form->submit(); $this->assertMessage(TEST_GOOD, 'Group updated'); // Check group and tag permissions in user group. $this->page->open('zabbix.php?action=usergroup.edit&usrgrpid='.self::$user_groupid)->waitUntilReady(); $group_form = $this->query('id:user-group-form')->asForm()->one(); $group_form->selectTab('Permissions'); $this->assertTableData($data['groups_after'], 'id:group-right-table'); $group_form->selectTab('Tag filter'); $this->assertTableData($data['tags_after'], 'id:tag-filter-table'); } }