summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--freeipa.spec.in1
-rw-r--r--ipatests/setup.py.in1
-rw-r--r--ipatests/test_webui/README1
-rw-r--r--ipatests/test_webui/__init__.py22
-rw-r--r--ipatests/test_webui/data_group.py70
-rw-r--r--ipatests/test_webui/data_hbac.py57
-rw-r--r--ipatests/test_webui/data_hostgroup.py81
-rw-r--r--ipatests/test_webui/data_netgroup.py43
-rw-r--r--ipatests/test_webui/data_sudo.py82
-rw-r--r--ipatests/test_webui/data_user.py49
-rw-r--r--ipatests/test_webui/test_automember.py94
-rw-r--r--ipatests/test_webui/test_automount.py104
-rw-r--r--ipatests/test_webui/test_cert.py47
-rw-r--r--ipatests/test_webui/test_config.py52
-rw-r--r--ipatests/test_webui/test_delegation.py51
-rw-r--r--ipatests/test_webui/test_dns.py107
-rw-r--r--ipatests/test_webui/test_group.py194
-rw-r--r--ipatests/test_webui/test_hbac.py159
-rw-r--r--ipatests/test_webui/test_host.py269
-rw-r--r--ipatests/test_webui/test_hostgroup.py141
-rw-r--r--ipatests/test_webui/test_krbtpolicy.py52
-rw-r--r--ipatests/test_webui/test_navigation.py149
-rw-r--r--ipatests/test_webui/test_netgroup.py81
-rw-r--r--ipatests/test_webui/test_pwpolicy.py52
-rw-r--r--ipatests/test_webui/test_range.py49
-rw-r--r--ipatests/test_webui/test_rbac.py83
-rw-r--r--ipatests/test_webui/test_realmdomains.py48
-rw-r--r--ipatests/test_webui/test_selfservice.py48
-rw-r--r--ipatests/test_webui/test_selinuxusermap.py104
-rw-r--r--ipatests/test_webui/test_service.py129
-rw-r--r--ipatests/test_webui/test_sudo.py122
-rw-r--r--ipatests/test_webui/test_user.py159
-rw-r--r--ipatests/test_webui/ui_driver.py1325
33 files changed, 4026 insertions, 0 deletions
diff --git a/freeipa.spec.in b/freeipa.spec.in
index 742877531..86de29ffc 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -813,6 +813,7 @@ fi
%dir %{python_sitelib}/ipatests/test_ipaserver
%dir %{python_sitelib}/ipatests/test_ipaserver/install
%dir %{python_sitelib}/ipatests/test_pkcs10
+%dir %{python_sitelib}/ipatests/test_webui
%dir %{python_sitelib}/ipatests/test_xmlrpc
%{_bindir}/ipa-run-tests
%{_bindir}/ipa-test-config
diff --git a/ipatests/setup.py.in b/ipatests/setup.py.in
index 2a5bffce9..845a64642 100644
--- a/ipatests/setup.py.in
+++ b/ipatests/setup.py.in
@@ -74,6 +74,7 @@ def setup_package():
"ipatests.test_ipaserver",
"ipatests.test_ipaserver.install",
"ipatests.test_pkcs10",
+ "ipatests.test_webui",
"ipatests.test_xmlrpc"],
scripts=['ipa-run-tests', 'ipa-test-config'],
package_data = {
diff --git a/ipatests/test_webui/README b/ipatests/test_webui/README
new file mode 100644
index 000000000..268c11d75
--- /dev/null
+++ b/ipatests/test_webui/README
@@ -0,0 +1 @@
+http://www.freeipa.org/page/Web_UI_Integration_Tests \ No newline at end of file
diff --git a/ipatests/test_webui/__init__.py b/ipatests/test_webui/__init__.py
new file mode 100644
index 000000000..56d10d90b
--- /dev/null
+++ b/ipatests/test_webui/__init__.py
@@ -0,0 +1,22 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Sub-package containing Web UI integration tests
+"""
diff --git a/ipatests/test_webui/data_group.py b/ipatests/test_webui/data_group.py
new file mode 100644
index 000000000..c933abee1
--- /dev/null
+++ b/ipatests/test_webui/data_group.py
@@ -0,0 +1,70 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+ENTITY = 'group'
+DEFAULT_FACET = 'member_user'
+
+PKEY = 'itest-group'
+DATA = {
+ 'pkey': PKEY,
+ 'add': [
+ ('textbox', 'cn', PKEY),
+ ('textarea', 'description','test-group desc'),
+ ('radio', 'type','normal'),
+ ],
+ 'mod': [
+ ('textarea', 'description','test-group desc modified'),
+ ],
+}
+
+PKEY2 = 'itest-group2'
+DATA2 = {
+ 'pkey': PKEY2,
+ 'add': [
+ ('textbox', 'cn', PKEY2),
+ ('textarea', 'description','test-group2 desc'),
+ ]
+}
+
+PKEY3 = 'itest-group3'
+DATA3= {
+ 'pkey': PKEY3,
+ 'add': [
+ ('textbox', 'cn', PKEY3),
+ ('textarea', 'description','test-group3 desc'),
+ ]
+}
+
+PKEY4 = 'itest-group4'
+DATA4= {
+ 'pkey': PKEY4,
+ 'add': [
+ ('textbox', 'cn', PKEY4),
+ ('textarea', 'description','test-group4 desc'),
+ ]
+}
+
+PKEY5 = 'itest-group5'
+DATA5= {
+ 'pkey': PKEY5,
+ 'add': [
+ ('textbox', 'cn', PKEY5),
+ ('textarea', 'description','test-group5 desc'),
+ ]
+} \ No newline at end of file
diff --git a/ipatests/test_webui/data_hbac.py b/ipatests/test_webui/data_hbac.py
new file mode 100644
index 000000000..bc420fe03
--- /dev/null
+++ b/ipatests/test_webui/data_hbac.py
@@ -0,0 +1,57 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+RULE_ENTITY = 'hbacrule'
+RULE_PKEY = 'itesthbacrule'
+RULE_DATA = {
+ 'pkey': RULE_PKEY,
+ 'add': [
+ ('textbox', 'cn', RULE_PKEY),
+ ],
+ 'mod': [
+ ('textarea', 'description', 'testhbacrulec desc'),
+ ],
+}
+
+SVC_ENTITY = 'hbacsvc'
+SVC_PKEY = 'itesthbacsvc'
+SVC_DATA = {
+ 'pkey': SVC_PKEY,
+ 'add': [
+ ('textbox', 'cn', SVC_PKEY),
+ ('textarea', 'description', 'testhbacsvc desc'),
+ ],
+ 'mod': [
+ ('textarea', 'description', 'testhbacsvc desc mod'),
+ ],
+}
+
+SVCGROUP_ENTITY = 'hbacsvcgroup'
+SVCGROUP_DEF_FACET = 'member_hbacsvc'
+SVCGROUP_PKEY = 'itesthbaccvcgroup'
+SVCGROUP_DATA = {
+ 'pkey': SVCGROUP_PKEY,
+ 'add': [
+ ('textbox', 'cn', SVCGROUP_PKEY),
+ ('textarea', 'description', 'testhbaccvcgroup desc'),
+ ],
+ 'mod': [
+ ('textarea', 'description', 'testhbaccvcgroup desc mod'),
+ ],
+} \ No newline at end of file
diff --git a/ipatests/test_webui/data_hostgroup.py b/ipatests/test_webui/data_hostgroup.py
new file mode 100644
index 000000000..811168cda
--- /dev/null
+++ b/ipatests/test_webui/data_hostgroup.py
@@ -0,0 +1,81 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+ENTITY = 'hostgroup'
+DEFAULT_FACET = 'member_host'
+
+PKEY = 'itest-hostgroup'
+DATA = {
+ 'pkey': PKEY,
+ 'add': [
+ ('textbox', 'cn', PKEY),
+ ('textarea', 'description','test-hostgroup desc'),
+ ],
+ 'mod': [
+ ('textarea', 'description','test-hostgroup desc modified'),
+ ],
+}
+
+PKEY2 = 'itest-hostgroup2'
+DATA2 = {
+ 'pkey': PKEY2,
+ 'add': [
+ ('textbox', 'cn', PKEY2),
+ ('textarea', 'description','test-hostgroup2 desc'),
+ ],
+ 'mod': [
+ ('textarea', 'description','test-hostgroup2 desc modified'),
+ ],
+}
+
+PKEY3 = 'itest-hostgroup3'
+DATA3 = {
+ 'pkey': PKEY3,
+ 'add': [
+ ('textbox', 'cn', PKEY3),
+ ('textarea', 'description','test-hostgroup3 desc'),
+ ],
+ 'mod': [
+ ('textarea', 'description','test-hostgroup3 desc modified'),
+ ],
+}
+
+PKEY4 = 'itest-hostgroup4'
+DATA4 = {
+ 'pkey': PKEY4,
+ 'add': [
+ ('textbox', 'cn', PKEY4),
+ ('textarea', 'description','test-hostgroup4 desc'),
+ ],
+ 'mod': [
+ ('textarea', 'description','test-hostgroup4 desc modified'),
+ ],
+}
+
+PKEY5 = 'itest-hostgroup5'
+DATA5 = {
+ 'pkey': PKEY5,
+ 'add': [
+ ('textbox', 'cn', PKEY5),
+ ('textarea', 'description','test-hostgroup5 desc'),
+ ],
+ 'mod': [
+ ('textarea', 'description','test-hostgroup5 desc modified'),
+ ],
+} \ No newline at end of file
diff --git a/ipatests/test_webui/data_netgroup.py b/ipatests/test_webui/data_netgroup.py
new file mode 100644
index 000000000..41ed85b31
--- /dev/null
+++ b/ipatests/test_webui/data_netgroup.py
@@ -0,0 +1,43 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+ENTITY = 'netgroup'
+PKEY = 'itest-netgroup'
+DATA = {
+ 'pkey': PKEY,
+ 'add': [
+ ('textbox', 'cn', PKEY),
+ ('textarea', 'description', 'test-netgroup desc'),
+ ],
+ 'mod': [
+ ('textarea', 'description', 'test-netgroup desc modified'),
+ ],
+}
+
+PKEY2 = 'itest-netgroup2'
+DATA2 = {
+ 'pkey': PKEY2,
+ 'add': [
+ ('textbox', 'cn', PKEY2),
+ ('textarea', 'description', 'test-netgroup2 desc'),
+ ],
+ 'mod': [
+ ('textarea', 'description', 'test-netgroup2 desc modified'),
+ ],
+} \ No newline at end of file
diff --git a/ipatests/test_webui/data_sudo.py b/ipatests/test_webui/data_sudo.py
new file mode 100644
index 000000000..e555bcf08
--- /dev/null
+++ b/ipatests/test_webui/data_sudo.py
@@ -0,0 +1,82 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+RULE_ENTITY = 'sudorule'
+CMDENTITY = 'sudocmd'
+CMDGROUP_ENTITY = 'sudocmdgroup'
+CMDGROUP_DEF_FACET = 'member_sudocmd'
+
+RULE_PKEY = 'itestsudorule'
+RULE_DATA = {
+ 'pkey': RULE_PKEY,
+ 'add': [
+ ('textbox', 'cn', RULE_PKEY),
+ ],
+ 'mod': [
+ ('textarea', 'description', 'itestsudorule desc'),
+ ],
+}
+
+CMD_PKEY = 'itestsudocmd'
+CMD_DATA = {
+ 'pkey': CMD_PKEY,
+ 'add': [
+ ('textbox', 'sudocmd', CMD_PKEY),
+ ('textarea', 'description', 'itestsudocmd desc'),
+ ],
+ 'mod': [
+ ('textarea', 'description', 'itestsudocmd desc mod'),
+ ],
+}
+
+CMD_PKEY2 = 'itestsudocmd2'
+CMD_DATA2 = {
+ 'pkey': CMD_PKEY2,
+ 'add': [
+ ('textbox', 'sudocmd', CMD_PKEY2),
+ ('textarea', 'description', 'itestsudocmd2 desc'),
+ ],
+ 'mod': [
+ ('textarea', 'description', 'itestsudocmd2 desc mod'),
+ ],
+}
+
+CMD_GROUP_PKEY = 'itestsudocmdgroup'
+CMDGROUP_DATA = {
+ 'pkey': CMD_GROUP_PKEY,
+ 'add': [
+ ('textbox', 'cn', CMD_GROUP_PKEY),
+ ('textarea', 'description', 'itestsudocmdgroup desc'),
+ ],
+ 'mod': [
+ ('textarea', 'description', 'itestsudocmdgroup desc mod'),
+ ],
+}
+
+CMD_GROUP_PKEY2 = 'itestsudocmdgroup2'
+CMDGROUP_DATA2 = {
+ 'pkey': CMD_GROUP_PKEY2,
+ 'add': [
+ ('textbox', 'cn', CMD_GROUP_PKEY2),
+ ('textarea', 'description', 'itestsudocmdgroup2 desc'),
+ ],
+ 'mod': [
+ ('textarea', 'description', 'itestsudocmdgroup2 desc mod'),
+ ],
+} \ No newline at end of file
diff --git a/ipatests/test_webui/data_user.py b/ipatests/test_webui/data_user.py
new file mode 100644
index 000000000..c44eb4307
--- /dev/null
+++ b/ipatests/test_webui/data_user.py
@@ -0,0 +1,49 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+
+ENTITY = 'user'
+
+PKEY = 'itest-user'
+DATA = {
+ 'pkey': PKEY,
+ 'add': [
+ ('textbox', 'uid', PKEY),
+ ('textbox', 'givenname', 'Name'),
+ ('textbox', 'sn', 'Surname'),
+ ],
+ 'mod': [
+ ('textbox', 'givenname','OtherName'),
+ ('textbox', 'sn','OtherSurname'),
+ ],
+}
+
+PKEY2 = 'itest-user2'
+DATA2 = {
+ 'pkey': PKEY2,
+ 'add': [
+ ('textbox', 'uid', PKEY2),
+ ('textbox', 'givenname', 'Name2'),
+ ('textbox', 'sn', 'Surname2'),
+ ],
+ 'mod': [
+ ('textbox', 'givenname','OtherName2'),
+ ('textbox', 'sn','OtherSurname2'),
+ ],
+} \ No newline at end of file
diff --git a/ipatests/test_webui/test_automember.py b/ipatests/test_webui/test_automember.py
new file mode 100644
index 000000000..d48ecc93d
--- /dev/null
+++ b/ipatests/test_webui/test_automember.py
@@ -0,0 +1,94 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Automember tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+import ipatests.test_webui.data_hostgroup as hostgroup
+
+ENTITY = 'automember'
+
+USER_GROUP_PKEY = 'admins'
+USER_GROUP_DATA = {
+ 'pkey': USER_GROUP_PKEY,
+ 'add': [
+ ('combobox', 'cn', USER_GROUP_PKEY),
+ ],
+ 'mod': [
+ ('textarea', 'description', 'user group rule description'),
+ #(
+ #'add_table_record',
+ #'automemberinclusiveregex',
+ #(
+ #'table-widget',
+ #{
+ #'fields': [
+ #('textbox', 'automemberinclusiveregex', 'testregex')
+ #]
+ #},
+ #)
+ #)
+ ],
+}
+
+HOST_GROUP_DATA = {
+ 'pkey': hostgroup.PKEY,
+ 'add': [
+ ('combobox', 'cn', hostgroup.PKEY),
+ ],
+ 'mod': [
+ ('textarea', 'description', 'host group rule description'),
+ ],
+}
+
+class test_automember(UI_driver):
+
+ def test_crud(self):
+ """
+ Basic CRUD: automember
+ """
+ self.init_app()
+
+ # user group rule
+ self.basic_crud(ENTITY, USER_GROUP_DATA,
+ search_facet='searchgroup',
+ default_facet='usergrouprule',
+ details_facet='usergrouprule',
+ )
+
+ # prepare host group
+ self.basic_crud(hostgroup.ENTITY, hostgroup.DATA,
+ default_facet=hostgroup.DEFAULT_FACET,
+ delete=False)
+
+ # host group rule
+ self.navigate_by_menu('policy/automember/amhostgroup')
+
+ self.basic_crud(ENTITY, HOST_GROUP_DATA,
+ search_facet='searchhostgroup',
+ default_facet='hostgrouprule',
+ details_facet='hostgrouprule',
+ navigate=False,
+ breadcrumb='Host group rules',
+ )
+
+ # cleanup
+ self.delete(hostgroup.ENTITY, [hostgroup.DATA])
diff --git a/ipatests/test_webui/test_automount.py b/ipatests/test_webui/test_automount.py
new file mode 100644
index 000000000..1f6f29b76
--- /dev/null
+++ b/ipatests/test_webui/test_automount.py
@@ -0,0 +1,104 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Automount tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+
+LOC_ENTITY = 'automountlocation'
+MAP_ENTITY = 'automountmap'
+KEY_ENTITY = 'automountkey'
+
+LOC_PKEY = 'itestloc'
+LOC_DATA = {
+ 'pkey': LOC_PKEY,
+ 'add': [
+ ('textbox', 'cn', LOC_PKEY),
+ ],
+}
+
+MAP_PKEY = 'itestmap'
+MAP_DATA = {
+ 'pkey': MAP_PKEY,
+ 'add': [
+ ('textbox', 'automountmapname', MAP_PKEY),
+ ('textarea', 'description', 'map desc'),
+ ],
+ 'mod': [
+ ('textarea', 'description', 'map desc mod'),
+ ]
+}
+
+KEY_PKEY = 'itestkey'
+KEY_DATA = {
+ 'pkey': KEY_PKEY,
+ 'add': [
+ ('textbox', 'automountkey', KEY_PKEY),
+ ('textbox', 'automountinformation', '/itest/key'),
+ ],
+ 'mod': [
+ ('textbox', 'automountinformation', '/itest/key2'),
+ ]
+}
+
+class test_automount(UI_driver):
+
+ def test_crud(self):
+ """
+ Basic CRUD: automount
+ """
+ self.init_app()
+
+ # location
+ self.basic_crud(LOC_ENTITY, LOC_DATA,
+ default_facet='maps',
+ delete=False,
+ breadcrumb='Automount Locations'
+ )
+
+ # map
+ self.navigate_to_record(LOC_PKEY)
+
+ self.basic_crud(MAP_ENTITY, MAP_DATA,
+ parent_entity=LOC_ENTITY,
+ search_facet='maps',
+ default_facet='keys',
+ delete=False,
+ navigate=False,
+ breadcrumb=LOC_PKEY,
+ )
+
+ # key
+ self.navigate_to_record(MAP_PKEY)
+
+ self.basic_crud(KEY_ENTITY, KEY_DATA,
+ parent_entity=MAP_ENTITY,
+ search_facet='keys',
+ navigate=False,
+ breadcrumb=MAP_PKEY,
+ )
+
+ # delete
+ self.navigate_by_breadcrumb(LOC_PKEY)
+ self.delete_record(MAP_PKEY)
+
+ self.navigate_by_breadcrumb('Automount Locations')
+ self.delete_record(LOC_PKEY)
diff --git a/ipatests/test_webui/test_cert.py b/ipatests/test_webui/test_cert.py
new file mode 100644
index 000000000..a59f12690
--- /dev/null
+++ b/ipatests/test_webui/test_cert.py
@@ -0,0 +1,47 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Cert tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+
+ENTITY = 'cert'
+
+class test_cert(UI_driver):
+
+ def __init__(self, *args, **kwargs):
+ super(test_cert, self).__init__(args, kwargs)
+
+ if not self.has_ca():
+ self.skip('CA not configured')
+
+
+ def test_read(self):
+ """
+ Basic read: cert
+
+ Certs don't have standard mod, add and delete methods.
+ """
+ self.init_app()
+ self.navigate_to_entity(ENTITY)
+ rows = self.get_rows()
+ self.navigate_to_row_record(rows[0])
+ self.navigate_by_breadcrumb("Certificates")
diff --git a/ipatests/test_webui/test_config.py b/ipatests/test_webui/test_config.py
new file mode 100644
index 000000000..fb160e81b
--- /dev/null
+++ b/ipatests/test_webui/test_config.py
@@ -0,0 +1,52 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Config tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+
+ENTITY = 'config'
+
+DATA = {
+ 'mod': [
+ ('textbox', 'ipasearchrecordslimit','200'),
+ ('textbox', 'ipasearchtimelimit','3'),
+ ],
+}
+
+DATA2 = {
+ 'mod': [
+ ('textbox', 'ipasearchrecordslimit','100'),
+ ('textbox', 'ipasearchtimelimit','2'),
+ ],
+}
+
+class test_config(UI_driver):
+
+ def test_mod(self):
+ """
+ Config mod tests
+ """
+ self.init_app()
+ self.navigate_to_entity(ENTITY)
+
+ self.mod_record(ENTITY, DATA)
+ self.mod_record(ENTITY, DATA2)
diff --git a/ipatests/test_webui/test_delegation.py b/ipatests/test_webui/test_delegation.py
new file mode 100644
index 000000000..0c59e8585
--- /dev/null
+++ b/ipatests/test_webui/test_delegation.py
@@ -0,0 +1,51 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Delegation tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+
+ENTITY = 'delegation'
+PKEY = 'itest-delegation-rule'
+
+DATA = {
+ 'pkey': PKEY,
+ 'add': [
+ ('textbox', 'aciname', PKEY),
+ ('combobox', 'group', 'editors'),
+ ('combobox', 'memberof', 'ipausers'),
+ ('table', 'attrs', 'audio'),
+ ('table', 'attrs', 'businesscategory'),
+ ],
+ 'mod': [
+ ('table', 'attrs', 'businesscategory'),
+ ],
+}
+
+class test_delegation(UI_driver):
+
+
+ def test_crud(self):
+ """
+ Basic CRUD: delegation
+ """
+ self.init_app()
+ self.basic_crud(ENTITY, DATA)
diff --git a/ipatests/test_webui/test_dns.py b/ipatests/test_webui/test_dns.py
new file mode 100644
index 000000000..23a3a4b19
--- /dev/null
+++ b/ipatests/test_webui/test_dns.py
@@ -0,0 +1,107 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+DNS tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+
+ZONE_ENTITY = 'dnszone'
+RECORD_ENTITY = 'dnsrecord'
+CONFIG_ENTITY = 'dnsconfig'
+
+ZONE_DEFAULT_FACET = 'records'
+
+ZONE_PKEY = 'foo.itest'
+
+ZONE_DATA = {
+ 'pkey': ZONE_PKEY,
+ 'add': [
+ ('textbox', 'idnsname', ZONE_PKEY),
+ ('textbox', 'idnssoamname', 'ns'),
+ ('textbox', 'ip_address', '192.168.1.1'),
+ ('checkbox', 'force', ''),
+ ],
+ 'mod': [
+ ('checkbox', 'idnsallowsyncptr',''),
+ ],
+}
+
+
+RECORD_PKEY = 'itest'
+RECORD_ADD_DATA = {
+ 'pkey': RECORD_PKEY,
+ 'add': [
+ ('textbox', 'idnsname', RECORD_PKEY),
+ ('textbox', 'a_part_ip_address', '192.168.1.10'),
+ ]
+}
+
+RECORD_MOD_DATA = {
+ 'fields': [
+ ('textbox', 'a_part_ip_address', '192.168.1.11'),
+ ]
+}
+
+CONFIG_MOD_DATA = {
+ 'mod': [
+ ('checkbox', 'idnsallowsyncptr',''),
+ ],
+}
+
+class test_dns(UI_driver):
+
+ def __init__(self, *args, **kwargs):
+ super(test_dns, self).__init__(args, kwargs)
+
+ if not self.has_dns():
+ self.skip('DNS not configured')
+
+ def test_zone_record_crud(self):
+ """
+ Basic CRUD: dns
+ """
+ self.init_app()
+
+ # add and mod zone
+ self.basic_crud(ZONE_ENTITY, ZONE_DATA,
+ default_facet=ZONE_DEFAULT_FACET , delete=False)
+
+ # add and mod record
+ self.navigate_to_record(ZONE_PKEY)
+ self.add_record(ZONE_ENTITY, RECORD_ADD_DATA,
+ facet=ZONE_DEFAULT_FACET, navigate=False)
+ self.navigate_to_record(RECORD_PKEY)
+ self.add_table_record('arecord', RECORD_MOD_DATA)
+
+ # del record, del zone
+ self.navigate_by_breadcrumb(ZONE_PKEY)
+ self.delete_record(RECORD_PKEY)
+ self.navigate_by_breadcrumb("DNS Zones")
+ self.delete_record(ZONE_PKEY)
+
+
+ def test_config_crud(self):
+ """
+ Basic CRUD: dnsconfig
+ """
+ self.init_app()
+ self.navigate_by_menu('identity/dns/dnsconfig')
+ self.mod_record(CONFIG_ENTITY, CONFIG_MOD_DATA)
diff --git a/ipatests/test_webui/test_group.py b/ipatests/test_webui/test_group.py
new file mode 100644
index 000000000..645dba0a0
--- /dev/null
+++ b/ipatests/test_webui/test_group.py
@@ -0,0 +1,194 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Group tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+import ipatests.test_webui.data_group as group
+import ipatests.test_webui.data_user as user
+import ipatests.test_webui.data_netgroup as netgroup
+import ipatests.test_webui.data_hbac as hbac
+import ipatests.test_webui.test_rbac as rbac
+import ipatests.test_webui.data_sudo as sudo
+
+
+class test_group(UI_driver):
+
+ def test_crud(self):
+ """
+ Basic CRUD: group
+ """
+ self.init_app()
+ self.basic_crud(group.ENTITY, group.DATA,
+ default_facet=group.DEFAULT_FACET)
+
+ def test_actions(self):
+ """
+ Test group actions
+ """
+
+ self.init_app()
+
+ self.add_record(group.ENTITY, group.DATA)
+ self.navigate_to_record(group.PKEY)
+ self.switch_to_facet('details')
+ self.make_posix_action()
+ self.delete_action()
+
+ self.add_record(group.ENTITY, group.DATA, navigate=False)
+ self.navigate_to_record(group.PKEY)
+ self.switch_to_facet('details')
+ self.facet_button_click('refresh')# workaround for BUG: #3702
+ self.make_external_action()
+ self.delete_action()
+
+ def make_external_action(self):
+ self.action_list_action('make_external')
+ self.wait_for_request(n=2)
+ self.assert_no_error_dialog()
+ self.assert_text_field('external', 'External', element='span')
+
+ def make_posix_action(self):
+ self.action_list_action('make_posix')
+ self.wait_for_request(n=2)
+ self.assert_no_error_dialog()
+ self.assert_text_field('external', 'POSIX', element='span')
+
+ def delete_action(self, entity=group.ENTITY, pkey=group.PKEY):
+ self.action_list_action('delete')
+ self.wait_for_request(n=4)
+ self.assert_no_error_dialog()
+ self.assert_facet(entity, 'search')
+ self.assert_record(pkey, negative=True)
+
+ def test_associations(self):
+ """
+ Test group associations
+ """
+ self.init_app()
+
+ # prepare
+ # -------
+ self.add_record(group.ENTITY, group.DATA)
+ self.add_record(group.ENTITY, group.DATA2, navigate=False)
+ self.add_record(group.ENTITY, group.DATA3, navigate=False)
+ self.add_record(user.ENTITY, user.DATA)
+ self.add_record(netgroup.ENTITY, netgroup.DATA)
+ self.add_record(rbac.ROLE_ENTITY, rbac.ROLE_DATA)
+ self.add_record(hbac.RULE_ENTITY, hbac.RULE_DATA)
+ self.add_record(sudo.RULE_ENTITY, sudo.RULE_DATA)
+
+ # add & remove associations
+ # -------------------------
+ self.navigate_to_record(group.PKEY, entity=group.ENTITY)
+
+ # members
+ self.add_associations([group.PKEY2], facet='member_group', delete=True)
+ self.add_associations([user.PKEY], facet='member_user', delete=True)
+ # TODO: external
+
+ # member of
+ self.add_associations([group.PKEY3], facet='memberof_group', delete=True)
+ self.add_associations([netgroup.PKEY], facet='memberof_netgroup', delete=True)
+ self.add_associations([rbac.ROLE_PKEY], facet='memberof_role', delete=True)
+ self.add_associations([hbac.RULE_PKEY], facet='memberof_hbacrule', delete=True)
+ self.navigate_to_record(group.PKEY, entity=group.ENTITY)
+ self.add_associations([sudo.RULE_PKEY], facet='memberof_sudorule', delete=True)
+
+ # cleanup
+ # -------
+ self.delete(group.ENTITY, [group.DATA, group.DATA2, group.DATA3])
+ self.delete(user.ENTITY, [user.DATA])
+ self.delete(netgroup.ENTITY, [netgroup.DATA])
+ self.delete(rbac.ROLE_ENTITY, [rbac.ROLE_DATA])
+ self.delete(hbac.RULE_ENTITY, [hbac.RULE_DATA])
+ self.delete(sudo.RULE_ENTITY, [sudo.RULE_DATA])
+
+ def test_indirect_associations(self):
+ """
+ Group indirect associations
+ """
+ self.init_app()
+
+ # add
+ # ---
+ self.add_record(group.ENTITY, group.DATA)
+ self.add_record(group.ENTITY, group.DATA2, navigate=False)
+ self.add_record(group.ENTITY, group.DATA3, navigate=False)
+ self.add_record(group.ENTITY, group.DATA4, navigate=False)
+ self.add_record(group.ENTITY, group.DATA5, navigate=False)
+ self.add_record(user.ENTITY, user.DATA)
+
+ # prepare indirect member
+ self.navigate_to_entity(group.ENTITY, 'search')
+ self.navigate_to_record(group.PKEY2)
+ self.add_associations([user.PKEY])
+ self.add_associations([group.PKEY3], 'member_group')
+
+ self.navigate_to_entity(group.ENTITY, 'search')
+ self.navigate_to_record(group.PKEY)
+ self.add_associations([group.PKEY2], 'member_group')
+
+ # prepare indirect memberof
+ self.navigate_to_entity(group.ENTITY, 'search')
+ self.navigate_to_record(group.PKEY4)
+ self.add_associations([group.PKEY], 'member_group')
+ self.add_associations([group.PKEY5], 'memberof_group')
+
+
+ self.add_record(netgroup.ENTITY, netgroup.DATA)
+ self.navigate_to_record(netgroup.PKEY)
+ self.add_table_associations('memberuser_group', [group.PKEY4])
+
+ self.add_record(rbac.ROLE_ENTITY, rbac.ROLE_DATA)
+ self.navigate_to_record(rbac.ROLE_PKEY)
+ self.add_associations([group.PKEY4], facet='member_group')
+
+ self.add_record(hbac.RULE_ENTITY, hbac.RULE_DATA)
+ self.navigate_to_record(hbac.RULE_PKEY)
+ self.add_table_associations('memberuser_group', [group.PKEY4])
+
+ self.add_record(sudo.RULE_ENTITY, sudo.RULE_DATA)
+ self.navigate_to_record(sudo.RULE_PKEY)
+ self.add_table_associations('memberuser_group', [group.PKEY4])
+
+ # check indirect associations
+ # ---------------------------
+ self.navigate_to_entity(group.ENTITY, 'search')
+ self.navigate_to_record(group.PKEY)
+
+ self.assert_indirect_record(user.PKEY, group.ENTITY, 'member_user')
+ self.assert_indirect_record(group.PKEY3, group.ENTITY, 'member_group')
+
+ self.assert_indirect_record(group.PKEY5, group.ENTITY, 'memberof_group')
+ self.assert_indirect_record(netgroup.PKEY, group.ENTITY, 'memberof_netgroup')
+ self.assert_indirect_record(rbac.ROLE_PKEY, group.ENTITY, 'memberof_role')
+ self.assert_indirect_record(hbac.RULE_PKEY, group.ENTITY, 'memberof_hbacrule')
+ self.assert_indirect_record(sudo.RULE_PKEY, group.ENTITY, 'memberof_sudorule')
+
+ ## cleanup
+ ## -------
+ self.delete(group.ENTITY, [group.DATA, group.DATA2, group.DATA3, group.DATA4, group.DATA5])
+ self.delete(user.ENTITY, [user.DATA])
+ self.delete(netgroup.ENTITY, [netgroup.DATA])
+ self.delete(rbac.ROLE_ENTITY, [rbac.ROLE_DATA])
+ self.delete(hbac.RULE_ENTITY, [hbac.RULE_DATA])
+ self.delete(sudo.RULE_ENTITY, [sudo.RULE_DATA])
diff --git a/ipatests/test_webui/test_hbac.py b/ipatests/test_webui/test_hbac.py
new file mode 100644
index 000000000..cfc28696c
--- /dev/null
+++ b/ipatests/test_webui/test_hbac.py
@@ -0,0 +1,159 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+HBAC tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+import ipatests.test_webui.data_hbac as hbac
+import ipatests.test_webui.data_hostgroup as hostgroup
+
+
+class test_hbac(UI_driver):
+
+ def test_crud(self):
+ """
+ Basic CRUD: hbac
+ """
+ self.init_app()
+ self.basic_crud(hbac.RULE_ENTITY, hbac.RULE_DATA)
+ self.basic_crud(hbac.SVC_ENTITY, hbac.SVC_DATA)
+ self.basic_crud(hbac.SVCGROUP_ENTITY, hbac.SVCGROUP_DATA,
+ default_facet=hbac.SVCGROUP_DEF_FACET)
+
+ def test_mod(self):
+ """
+ Mod: hbac
+ """
+ self.init_app()
+ host_key = self.config.get('ipa_server').strip()
+
+ self.add_record(hostgroup.ENTITY, hostgroup.DATA)
+ self.add_record(hbac.RULE_ENTITY, hbac.RULE_DATA)
+
+ self.navigate_to_record(hbac.RULE_PKEY)
+
+ tables = [
+ ['memberuser_user', ['admin'],],
+ ['memberuser_group', ['editors'],],
+ ['memberhost_host', [host_key],],
+ ['memberhost_hostgroup', [hostgroup.PKEY],],
+ ['memberservice_hbacsvc', ['ftp'],],
+ ['memberservice_hbacsvcgroup', ['Sudo'],],
+ ]
+
+ categories = [
+ 'usercategory',
+ 'hostcategory',
+ 'servicecategory',
+ ]
+
+ self.mod_rule_tables(tables, categories, [])
+
+ # cleanup
+ # -------
+ self.delete(hbac.RULE_ENTITY, [hbac.RULE_DATA])
+ self.delete(hostgroup.ENTITY, [hostgroup.DATA])
+
+ def test_actions(self):
+ """
+ Test hbac rule actions
+ """
+ self.init_app()
+
+ self.add_record(hbac.RULE_ENTITY, hbac.RULE_DATA)
+ self.navigate_to_record(hbac.RULE_PKEY)
+
+ self.disable_action()
+ self.enable_action()
+ self.delete_action(hbac.RULE_ENTITY, hbac.RULE_PKEY)
+
+ def test_hbac_test(self):
+ """
+ Test HBAC test UI
+
+ Test:
+ * basic functionality
+ * navigation by next/prev buttons
+ * navigation by facet tabs
+ * resetting test
+ """
+
+ self.init_app()
+ host_key = self.config.get('ipa_server').strip()
+
+ self.navigate_to_entity('hbactest', 'user')
+ self.assert_facet('hbactest', 'user')
+ self.select_record('admin')
+ self.button_click('next')
+
+ self.wait_for_request(n=2)
+ self.assert_facet('hbactest', 'targethost')
+ self.select_record(host_key)
+ self.button_click('prev')
+ self.assert_facet('hbactest', 'user')
+ self.switch_to_facet('targethost')
+ self.button_click('next')
+
+ self.wait_for_request(n=2)
+ self.assert_facet('hbactest', 'service')
+ self.select_record('ftp')
+ self.button_click('prev')
+ self.assert_facet('hbactest', 'targethost')
+ self.switch_to_facet('service')
+ self.button_click('next')
+
+ self.wait_for_request(n=2)
+ self.assert_facet('hbactest', 'rules')
+ self.select_record('allow_all')
+ self.button_click('prev')
+ self.assert_facet('hbactest', 'service')
+ self.switch_to_facet('rules')
+ self.button_click('next')
+
+ self.wait_for_request(n=2)
+ self.assert_facet('hbactest', 'run_test')
+ self.button_click('run_test')
+ self.wait_for_request(n=2)
+ self.assert_text("div.hbac-test-result-panel p", 'Access Granted'.upper())
+ self.button_click('prev')
+ self.assert_facet('hbactest', 'rules')
+ self.switch_to_facet('run_test')
+ self.button_click('new_test')
+ self.assert_facet('hbactest', 'user')
+
+ # test pre-run validation and navigation to related facet
+ self.switch_to_facet('run_test')
+ self.button_click('run_test')
+ self.assert_dialog('message_dialog')
+ self.click_on_link('User name')
+ self.assert_facet('hbactest', 'user')
+
+ self.switch_to_facet('run_test')
+ self.button_click('run_test')
+ self.assert_dialog('message_dialog')
+ self.click_on_link('Target host')
+ self.assert_facet('hbactest', 'targethost')
+
+ self.switch_to_facet('run_test')
+ self.button_click('run_test')
+ self.assert_dialog('message_dialog')
+ self.click_on_link('Service')
+ self.assert_facet('hbactest', 'service')
diff --git a/ipatests/test_webui/test_host.py b/ipatests/test_webui/test_host.py
new file mode 100644
index 000000000..a3938fdbf
--- /dev/null
+++ b/ipatests/test_webui/test_host.py
@@ -0,0 +1,269 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Host tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+import ipatests.test_webui.data_hostgroup as hostgroup
+import ipatests.test_webui.data_netgroup as netgroup
+import ipatests.test_webui.data_hbac as hbac
+import ipatests.test_webui.test_rbac as rbac
+import ipatests.test_webui.data_sudo as sudo
+
+ENTITY = 'host'
+
+class host_tasks(UI_driver):
+
+ def __init__(self, *args, **kwargs):
+ super(host_tasks, self).__init__(args, kwargs)
+ self.prep_data()
+ self.prep_data2()
+
+ def prep_data(self):
+ host = 'itest'
+ domain = self.config.get('ipa_domain')
+ ip = self.get_ip()
+ self.data = self.get_data(host, domain, ip)
+ self.pkey = self.data['pkey']
+ return self.data
+
+ def prep_data2(self):
+ host = 'itest2'
+ domain = self.config.get('ipa_domain')
+ self.data2 = self.get_data(host, domain)
+ self.pkey2 = self.data2['pkey']
+ return self.data2
+
+ def get_data(self, host, domain, ip=None):
+ if self.has_dns():
+ add_data = [
+ ('textbox', 'hostname', host),
+ ('combobox', 'dnszone', domain),
+ ]
+ if ip:
+ add_data.append(('textbox', 'ip_address', ip))
+ add_data.append(('checkbox', 'force', ''))
+ del_data = [
+ ('checkbox', 'updatedns', '')
+ ]
+ else:
+ add_data = [
+ ('textbox', 'fqdn', '%s.%s' % (host, domain)),
+ ('checkbox', 'force', ''),
+ ]
+ del_data = None
+
+ data = {
+ 'pkey': '%s.%s' % (host, domain),
+ 'add': add_data,
+ 'mod': [
+ ('textarea', 'description','Desc'),
+ ],
+ 'del': del_data,
+ }
+
+ return data
+
+ def get_ip(self):
+ """
+ Get next IP
+ """
+ ip = self.config.get('ipa_ip')
+ if not ip:
+ self.skip('FreeIPA Server IP address not configured')
+ ip = ip.split('.')
+ last = int(ip.pop())
+ ip.append(str(last+1))
+ return '.'.join(ip)
+
+ def load_csr(self, path):
+ # ENHANCEMENT: generate csr dynamically
+ with open(path, 'r') as csr_file:
+ csr = csr_file.read()
+ return csr
+
+
+class test_host(host_tasks):
+
+ def test_crud(self):
+ """
+ Basic CRUD: host
+ """
+ self.init_app()
+ self.basic_crud(ENTITY, self.data)
+
+ def test_certificates(self):
+ """
+ Test host certificate actions
+
+ Requires to have CA installed and 'host_csr_path' configuration option
+ set.
+ """
+
+ if not self.has_ca():
+ self.skip('CA is not configured')
+
+ csr_path = self.config.get('host_csr_path')
+ if not csr_path:
+ self.skip('CSR file is not configured')
+
+ self.init_app()
+ csr = self.load_csr(csr_path)
+ panel = 'cert_actions'
+ realm = self.config.get('ipa_realm')
+
+ self.add_record(ENTITY, self.data)
+ self.navigate_to_record(self.pkey)
+
+ self.assert_visible("div[name='certificate-missing']")
+
+ # cert request
+ self.action_panel_action(panel, 'request_cert')
+ self.fill_text('textarea.certificate', csr)
+ self.dialog_button_click('issue')
+ self.wait_for_request(n=2,d=0.5)
+ self.assert_visible("div[name='certificate-valid']")
+
+ # cert view
+ self.action_panel_action(panel, 'view_cert')
+ self.wait()
+ self.assert_text("tbody tr:nth-child(2) td:nth-child(2)", self.pkey)
+ self.assert_text("tbody tr:nth-child(3) td:nth-child(2)", realm)
+ self.dialog_button_click('close')
+
+ # cert get
+ self.action_panel_action(panel, 'get_cert')
+ self.wait()
+ # We don't know the cert text, so at least open and close the dialog
+ self.dialog_button_click('close')
+
+ ## cert revoke
+ self.action_panel_action(panel, 'revoke_cert')
+ self.wait()
+ self.select('select', '6')
+ self.dialog_button_click('ok')
+ self.wait_for_request(n=2)
+ self.assert_visible("div[name='certificate-revoked']")
+
+ ## cert restore
+ self.action_panel_action(panel, 'restore_cert')
+ self.wait()
+ self.dialog_button_click('ok')
+ self.wait_for_request(n=2)
+ self.assert_visible("div[name='certificate-valid']")
+
+ # cleanup
+ self.navigate_to_entity(ENTITY, 'search')
+ self.delete_record(self.pkey, self.data.get('del'))
+
+ def test_associations(self):
+ """
+ Host direct associations
+ """
+
+ self.init_app()
+
+ # prepare
+ # -------
+ self.add_record(ENTITY, self.data)
+ self.add_record(ENTITY, self.data2, navigate=False)
+ self.add_record(hostgroup.ENTITY, hostgroup.DATA)
+ self.add_record(netgroup.ENTITY, netgroup.DATA)
+ self.add_record(rbac.ROLE_ENTITY, rbac.ROLE_DATA)
+ self.add_record(hbac.RULE_ENTITY, hbac.RULE_DATA)
+ self.add_record(sudo.RULE_ENTITY, sudo.RULE_DATA)
+
+ # add & remove associations
+ # -------------------------
+ self.navigate_to_entity(ENTITY)
+ self.navigate_to_record(self.pkey)
+
+ self.add_associations([hostgroup.PKEY], facet='memberof_hostgroup', delete=True)
+ self.add_associations([netgroup.PKEY], facet='memberof_netgroup', delete=True)
+ self.add_associations([rbac.ROLE_PKEY], facet='memberof_role', delete=True)
+ self.add_associations([hbac.RULE_PKEY], facet='memberof_hbacrule', delete=True)
+ self.add_associations([sudo.RULE_PKEY], facet='memberof_sudorule', delete=True)
+ self.add_associations([self.pkey2], facet='managedby_host', delete=True)
+
+ # cleanup
+ # -------
+ self.delete(ENTITY, [self.data, self.data2])
+ self.delete(hostgroup.ENTITY, [hostgroup.DATA])
+ self.delete(netgroup.ENTITY, [netgroup.DATA])
+ self.delete(rbac.ROLE_ENTITY, [rbac.ROLE_DATA])
+ self.delete(hbac.RULE_ENTITY, [hbac.RULE_DATA])
+ self.delete(sudo.RULE_ENTITY, [sudo.RULE_DATA])
+
+ def test_indirect_associations(self):
+ """
+ Host indirect associations
+ """
+ self.init_app()
+
+ # add
+ # ---
+ self.add_record(ENTITY, self.data)
+
+ self.add_record(hostgroup.ENTITY, hostgroup.DATA)
+ self.navigate_to_record(hostgroup.PKEY)
+ self.add_associations([self.pkey])
+
+ self.add_record(hostgroup.ENTITY, hostgroup.DATA2)
+ self.navigate_to_record(hostgroup.PKEY2)
+ self.switch_to_facet('member_hostgroup')
+ self.add_associations([hostgroup.PKEY])
+
+ self.add_record(netgroup.ENTITY, netgroup.DATA)
+ self.navigate_to_record(netgroup.PKEY)
+ self.add_table_associations('memberhost_hostgroup', [hostgroup.PKEY2])
+
+ self.add_record(rbac.ROLE_ENTITY, rbac.ROLE_DATA)
+ self.navigate_to_record(rbac.ROLE_PKEY)
+ self.switch_to_facet('member_hostgroup')
+ self.add_associations([hostgroup.PKEY2])
+
+ self.add_record(hbac.RULE_ENTITY, hbac.RULE_DATA)
+ self.navigate_to_record(hbac.RULE_PKEY)
+ self.add_table_associations('memberhost_hostgroup', [hostgroup.PKEY2])
+
+ self.add_record(sudo.RULE_ENTITY, sudo.RULE_DATA)
+ self.navigate_to_record(sudo.RULE_PKEY)
+ self.add_table_associations('memberhost_hostgroup', [hostgroup.PKEY2])
+
+ # check indirect associations
+ # ---------------------------
+ self.navigate_to_entity(ENTITY, 'search')
+ self.navigate_to_record(self.pkey)
+
+ self.assert_indirect_record(hostgroup.PKEY2, ENTITY, 'memberof_hostgroup')
+ self.assert_indirect_record(netgroup.PKEY, ENTITY, 'memberof_netgroup')
+ self.assert_indirect_record(rbac.ROLE_PKEY, ENTITY, 'memberof_role')
+ self.assert_indirect_record(hbac.RULE_PKEY, ENTITY, 'memberof_hbacrule')
+ self.assert_indirect_record(sudo.RULE_PKEY, ENTITY, 'memberof_sudorule')
+
+ ## cleanup
+ ## -------
+ self.delete(ENTITY, [self.data])
+ self.delete(hostgroup.ENTITY, [hostgroup.DATA, hostgroup.DATA2])
+ self.delete(netgroup.ENTITY, [netgroup.DATA])
+ self.delete(rbac.ROLE_ENTITY, [rbac.ROLE_DATA])
+ self.delete(hbac.RULE_ENTITY, [hbac.RULE_DATA])
+ self.delete(sudo.RULE_ENTITY, [sudo.RULE_DATA])
diff --git a/ipatests/test_webui/test_hostgroup.py b/ipatests/test_webui/test_hostgroup.py
new file mode 100644
index 000000000..6bd239f18
--- /dev/null
+++ b/ipatests/test_webui/test_hostgroup.py
@@ -0,0 +1,141 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Hostgroup tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+import ipatests.test_webui.data_hostgroup as hostgroup
+from ipatests.test_webui.test_host import host_tasks, ENTITY as HOST_ENTITY
+import ipatests.test_webui.data_netgroup as netgroup
+import ipatests.test_webui.data_hbac as hbac
+import ipatests.test_webui.test_rbac as rbac
+import ipatests.test_webui.data_sudo as sudo
+
+class test_hostgroup(UI_driver):
+
+ def test_crud(self):
+ """
+ Basic CRUD: hostgroup
+ """
+ self.init_app()
+ self.basic_crud(hostgroup.ENTITY, hostgroup.DATA,
+ default_facet=hostgroup.DEFAULT_FACET)
+
+ def test_associations(self):
+ """
+ Hostgroup associations
+ """
+ self.init_app()
+ host = host_tasks(self.driver, self.config)
+
+ # prepare
+ # -------
+ self.add_record(hostgroup.ENTITY, hostgroup.DATA)
+ self.add_record(hostgroup.ENTITY, hostgroup.DATA2, navigate=False)
+ self.add_record(hostgroup.ENTITY, hostgroup.DATA3, navigate=False)
+ self.add_record(HOST_ENTITY, host.data2)
+ self.add_record(netgroup.ENTITY, netgroup.DATA)
+ self.add_record(hbac.RULE_ENTITY, hbac.RULE_DATA)
+ self.add_record(sudo.RULE_ENTITY, sudo.RULE_DATA)
+
+ # add & remove associations
+ # -------------------------
+ self.navigate_to_entity(hostgroup.ENTITY)
+ self.navigate_to_record(hostgroup.PKEY)
+
+ # members
+ self.add_associations([hostgroup.PKEY2], facet='member_hostgroup', delete=True)
+ self.add_associations([host.pkey2], facet='member_host', delete=True)
+
+ # member of
+ self.add_associations([hostgroup.PKEY3], facet='memberof_hostgroup', delete=True)
+ self.add_associations([netgroup.PKEY], facet='memberof_netgroup', delete=True)
+ self.add_associations([hbac.RULE_PKEY], facet='memberof_hbacrule', delete=True)
+ self.add_associations([sudo.RULE_PKEY], facet='memberof_sudorule', delete=True)
+
+ # cleanup
+ # -------
+ self.delete(hostgroup.ENTITY, [hostgroup.DATA, hostgroup.DATA2, hostgroup.DATA3])
+ self.delete(HOST_ENTITY, [host.data2])
+ self.delete(netgroup.ENTITY, [netgroup.DATA])
+ self.delete(hbac.RULE_ENTITY, [hbac.RULE_DATA])
+ self.delete(sudo.RULE_ENTITY, [sudo.RULE_DATA])
+
+ def test_indirect_associations(self):
+ """
+ Hostgroup indirect associations
+ """
+ self.init_app()
+ host = host_tasks(self.driver, self.config)
+
+ # add
+ # ---
+ self.add_record(hostgroup.ENTITY, hostgroup.DATA)
+ self.add_record(hostgroup.ENTITY, hostgroup.DATA2, navigate=False)
+ self.add_record(hostgroup.ENTITY, hostgroup.DATA3, navigate=False)
+ self.add_record(hostgroup.ENTITY, hostgroup.DATA4, navigate=False)
+ self.add_record(hostgroup.ENTITY, hostgroup.DATA5, navigate=False)
+ self.add_record(HOST_ENTITY, host.data2)
+
+ # prepare indirect member
+ self.navigate_to_entity(hostgroup.ENTITY, 'search')
+ self.navigate_to_record(hostgroup.PKEY2)
+ self.add_associations([host.pkey2])
+ self.add_associations([hostgroup.PKEY3], 'member_hostgroup')
+
+ self.navigate_to_entity(hostgroup.ENTITY, 'search')
+ self.navigate_to_record(hostgroup.PKEY)
+ self.add_associations([hostgroup.PKEY2], 'member_hostgroup')
+
+ # prepare indirect memberof
+ self.navigate_to_entity(hostgroup.ENTITY, 'search')
+ self.navigate_to_record(hostgroup.PKEY4)
+ self.add_associations([hostgroup.PKEY], 'member_hostgroup')
+ self.add_associations([hostgroup.PKEY5], 'memberof_hostgroup')
+
+
+ self.add_record(hbac.RULE_ENTITY, hbac.RULE_DATA)
+ self.navigate_to_record(hbac.RULE_PKEY)
+ self.add_table_associations('memberhost_hostgroup', [hostgroup.PKEY4])
+
+ self.add_record(sudo.RULE_ENTITY, sudo.RULE_DATA)
+ self.navigate_to_record(sudo.RULE_PKEY)
+ self.add_table_associations('memberhost_hostgroup', [hostgroup.PKEY4])
+
+ # check indirect associations
+ # ---------------------------
+ self.navigate_to_entity(hostgroup.ENTITY, 'search')
+ self.navigate_to_record(hostgroup.PKEY)
+
+ self.assert_indirect_record(hostgroup.PKEY3, hostgroup.ENTITY, 'member_hostgroup')
+ self.assert_indirect_record(host.pkey2, hostgroup.ENTITY, 'member_host')
+
+ self.assert_indirect_record(hostgroup.PKEY5, hostgroup.ENTITY, 'memberof_hostgroup')
+ self.assert_indirect_record(hbac.RULE_PKEY, hostgroup.ENTITY, 'memberof_hbacrule')
+ self.assert_indirect_record(sudo.RULE_PKEY, hostgroup.ENTITY, 'memberof_sudorule')
+
+ ## cleanup
+ ## -------
+ self.delete(hostgroup.ENTITY, [hostgroup.DATA, hostgroup.DATA2,
+ hostgroup.DATA3, hostgroup.DATA4, hostgroup.DATA5])
+ self.delete(HOST_ENTITY, [host.data2])
+ self.delete(hbac.RULE_ENTITY, [hbac.RULE_DATA])
+ self.delete(sudo.RULE_ENTITY, [sudo.RULE_DATA]) \ No newline at end of file
diff --git a/ipatests/test_webui/test_krbtpolicy.py b/ipatests/test_webui/test_krbtpolicy.py
new file mode 100644
index 000000000..d6db13027
--- /dev/null
+++ b/ipatests/test_webui/test_krbtpolicy.py
@@ -0,0 +1,52 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Kerberos policy tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+
+ENTITY = 'krbtpolicy'
+
+DATA = {
+ 'mod': [
+ ('textbox', 'krbmaxrenewableage','599000'),
+ ('textbox', 'krbmaxticketlife','79800'),
+ ],
+}
+
+DATA2 = {
+ 'mod': [
+ ('textbox', 'krbmaxrenewableage','604800'),
+ ('textbox', 'krbmaxticketlife','86400'),
+ ],
+}
+
+class test_krbtpolicy(UI_driver):
+
+ def test_mod(self):
+ """
+ Kerberos policy mod test
+ """
+ self.init_app()
+ self.navigate_to_entity(ENTITY)
+
+ self.mod_record(ENTITY, DATA)
+ self.mod_record(ENTITY, DATA2)
diff --git a/ipatests/test_webui/test_navigation.py b/ipatests/test_webui/test_navigation.py
new file mode 100644
index 000000000..005cbfee3
--- /dev/null
+++ b/ipatests/test_webui/test_navigation.py
@@ -0,0 +1,149 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Basic ui tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+
+
+ENTITIES = [
+ 'group',
+ 'user',
+ 'host',
+ 'hostgroup',
+ 'netgroup',
+ 'service',
+ 'dnszone',
+ # TODO: dnsrecord
+ 'dnsconfig',
+ 'cert',
+ 'realmdomains',
+ 'hbacrule',
+ 'hbacsvc',
+ 'hbacsvcgroup',
+ 'hbactest',
+ 'sudorule',
+ 'sudocmd',
+ 'sudocmdgroup',
+ 'automountlocation',
+ # TODO: add nested maps, keys
+ 'pwpolicy',
+ 'krbtpolicy',
+ 'selinuxusermap',
+ 'automember',
+ # TODO: add different types
+ 'role',
+ 'privilege',
+ 'permission',
+ 'selfservice',
+ 'delegation',
+ 'idrange',
+ 'config',
+ # TODO: add conditional
+]
+
+class test_navigation(UI_driver):
+
+ def test_url_navigation(self):
+ """
+ Navigation test: direct url change
+ """
+
+ self.init_app()
+
+ unsupported = []
+ if not self.has_dns():
+ unsupported.extend([
+ 'dnszone',
+ 'dnsconfig',
+ ])
+ if not self.has_ca():
+ unsupported.append('cert')
+
+ entities = [e for e in ENTITIES if e not in unsupported]
+
+ for e in entities:
+ self.wait_for_request()
+ self.navigate_to_entity(e)
+ self.assert_facet(e)
+ url = self.get_url(e)
+ self.assert_e_url(url, e)
+
+ def test_menu_navigation(self):
+ """
+ Navigation test: menu items
+ """
+
+ self.init_app()
+
+ # don't start by users (default)
+ self.navigate_by_menu('identity/group', False)
+ self.navigate_by_menu('identity/user', False)
+ self.navigate_by_menu('identity/host', False)
+ self.navigate_by_menu('identity/hostgroup', False)
+ self.navigate_by_menu('identity/netgroup', False)
+ self.navigate_by_menu('identity/service', False)
+ if self.has_dns():
+ self.navigate_by_menu('identity/dns', False)
+ self.navigate_by_menu('identity/dns/dnsconfig', False)
+ self.navigate_by_menu('identity/dns/dnszone', False)
+ if self.has_ca():
+ self.navigate_by_menu('identity/cert', False)
+ self.navigate_by_menu('identity/realmdomains', False)
+ self.navigate_by_menu('policy')
+ self.navigate_by_menu('policy/hbac', False)
+ self.navigate_by_menu('policy/hbac/hbacsvc', False)
+ self.navigate_by_menu('policy/hbac/hbacrule', False)
+ self.navigate_by_menu('policy/hbac/hbacsvcgroup', False)
+ self.navigate_by_menu('policy/hbac/hbactest', False)
+ self.navigate_by_menu('policy/sudo', False)
+ self.navigate_by_menu('policy/sudo/sudorule', False)
+ self.navigate_by_menu('policy/sudo/sudocmd', False)
+ self.navigate_by_menu('policy/sudo/sudocmdgroup', False)
+ self.navigate_by_menu('policy/automount', False)
+ self.navigate_by_menu('policy/pwpolicy', False)
+ self.navigate_by_menu('policy/krbtpolicy', False)
+ self.navigate_by_menu('policy/selinuxusermap', False)
+ self.navigate_by_menu('policy/automember', False)
+ self.navigate_by_menu('policy/automember/amhostgroup', False)
+ self.navigate_by_menu('policy/automember/amgroup', False)
+ self.navigate_by_menu('ipaserver')
+ self.navigate_by_menu('ipaserver/rolebased', False)
+ self.navigate_by_menu('ipaserver/rolebased/privilege', False)
+ self.navigate_by_menu('ipaserver/rolebased/role', False)
+ self.navigate_by_menu('ipaserver/rolebased/permission', False)
+ self.navigate_by_menu('ipaserver/selfservice', False)
+ self.navigate_by_menu('ipaserver/delegation', False)
+ self.navigate_by_menu('ipaserver/idrange', False)
+ if self.has_trusts():
+ self.navigate_by_menu('ipaserver/trusts', False)
+ self.navigate_by_menu('ipaserver/trusts/trust', False)
+ self.navigate_by_menu('ipaserver/trusts/trustconfig', False)
+ self.navigate_by_menu('ipaserver/config', False)
+
+
+ def assert_e_url(self, url, e):
+ """
+ Assert correct url for entity
+ """
+ if not self.driver.current_url.startswith(url):
+ msg = 'Invalid url for: %s' % e
+ raise AssertionError(msg)
diff --git a/ipatests/test_webui/test_netgroup.py b/ipatests/test_webui/test_netgroup.py
new file mode 100644
index 000000000..31dfde677
--- /dev/null
+++ b/ipatests/test_webui/test_netgroup.py
@@ -0,0 +1,81 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Netgroup tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+import ipatests.test_webui.data_netgroup as netgroup
+import ipatests.test_webui.data_user as user
+import ipatests.test_webui.data_group as group
+import ipatests.test_webui.data_hostgroup as hostgroup
+from ipatests.test_webui.test_host import host_tasks, ENTITY as HOST_ENTITY
+
+
+class test_netgroup(UI_driver):
+
+ def test_crud(self):
+ """
+ Basic CRUD: netgroup
+ """
+ self.init_app()
+ self.basic_crud(netgroup.ENTITY, netgroup.DATA)
+
+ def test_mod(self):
+ """
+ Mod: netgroup
+ """
+ self.init_app()
+ host = host_tasks(self.driver, self.config)
+
+ self.add_record(netgroup.ENTITY, netgroup.DATA2)
+ self.add_record(user.ENTITY, user.DATA)
+ self.add_record(user.ENTITY, user.DATA2, navigate=False)
+ self.add_record(group.ENTITY, group.DATA)
+ self.add_record(group.ENTITY, group.DATA2, navigate=False)
+ self.add_record(HOST_ENTITY, host.data)
+ self.add_record(HOST_ENTITY, host.data2, navigate=False)
+ self.add_record(hostgroup.ENTITY, hostgroup.DATA)
+ self.add_record(hostgroup.ENTITY, hostgroup.DATA2, navigate=False)
+ self.add_record(netgroup.ENTITY, netgroup.DATA)
+
+ self.navigate_to_record(netgroup.PKEY, entity=netgroup.ENTITY)
+
+ tables = [
+ ['memberuser_user', [user.PKEY, user.PKEY2],],
+ ['memberuser_group', [group.PKEY, group.PKEY2],],
+ ['memberhost_host', [host.pkey, host.pkey2],],
+ ['memberhost_hostgroup', [hostgroup.PKEY, hostgroup.PKEY2],],
+ ]
+
+ categories = [
+ 'usercategory',
+ 'hostcategory',
+ ]
+
+ self.mod_rule_tables(tables, categories, [])
+
+ # cleanup
+ # -------
+ self.delete(netgroup.ENTITY, [netgroup.DATA, netgroup.DATA2])
+ self.delete(user.ENTITY, [user.DATA, user.DATA2])
+ self.delete(group.ENTITY, [group.DATA, group.DATA2])
+ self.delete(HOST_ENTITY, [host.data, host.data2])
+ self.delete(hostgroup.ENTITY, [hostgroup.DATA, hostgroup.DATA2])
diff --git a/ipatests/test_webui/test_pwpolicy.py b/ipatests/test_webui/test_pwpolicy.py
new file mode 100644
index 000000000..a319a29c0
--- /dev/null
+++ b/ipatests/test_webui/test_pwpolicy.py
@@ -0,0 +1,52 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Password policy tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+
+ENTITY = 'pwpolicy'
+DATA = {
+ 'pkey': 'admins',
+ 'add': [
+ ('combobox', 'cn', 'admins'),
+ ('textbox', 'cospriority', '364'),
+ ],
+ 'mod': [
+ ('textbox', 'krbmaxpwdlife','3000'),
+ ('textbox', 'krbminpwdlife','1'),
+ ('textbox', 'krbpwdhistorylength','0'),
+ ('textbox', 'krbpwdmindiffchars','2'),
+ ('textbox', 'krbpwdminlength','2'),
+ ('textbox', 'krbpwdmaxfailure','15'),
+ ('textbox', 'krbpwdfailurecountinterval','5'),
+ ('textbox', 'krbpwdlockoutduration','3600'),
+ ],
+}
+
+class test_pwpolicy(UI_driver):
+
+ def test_crud(self):
+ """
+ Basic CRUD: pwpolicy
+ """
+ self.init_app()
+ self.basic_crud(ENTITY, DATA)
diff --git a/ipatests/test_webui/test_range.py b/ipatests/test_webui/test_range.py
new file mode 100644
index 000000000..baa53cbaa
--- /dev/null
+++ b/ipatests/test_webui/test_range.py
@@ -0,0 +1,49 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+User tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+
+ENTITY = 'idrange'
+PKEY = 'itest-range'
+DATA = {
+ 'pkey': PKEY,
+ 'add': [
+ ('textbox', 'cn', PKEY),
+ ('textbox', 'ipabaseid', '900000'),
+ ('textbox', 'ipaidrangesize', '99999'),
+ ('textbox', 'ipabaserid', '10000'),
+ ('textbox', 'ipasecondarybaserid', '200000'),
+ ],
+ 'mod': [
+ ('textbox', 'ipaidrangesize', '100000'),
+ ],
+}
+
+class test_range(UI_driver):
+
+ def test_crud(self):
+ """
+ Basic CRUD: range
+ """
+ self.init_app()
+ self.basic_crud(ENTITY, DATA)
diff --git a/ipatests/test_webui/test_rbac.py b/ipatests/test_webui/test_rbac.py
new file mode 100644
index 000000000..8d8c1acb6
--- /dev/null
+++ b/ipatests/test_webui/test_rbac.py
@@ -0,0 +1,83 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+RBAC tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+
+ROLE_ENTITY = 'role'
+ROLE_DEF_FACET = 'member_user'
+ROLE_PKEY = 'AAtest_role'
+ROLE_DATA = {
+ 'pkey': ROLE_PKEY,
+ 'add': [
+ ('textbox', 'cn', ROLE_PKEY),
+ ('textarea', 'description','role desc'),
+ ],
+ 'mod': [
+ ('textarea', 'description','role desc mod'),
+ ],
+}
+
+PRIVILEGE_ENTITY = 'privilege'
+PRIVILEGE_DEF_FACET = 'memberof_permission'
+PRIVILEGE_PKEY = 'AAtest_privilege'
+PRIVILEGE_DATA = {
+ 'pkey': PRIVILEGE_PKEY,
+ 'add': [
+ ('textbox', 'cn', PRIVILEGE_PKEY),
+ ('textarea', 'description','privilege desc'),
+ ],
+ 'mod': [
+ ('textarea', 'description','privilege desc mod'),
+ ],
+}
+
+PERMISSION_ENTITY = 'permission'
+PERMISSION_PKEY = 'AAtest_perm'
+PERMISSION_DATA = {
+ 'pkey': PERMISSION_PKEY,
+ 'add': [
+ ('textbox', 'cn', PERMISSION_PKEY),
+ ('checkbox', 'permissions', 'write'),
+ ('textbox', 'filter', 'cn=user'),
+ ],
+ 'mod': [
+ ('textbox', 'filter','cn=user2'),
+ ],
+}
+
+class test_rbac(UI_driver):
+
+ def test_crud(self):
+ """
+ Basic CRUD: RBAC
+ """
+ self.init_app()
+ self.basic_crud(ROLE_ENTITY, ROLE_DATA,
+ default_facet=ROLE_DEF_FACET
+ )
+
+ self.basic_crud(PRIVILEGE_ENTITY, PRIVILEGE_DATA,
+ default_facet=PRIVILEGE_DEF_FACET
+ )
+
+ self.basic_crud(PERMISSION_ENTITY, PERMISSION_DATA)
diff --git a/ipatests/test_webui/test_realmdomains.py b/ipatests/test_webui/test_realmdomains.py
new file mode 100644
index 000000000..ea8984118
--- /dev/null
+++ b/ipatests/test_webui/test_realmdomains.py
@@ -0,0 +1,48 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Realm domains tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+
+ENTITY = 'realmdomains'
+
+class test_realmdomains(UI_driver):
+
+ def test_read(self):
+ """
+ Realm domains mod tests
+ """
+ self.init_app()
+ self.navigate_to_entity(ENTITY)
+
+ # add
+ self.add_multivalued('associateddomain', 'itest.bar')
+ self.facet_button_click('update')
+ self.dialog_button_click('force')
+ self.wait_for_request()
+
+ # delete
+ self.delete_multivalued('associateddomain', 'itest.bar')
+ self.facet_button_click('update')
+ self.dialog_button_click('force')
+ self.wait_for_request()
+ self.wait_for_request() \ No newline at end of file
diff --git a/ipatests/test_webui/test_selfservice.py b/ipatests/test_webui/test_selfservice.py
new file mode 100644
index 000000000..5e7c41910
--- /dev/null
+++ b/ipatests/test_webui/test_selfservice.py
@@ -0,0 +1,48 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Selfservice tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+
+ENTITY = 'selfservice'
+PKEY = 'itest-selfservice-rule'
+DATA = {
+ 'pkey': PKEY,
+ 'add': [
+ ('textbox', 'aciname', PKEY),
+ ('table', 'attrs', 'audio'),
+ ('table', 'attrs', 'businesscategory'),
+ ],
+ 'mod': [
+ ('table', 'attrs', 'businesscategory'),
+ ],
+}
+
+class test_selfservice(UI_driver):
+
+
+ def test_crud(self):
+ """
+ Basic CRUD: selfservice entity
+ """
+ self.init_app()
+ self.basic_crud(ENTITY, DATA)
diff --git a/ipatests/test_webui/test_selinuxusermap.py b/ipatests/test_webui/test_selinuxusermap.py
new file mode 100644
index 000000000..397005441
--- /dev/null
+++ b/ipatests/test_webui/test_selinuxusermap.py
@@ -0,0 +1,104 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+SELinux user map tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+import ipatests.test_webui.data_user as user
+import ipatests.test_webui.data_group as group
+import ipatests.test_webui.data_hostgroup as hostgroup
+from ipatests.test_webui.test_host import host_tasks, ENTITY as HOST_ENTITY
+
+ENTITY = 'selinuxusermap'
+PKEY='itest-selinuxusermap'
+DATA = {
+ 'pkey': PKEY,
+ 'add': [
+ ('textbox', 'cn', PKEY),
+ ('textbox', 'ipaselinuxuser','user_u:s0'),
+ ],
+ 'mod': [
+ ('textarea', 'description','itest-selinuxusermap desc'),
+ ],
+}
+
+class test_selinuxusermap(UI_driver):
+
+ def test_crud(self):
+ """
+ Basic CRUD: selinuxusermap
+ """
+ self.init_app()
+ self.basic_crud(ENTITY, DATA)
+
+ def test_mod(self):
+ """
+ Mod: selinuxusermap
+ """
+ self.init_app()
+ host = host_tasks(self.driver, self.config)
+
+ self.add_record(user.ENTITY, user.DATA)
+ self.add_record(user.ENTITY, user.DATA2, navigate=False)
+ self.add_record(group.ENTITY, group.DATA)
+ self.add_record(group.ENTITY, group.DATA2, navigate=False)
+ self.add_record(HOST_ENTITY, host.data)
+ self.add_record(HOST_ENTITY, host.data2, navigate=False)
+ self.add_record(hostgroup.ENTITY, hostgroup.DATA)
+ self.add_record(hostgroup.ENTITY, hostgroup.DATA2, navigate=False)
+ self.add_record(ENTITY, DATA)
+
+ self.navigate_to_record(PKEY)
+
+ tables = [
+ ['memberuser_user', [user.PKEY, user.PKEY2],],
+ ['memberuser_group', [group.PKEY, group.PKEY2],],
+ ['memberhost_host', [host.pkey, host.pkey2],],
+ ['memberhost_hostgroup', [hostgroup.PKEY, hostgroup.PKEY2],],
+ ]
+
+ categories = [
+ 'usercategory',
+ 'hostcategory',
+ ]
+
+ self.mod_rule_tables(tables, categories, [])
+
+ # cleanup
+ # -------
+ self.delete(ENTITY, [DATA])
+ self.delete(user.ENTITY, [user.DATA, user.DATA2])
+ self.delete(group.ENTITY, [group.DATA, group.DATA2])
+ self.delete(HOST_ENTITY, [host.data, host.data2])
+ self.delete(hostgroup.ENTITY, [hostgroup.DATA, hostgroup.DATA2])
+
+ def test_actions(self):
+ """
+ Test SELinux user map actions
+ """
+ self.init_app()
+
+ self.add_record(ENTITY, DATA)
+ self.navigate_to_record(PKEY)
+
+ self.disable_action()
+ self.enable_action()
+ self.delete_action(ENTITY, PKEY) \ No newline at end of file
diff --git a/ipatests/test_webui/test_service.py b/ipatests/test_webui/test_service.py
new file mode 100644
index 000000000..cd32cce36
--- /dev/null
+++ b/ipatests/test_webui/test_service.py
@@ -0,0 +1,129 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Service tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+
+ENTITY = 'service'
+
+class sevice_tasks(UI_driver):
+
+ def prep_data(self):
+
+ host = self.config.get('ipa_server')
+ realm = self.config.get('ipa_realm')
+ pkey = 'itest'
+
+ return {
+ 'pkey': '%s/%s@%s' % (pkey, host, realm),
+ 'add': [
+ ('textbox', 'service', pkey),
+ ('combobox', 'host', host)
+ ],
+ 'mod': [
+ ('checkbox', 'ipakrbokasdelegate',''),
+ ],
+ }
+
+ def load_csr(self, path):
+ # ENHANCEMENT: generate csr dynamically
+ with open(path, 'r') as csr_file:
+ csr = csr_file.read()
+ return csr
+
+
+class test_service(sevice_tasks):
+
+ def test_crud(self):
+ """
+ Basic CRUD: service
+ """
+ self.init_app()
+ data = self.prep_data()
+ self.basic_crud(ENTITY, data)
+
+ def test_certificates(self):
+ """
+ Test service certificate actions
+
+ Requires to have CA installed and 'service_csr_path' configuration option
+ set.
+ """
+
+ if not self.has_ca():
+ self.skip('CA is not configured')
+
+ csr_path = self.config.get('service_csr_path')
+ if not csr_path:
+ self.skip('CSR file is not configured')
+
+ self.init_app()
+ data = self.prep_data()
+ pkey = data.get('pkey')
+ csr = self.load_csr(csr_path)
+ panel = 'cert_actions'
+ host = self.config.get('ipa_server')
+ realm = self.config.get('ipa_realm')
+
+ self.add_record(ENTITY, data)
+ self.navigate_to_record(pkey)
+
+ self.assert_visible("div[name='certificate-missing']")
+
+ # cert request
+ self.action_panel_action(panel, 'request_cert')
+ self.fill_text('textarea.certificate', csr)
+ self.dialog_button_click('issue')
+ self.wait_for_request(n=2,d=0.5)
+ self.assert_visible("div[name='certificate-valid']")
+
+ # cert view
+ self.action_panel_action(panel, 'view_cert')
+ self.wait()
+ self.assert_text("tbody tr:nth-child(2) td:nth-child(2)", host)
+ self.assert_text("tbody tr:nth-child(3) td:nth-child(2)", realm)
+ self.dialog_button_click('close')
+
+ # cert get
+ self.action_panel_action(panel, 'get_cert')
+ self.wait()
+ # We don't know the cert text, so at least open and close the dialog
+ self.dialog_button_click('close')
+
+ ## cert revoke
+ self.action_panel_action(panel, 'revoke_cert')
+ self.wait()
+ self.select('select', '6')
+ self.dialog_button_click('ok')
+ self.wait_for_request(n=2)
+ self.assert_visible("div[name='certificate-revoked']")
+
+ ## cert restore
+ self.action_panel_action(panel, 'restore_cert')
+ self.wait()
+ self.dialog_button_click('ok')
+ self.wait_for_request(n=2)
+ self.assert_visible("div[name='certificate-valid']")
+
+ # cleanup
+ self.navigate_to_entity(ENTITY, 'search')
+ self.delete_record(pkey, data.get('del'))
diff --git a/ipatests/test_webui/test_sudo.py b/ipatests/test_webui/test_sudo.py
new file mode 100644
index 000000000..3e7b448e3
--- /dev/null
+++ b/ipatests/test_webui/test_sudo.py
@@ -0,0 +1,122 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Sudo tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+import ipatests.test_webui.data_sudo as sudo
+import ipatests.test_webui.data_netgroup as netgroup
+import ipatests.test_webui.data_user as user
+import ipatests.test_webui.data_group as group
+import ipatests.test_webui.data_hostgroup as hostgroup
+from ipatests.test_webui.test_host import host_tasks, ENTITY as HOST_ENTITY
+
+class test_sudo(UI_driver):
+
+ def test_crud(self):
+ """
+ Basic CRUD: sudo
+ """
+ self.init_app()
+ self.basic_crud(sudo.RULE_ENTITY, sudo.RULE_DATA)
+ self.basic_crud(sudo.CMDENTITY, sudo.CMD_DATA)
+ self.basic_crud(sudo.CMDGROUP_ENTITY, sudo.CMDGROUP_DATA,
+ default_facet=sudo.CMDGROUP_DEF_FACET)
+
+ def test_mod(self):
+ """
+ Mod: sudo
+ """
+ self.init_app()
+ host = host_tasks(self.driver, self.config)
+
+ self.add_record(netgroup.ENTITY, netgroup.DATA2)
+
+ self.add_record(user.ENTITY, user.DATA)
+ self.add_record(user.ENTITY, user.DATA2, navigate=False)
+
+ self.add_record(group.ENTITY, group.DATA)
+ self.add_record(group.ENTITY, group.DATA2, navigate=False)
+
+ self.add_record(HOST_ENTITY, host.data)
+ self.add_record(HOST_ENTITY, host.data2, navigate=False)
+
+ self.add_record(hostgroup.ENTITY, hostgroup.DATA)
+ self.add_record(hostgroup.ENTITY, hostgroup.DATA2, navigate=False)
+
+ self.add_record(sudo.CMDENTITY, sudo.CMD_DATA)
+ self.add_record(sudo.CMDENTITY, sudo.CMD_DATA2, navigate=False)
+
+ self.add_record(sudo.CMDGROUP_ENTITY, sudo.CMDGROUP_DATA)
+ self.add_record(sudo.CMDGROUP_ENTITY, sudo.CMDGROUP_DATA2, navigate=False)
+
+ self.add_record(sudo.RULE_ENTITY, sudo.RULE_DATA)
+ self.navigate_to_record(sudo.RULE_PKEY, entity=sudo.RULE_ENTITY)
+
+ tables = [
+ ['memberuser_user', [user.PKEY, user.PKEY2],],
+ ['memberuser_group', [group.PKEY, group.PKEY2],],
+ ['memberhost_host', [host.pkey, host.pkey2],],
+ ['memberhost_hostgroup', [hostgroup.PKEY, hostgroup.PKEY2],],
+ ['memberallowcmd_sudocmd', [sudo.CMD_PKEY, sudo.CMD_PKEY2],],
+ ['memberallowcmd_sudocmdgroup', [sudo.CMD_GROUP_PKEY, sudo.CMD_GROUP_PKEY2],],
+ ['memberdenycmd_sudocmd', [sudo.CMD_PKEY, sudo.CMD_PKEY2],],
+ ['memberdenycmd_sudocmdgroup', [sudo.CMD_GROUP_PKEY, sudo.CMD_GROUP_PKEY2],],
+ ['ipasudorunas_user', ['admin'],],
+ ['ipasudorunas_group', ['editors', 'admins'],],
+ ['ipasudorunasgroup_group', ['editors', 'admins'],],
+ ]
+
+ categories = [
+ 'usercategory',
+ 'hostcategory',
+ 'cmdcategory',
+ 'ipasudorunasusercategory',
+ 'ipasudorunasgroupcategory',
+ ]
+
+ no_cats = [
+ 'memberdenycmd_sudocmd',
+ 'memberdenycmd_sudocmdgroup',
+ ]
+
+ self.mod_rule_tables(tables, categories, no_cats)
+
+ # cleanup
+ # -------
+ self.delete(sudo.RULE_ENTITY, [sudo.RULE_DATA])
+ self.delete(user.ENTITY, [user.DATA, user.DATA2])
+ self.delete(group.ENTITY, [group.DATA, group.DATA2])
+ self.delete(HOST_ENTITY, [host.data, host.data2])
+ self.delete(hostgroup.ENTITY, [hostgroup.DATA, hostgroup.DATA2])
+
+ def test_actions(self):
+ """
+ Test sudo rule actions
+ """
+ self.init_app()
+
+ self.add_record(sudo.RULE_ENTITY, sudo.RULE_DATA)
+ self.navigate_to_record(sudo.RULE_PKEY)
+
+ self.disable_action()
+ self.enable_action()
+ self.delete_action(sudo.RULE_ENTITY, sudo.RULE_PKEY)
diff --git a/ipatests/test_webui/test_user.py b/ipatests/test_webui/test_user.py
new file mode 100644
index 000000000..ec1d1951e
--- /dev/null
+++ b/ipatests/test_webui/test_user.py
@@ -0,0 +1,159 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+User tests
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+import ipatests.test_webui.data_user as user
+import ipatests.test_webui.data_group as group
+import ipatests.test_webui.data_netgroup as netgroup
+import ipatests.test_webui.data_hbac as hbac
+import ipatests.test_webui.test_rbac as rbac
+import ipatests.test_webui.data_sudo as sudo
+
+class test_user(UI_driver):
+
+ def test_crud(self):
+ """
+ Basic CRUD: user
+ """
+ self.init_app()
+ self.basic_crud(user.ENTITY, user.DATA)
+
+ def test_associations(self):
+ """
+ User direct associations
+ """
+
+ self.init_app()
+
+ # prepare - add user, group, netgroup, role, hbac rule, sudo rule
+ # ---------------------------------------------------------------
+ self.add_record(user.ENTITY, user.DATA, navigate=False)
+ self.add_record(group.ENTITY, group.DATA)
+ self.add_record(netgroup.ENTITY, netgroup.DATA)
+ self.add_record(rbac.ROLE_ENTITY, rbac.ROLE_DATA)
+ self.add_record(hbac.RULE_ENTITY, hbac.RULE_DATA)
+ self.add_record(sudo.RULE_ENTITY, sudo.RULE_DATA)
+
+ # add & remove associations
+ # -------------------------
+ self.navigate_to_entity(user.ENTITY)
+ self.navigate_to_record(user.PKEY)
+
+ self.add_associations([group.PKEY, 'editors'], facet='memberof_group', delete=True)
+ self.add_associations([netgroup.PKEY], facet='memberof_netgroup', delete=True)
+ self.add_associations([rbac.ROLE_PKEY], facet='memberof_role', delete=True)
+ self.add_associations([hbac.RULE_PKEY], facet='memberof_hbacrule', delete=True)
+ self.add_associations([sudo.RULE_PKEY], facet='memberof_sudorule', delete=True)
+
+ # cleanup
+ # -------
+ self.delete(user.ENTITY, [user.DATA])
+ self.delete(group.ENTITY, [group.DATA])
+ self.delete(netgroup.ENTITY, [netgroup.DATA])
+ self.delete(rbac.ROLE_ENTITY, [rbac.ROLE_DATA])
+ self.delete(hbac.RULE_ENTITY, [hbac.RULE_DATA])
+ self.delete(sudo.RULE_ENTITY, [sudo.RULE_DATA])
+
+ def test_indirect_associations(self):
+ """
+ User indirect associations
+ """
+ self.init_app()
+
+ # add
+ # ---
+ self.add_record(user.ENTITY, user.DATA, navigate=False)
+
+ self.add_record(group.ENTITY, group.DATA)
+ self.navigate_to_record(group.PKEY)
+ self.add_associations([user.PKEY])
+
+ self.add_record(group.ENTITY, group.DATA2)
+ self.navigate_to_record(group.PKEY2)
+ self.add_associations([group.PKEY],facet='member_group')
+
+ self.add_record(netgroup.ENTITY, netgroup.DATA)
+ self.navigate_to_record(netgroup.PKEY)
+ self.add_table_associations('memberuser_group', [group.PKEY2])
+
+ self.add_record(rbac.ROLE_ENTITY, rbac.ROLE_DATA)
+ self.navigate_to_record(rbac.ROLE_PKEY)
+ self.add_associations([group.PKEY2], facet='member_group')
+
+ self.add_record(hbac.RULE_ENTITY, hbac.RULE_DATA)
+ self.navigate_to_record(hbac.RULE_PKEY)
+ self.add_table_associations('memberuser_group', [group.PKEY2])
+
+ self.add_record(sudo.RULE_ENTITY, sudo.RULE_DATA)
+ self.navigate_to_record(sudo.RULE_PKEY)
+ self.add_table_associations('memberuser_group', [group.PKEY2])
+
+ # check indirect associations
+ # ---------------------------
+ self.navigate_to_entity(user.ENTITY, 'search')
+ self.navigate_to_record(user.PKEY)
+
+ self.assert_indirect_record(group.PKEY2, user.ENTITY, 'memberof_group')
+ self.assert_indirect_record(netgroup.PKEY, user.ENTITY, 'memberof_netgroup')
+ self.assert_indirect_record(rbac.ROLE_PKEY, user.ENTITY, 'memberof_role')
+ self.assert_indirect_record(hbac.RULE_PKEY, user.ENTITY, 'memberof_hbacrule')
+ self.assert_indirect_record(sudo.RULE_PKEY, user.ENTITY, 'memberof_sudorule')
+
+ ## cleanup
+ ## -------
+ self.delete(user.ENTITY, [user.DATA])
+ self.delete(group.ENTITY, [group.DATA, group.DATA2])
+ self.delete(netgroup.ENTITY, [netgroup.DATA])
+ self.delete(rbac.ROLE_ENTITY, [rbac.ROLE_DATA])
+ self.delete(hbac.RULE_ENTITY, [hbac.RULE_DATA])
+ self.delete(sudo.RULE_ENTITY, [sudo.RULE_DATA])
+
+
+ def test_actions(self):
+ """
+ Test user actions
+ """
+ self.init_app()
+
+ self.add_record(user.ENTITY, user.DATA, navigate=False)
+ self.navigate_to_record(user.PKEY)
+
+ self.disable_action()
+ self.enable_action()
+
+ # reset password
+ pwd = self.config.get('ipa_password')
+ fields = [
+ ('password' ,'password1', pwd),
+ ('password' ,'password2', pwd),
+ ]
+ self.action_panel_action('account_actions', 'reset_password')
+ self.assert_dialog()
+ self.fill_fields(fields)
+ self.dialog_button_click('reset_password')
+ self.wait_for_request(n=2)
+ self.assert_no_error_dialog()
+ self.assert_text_field('has_password', '******')
+
+ # delete
+ self.delete_action(user.ENTITY, user.PKEY)
diff --git a/ipatests/test_webui/ui_driver.py b/ipatests/test_webui/ui_driver.py
new file mode 100644
index 000000000..e7551dea5
--- /dev/null
+++ b/ipatests/test_webui/ui_driver.py
@@ -0,0 +1,1325 @@
+# Authors:
+# Petr Vobornik <pvoborni@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Base class for UI integration tests.
+
+Contains browser driver and common tasks.
+"""
+
+import nose
+import time
+import re
+import os
+
+try:
+ from selenium import webdriver
+ from selenium.common.exceptions import NoSuchElementException
+ from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
+ from selenium.webdriver.common.keys import Keys
+ from selenium.webdriver.common.by import By
+ from selenium.webdriver.chrome.options import Options as ChromeOptions
+ from selenium.webdriver.support.wait import WebDriverWait
+ from selenium.webdriver.support.ui import Select
+ NO_SELENIUM = False
+except ImportError:
+ NO_SELENIUM = True
+try:
+ import yaml
+ NO_YAML = False
+except ImportError:
+ NO_YAML = True
+from urllib2 import URLError
+
+ENV_MAP = {
+ 'MASTER': 'ipa_server',
+ 'ADMINID': 'ipa_admin',
+ 'ADMINPW': 'ipa_password',
+ 'DOMAIN': 'ipa_domain',
+ 'IPA_REALM': 'ipa_realm',
+ 'IPA_IP': 'ipa_ip',
+ 'IPA_NO_CA': 'no_ca',
+ 'IPA_NO_DNS': 'no_dns',
+ 'IPA_HAS_TRUSTS': 'has_trusts',
+ 'IPA_HOST_CSR_PATH': 'host_csr_path',
+ 'IPA_SERVICE_CSR_PATH': 'service_csr_path',
+ 'AD_DOMAIN': 'ad_domain',
+ 'AD_DC': 'ad_dc',
+ 'AD_ADMIN': 'ad_admin',
+ 'AD_PASSWORD': 'ad_password',
+ 'AD_DC_IP': 'ad_dc_ip',
+ 'TRUST_SECRET': 'trust_secret',
+ 'SEL_TYPE': 'type',
+ 'SEL_BROWSER': 'browser',
+ 'SEL_HOST': 'host',
+ 'FF_PROFILE': 'ff_profile',
+}
+
+DEFAULT_BROWSER = 'firefox'
+DEFAULT_PORT = 4444
+DEFAULT_TYPE = 'local'
+
+class UI_driver(object):
+ """
+ Base class for all UI integration tests
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ if NO_SELENIUM:
+ raise nose.SkipTest('Selenium not installed')
+
+ def __init__(self, driver=None, config=None):
+ self.driver = driver
+ self.config = config
+ if not config:
+ self.load_config()
+
+ def load_config(self):
+ """
+ Load configuration
+
+ 1) From ~/.ipa/ui_test.conf
+ 2) From environmental variables
+ """
+
+ # load config file
+ path = os.path.join(os.path.expanduser("~"), ".ipa/ui_test.conf")
+ if not NO_YAML and os.path.isfile(path):
+ try:
+ with open(path, 'r') as conf:
+ self.config = yaml.load(conf)
+ except yaml.YAMLError, e:
+ raise nose.SkipTest("Invalid Web UI config.\n%s" % e)
+ except IOError, e:
+ raise nose.SkipTest("Can't load Web UI test config: %s" %e)
+ else:
+ self.config = {}
+
+ c = self.config
+
+ # override with environmental variables
+ for k,v in ENV_MAP.iteritems():
+ val = os.environ.get(k)
+ if val is not None:
+ c[v] = val
+
+ # apply defaults
+ if 'port' not in c:
+ c['port'] = DEFAULT_PORT
+ if 'browser' not in c:
+ c['browser'] = DEFAULT_BROWSER
+ if 'type' not in c:
+ c['type'] = DEFAULT_TYPE
+
+
+ def setUp(self):
+ """
+ Test setup
+ """
+ if not self.driver:
+ self.driver = self.get_driver()
+
+ def tearDown(self):
+ """
+ Test clean up
+ """
+ self.driver.quit()
+
+ def get_driver(self):
+ """
+ Get WebDriver according to configuration
+ """
+ browser = self.config["browser"]
+ port = self.config["port"]
+ type = self.config["type"]
+
+ options = None
+
+ if browser == 'chromium':
+ options = ChromeOptions()
+ options.binary_location = '/usr/bin/chromium-browser'
+
+ if type == 'remote':
+ if not 'host' in self.config:
+ raise nose.SkipTest('Selenium server host not configured')
+ host = self.config["host"]
+
+ if browser == 'chrome':
+ capabilities = DesiredCapabilities.CHROME
+ elif browser == 'chromium':
+ capabilities = options.to_capabilities()
+ elif browser == 'ie':
+ capabilities = DesiredCapabilities.INTERNETEXPLORER
+ else:
+ capabilities = DesiredCapabilities.FIREFOX
+ try:
+ driver = webdriver.Remote(
+ command_executor='http://%s:%d/wd/hub' % (host, port),
+ desired_capabilities=capabilities)
+ except URLError, e:
+ raise nose.SkipTest('Error connecting to selenium server: %s' % e)
+ except RuntimeError, e:
+ raise nose.SkipTest('Error while establishing webdriver: %s' % e)
+ else:
+ try:
+ if browser == 'chrome' or browser == 'chromium':
+ driver = webdriver.Chrome(chrome_options=options)
+ elif browser == 'ie':
+ driver = webdriver.Ie()
+ else:
+ fp = None
+ if "ff_profile" in self.config:
+ fp = webdriver.FirefoxProfile(self.config["ff_profile"])
+ driver = webdriver.Firefox(fp)
+ except URLError, e:
+ raise nose.SkipTest('Error connecting to selenium server: %s' % e)
+ except RuntimeError, e:
+ raise nose.SkipTest('Error while establishing webdriver: %s' % e)
+
+ return driver
+
+ def find(self, expression, by='id', context=None, all=False, strict=False):
+ """
+ Helper which calls selenium find_element_by_xxx methods.
+
+ expression: search expression
+ by: selenium.webdriver.common.by
+ context: element to search on. Default: driver
+ all: return first or array of all matching elements
+ strict: error out when element is not found
+
+ Returns None instead of raising exception when element is not found.
+ """
+
+ assert expression, 'expression is missing'
+
+ if context is None:
+ context = self.driver
+
+ if not all:
+ method_name = 'find_element'
+ else:
+ method_name = 'find_elements'
+
+ try:
+ func = getattr(context, method_name)
+ result = func(by, expression)
+ except NoSuchElementException:
+ if strict:
+ raise
+ else:
+ result = None
+
+ return result
+
+ def files_loaded(self):
+ indicator = self.find("span.network-activity-indicator", By.CSS_SELECTOR)
+ return indicator is not None
+
+ def has_ca(self):
+ return 'no_ca' not in self.config
+
+ def has_dns(self):
+ return 'no_dns' not in self.config
+
+ def has_trusts(self):
+ return 'has_trusts' in self.config
+
+ def has_active_request(self):
+ """
+ Check if there is running AJAX request
+ """
+ indicator = self.find("span.network-activity-indicator", By.CSS_SELECTOR)
+ displayed = indicator and indicator.is_displayed()
+ return displayed
+
+ def wait(self, seconds=0.2):
+ time.sleep(seconds)
+
+ def wait_for_request(self, implicit=0.2, n=1, d=0):
+ """
+ Wait for AJAX request to finish
+ """
+ runner = self
+
+ for i in range(n):
+ self.wait(implicit)
+ WebDriverWait(self.driver, 10).until_not(lambda d: runner.has_active_request())
+ self.wait()
+ self.wait(d)
+
+ def xpath_has_val(self, attr, val):
+ """
+ Create xpath expression for matching a presence of item in attribute
+ value where value is a list of items separated by space.
+ """
+ return "contains(concat(' ',normalize-space(@%s), ' '),' %s ')" % (attr, val)
+
+
+ def init_app(self):
+ """
+ Load and login
+ """
+ self.load()
+ self.wait(0.5)
+ self.login()
+ # metadata + default page
+ self.wait_for_request(n=5)
+
+ def load(self):
+ self.driver.get(self.get_base_url())
+ runner = self
+ WebDriverWait(self.driver, 10).until(lambda d: runner.files_loaded())
+
+ def login(self):
+ """
+ Log in if user is not logged in.
+ """
+ self.wait_for_request(n=2)
+ if not self.logged_in():
+ auth = self.get_auth_dialog()
+ login_tb = self.find("//input[@type='text'][@name='username']", 'xpath', auth, strict=True)
+ psw_tb = self.find("//input[@type='password'][@name='password']", 'xpath', auth, strict=True)
+ login_tb.send_keys(self.config['ipa_admin'])
+ psw_tb.send_keys(self.config['ipa_password'])
+ psw_tb.send_keys(Keys.RETURN)
+ self.wait(0.5)
+ self.wait_for_request()
+
+ def logged_in(self):
+ """
+ Check if user is logged in
+ """
+ login_as = self.find('header-loggedinas', 'class name')
+ visible_name = login_as and login_as.is_displayed()
+ logged_in = not self.auth_dialog_opened() and visible_name
+ return logged_in
+
+ def get_auth_dialog(self):
+ """
+ Return reference to authentication dialog
+ """
+ return self.find('unauthorized_dialog', 'id')
+
+ def auth_dialog_opened(self):
+ """
+ Check if authentication dialog is opened
+ """
+ dialog = self.get_auth_dialog()
+ return dialog and dialog.is_displayed()
+
+ def navigate_to_entity(self, entity, facet=None):
+ self.driver.get(self.get_url(entity, facet))
+ self.wait_for_request(n=3,d=0.4)
+
+ def navigate_by_menu(self, item, complete=True):
+ """
+ Navigate by using menu
+ """
+
+ if complete:
+ parts = item.split('/')
+ if len(parts) > 1:
+ parent = parts[0:-1]
+ self.navigate_by_menu('/'.join(parent), complete)
+
+ s = ".navigation a[href='#%s']" % item
+ link = self.find(s, By.CSS_SELECTOR, strict=True)
+ assert link.is_displayed(), 'Navigation link is not displayed'
+ link.click()
+ self.wait_for_request()
+ self.wait_for_request(0.4)
+
+ def navigate_by_breadcrumb(self, item):
+ """
+ Navigate by breadcrumb navigation
+ """
+ facet = self.get_facet()
+ nav = self.find('div.breadcrumb', By.CSS_SELECTOR, facet, strict=True)
+ a = self.find(item, By.LINK_TEXT, nav, strict=True)
+ a.click()
+ self.wait_for_request()
+ self.wait_for_request(0.4)
+
+ def switch_to_facet(self, name):
+ """
+ Click on tab with given name
+ """
+ facet = self.get_facet()
+ s = "div.facet-tabs li[name='%s'] a" % name
+ link = self.find(s, By.CSS_SELECTOR, facet, strict=True)
+ link.click()
+ # double wait because of facet's paging
+ self.wait_for_request(0.5)
+ self.wait_for_request()
+
+ def get_url(self, entity, facet=None):
+ """
+ Create entity url
+ """
+ url = [self.get_base_url(), '#', 'e', entity]
+ if facet:
+ url.append(facet)
+ return '/'.join(url)
+
+ def get_base_url(self):
+ host = self.config.get('ipa_server')
+ if not host:
+ self.skip('FreeIPA server hostname not configured')
+ return 'https://%s/ipa/ui' % host
+
+ def get_facet(self):
+ """
+ Get currently displayed facet
+ """
+ facet = self.find('.active-facet', By.CSS_SELECTOR)
+ assert facet is not None, "Current facet not found"
+ return facet
+
+ def get_facet_info(self, facet=None):
+ """
+ Get information of currently displayed facet
+ """
+ info = {}
+
+ # get facet
+ if facet is None:
+ facet = self.get_facet()
+ info["element"] = facet
+
+ #get facet name and entity
+ info["name"] = facet.get_attribute('data-name')
+ info["entity"] = facet.get_attribute('data-entity')
+
+ # get facet title
+ el = self.find(".facet-header h3 *:first-child", By.CSS_SELECTOR, facet)
+ if el: info["title"] = el.text
+
+ # get facet pkey
+ el = self.find(".facet-header h3 span.facet-pkey", By.CSS_SELECTOR, facet)
+ if el: info["pkey"] = el.text
+
+ return info
+
+ def get_dialogs(self, strict=False, name=None):
+ """
+ Get all dialogs in DOM
+ """
+ s = 'div[role=dialog]'
+ if name:
+ s += " div[data-name='%s'" % name
+ dialogs = self.find(s, By.CSS_SELECTOR, all=True)
+ if strict:
+ assert dialogs, "No dialogs found"
+ return dialogs
+
+ def get_dialog(self, strict=False, name=None):
+ """
+ Get last opened dialog
+ """
+ dialogs = self.get_dialogs(strict, name)
+ dialog = None
+ if len(dialogs):
+ dialog = dialogs[-1]
+ return dialog
+
+ def get_last_error_dialog(self, dialog_name='error_dialog'):
+ """
+ Get last opened error dialog or None.
+ """
+ s = "div[role=dialog] div[data-name='%s']" % dialog_name
+ dialogs = self.find(s, By.CSS_SELECTOR, all=True)
+ dialog = None
+ if dialogs:
+ dialog = dialogs[-1]
+ return dialog
+
+ def get_dialog_info(self):
+ """
+ Get last open dialog info: name, text if any.
+ Returns None if no dialog is open.
+ """
+ dialog = self.get_dialog()
+
+ info = None
+ if dialog:
+ content = self.find('div.ui-dialog-content', By.CSS_SELECTOR, dialog, strict=True)
+ info = {
+ 'name': content.get_attribute('data-name'),
+ 'text': content.text,
+ }
+ return info
+
+ def click_on_link(self, text, parent=None):
+ """
+ Click on link with given text and parent.
+ """
+
+ if not parent:
+ parent = self.get_form()
+
+ link = self.find(text, By.LINK_TEXT, parent, strict=True)
+ link.click()
+
+ def facet_button_click(self, name):
+ """
+ Click on facet button with given name
+ """
+ facet = self.get_facet()
+ s = ".facet-controls a[name=%s]" % name
+ self._button_click(s, facet, name)
+
+ def dialog_button_click(self, name, dialog=None):
+ """
+ Click on dialog button with given name
+
+ Chooses last dialog if none is supplied
+ """
+ if not dialog:
+ dialog = self.get_dialog(strict=True)
+
+ s = "div.ui-dialog-buttonset button[name=%s]" % name
+ self._button_click(s, dialog, name)
+
+ def action_button_click(self, name, parent):
+ if not parent:
+ parent = self.get_form()
+
+ s = "a[name='%s'].action-button" % name
+ self._button_click(s, parent, name)
+
+ def button_click(self, name, parent=None):
+ if not parent:
+ parent = self.get_form()
+
+ s = "a[name='%s'].ui-button" % name
+ self._button_click(s, parent, name)
+
+ def _button_click(self, selector, parent, name=''):
+ btn = self.find(selector, By.CSS_SELECTOR, parent, strict=True)
+ disabled = 'ui-state-disabled' in btn.get_attribute("class").split(' ')
+ assert btn.is_displayed(), 'Button is not displayed: %s' % name
+ assert not disabled, 'Invalid button state: disabled. Button: %s' % name
+ btn.click()
+ self.wait_for_request()
+
+ def get_form(self):
+ """
+ Get last dialog or visible facet
+ """
+ form = self.get_dialog()
+ if not form:
+ form = self.get_facet()
+ return form
+
+ def select(self, selector, value, parent=None):
+ if not parent:
+ parent = self.get_form()
+ el = self.find(selector, By.CSS_SELECTOR, parent, strict=True)
+ Select(el).select_by_value(value)
+
+ def fill_text(self, selector, value, parent=None):
+ """
+ Clear and enter text into input defined by selector.
+ Use for non-standard fields.
+ """
+
+ if not parent:
+ parent = self.get_form()
+ tb = self.find(selector, By.CSS_SELECTOR, parent, strict=True)
+ tb.clear()
+ tb.send_keys(value)
+
+ def fill_input(self, name, value, type="text", parent=None):
+
+ s = "div[name='%s'] input[type='%s'][name='%s']" % (name, type, name)
+ self.fill_text(s, value, parent)
+
+ def fill_textarea(self, name, value, parent=None):
+ """
+ Clear and fill textarea.
+ """
+ s = "textarea[name='%s']" % (name)
+ self.fill_text(s, value, parent)
+
+ def fill_textbox(self, name, value, parent=None):
+ """
+ Clear and fill textbox.
+ """
+ self.fill_input(name, value, "text", parent)
+
+ def fill_password(self, name, value, parent=None):
+ """
+ Clear and fill input[type=password]
+ """
+ self.fill_input(name, value, "password", parent)
+
+ def add_multivalued(self, name, value, parent=None):
+ """
+ Adds new value to multivalued textbox
+ """
+ if not parent:
+ parent = self.get_form()
+ s = "div[name='%s'].multivalued-widget" % name
+ w = self.find(s, By.CSS_SELECTOR, parent, strict=True)
+ add_btn = self.find("Add", By.LINK_TEXT, w, strict=True)
+ add_btn.click()
+ s = "div[name=value] input"
+ inputs = self.find(s, By.CSS_SELECTOR, w, all=True)
+ last = inputs[-1]
+ last.send_keys(value)
+
+ def delete_multivalued(self, name, value, parent=None):
+ if not parent:
+ parent = self.get_form()
+ s = "div[name='%s'].multivalued-widget" % name
+ w = self.find(s, By.CSS_SELECTOR, parent, strict=True)
+ s = "div[name=value] input"
+ inputs = self.find(s, By.CSS_SELECTOR, w, all=True)
+ clicked = False
+ for i in inputs:
+ val = i.get_attribute('value')
+ n = i.get_attribute('name')
+ if val == value:
+ s = "input[name='%s'] ~ a[name=remove]" % n
+ link = self.find(s, By.CSS_SELECTOR, w, strict=True)
+ link.click()
+ self.wait()
+ clicked = True
+
+ assert clicked, 'Value was not removed'
+
+
+ def check_option(self, name, value=None, parent=None):
+ """
+ Find checkbox or radio with name which matches ^NAME\d$
+ """
+
+ if not parent:
+ parent = self.get_form()
+ s = "//input[@type='checkbox' or 'radio'][contains(@name, '%s')]" % name
+ if value is not None:
+ s += "[@value='%s']" % value
+ opts = self.find(s, "xpath", parent, all=True)
+ opt = None
+ # Select only the one which matches exactly the name
+ for o in opts:
+ n = o.get_attribute("name")
+ if n == name or re.match("^%s\d$" % name, n):
+ opt = o
+ break
+ assert opt is not None, "Option not found: %s" % name
+ opt.click()
+
+ def select_combobox(self, name, value, parent=None):
+ """
+ Select value in a combobox. Search if not found.
+ """
+ if not parent:
+ parent = self.get_form()
+ s = "[name='%s'].combobox-widget" % name
+ cb = self.find(s, By.CSS_SELECTOR, parent, strict=True)
+ open_btn = self.find('a[name=open] span', By.CSS_SELECTOR, cb, strict=True)
+ open_btn.click()
+ self.wait()
+ self.wait_for_request()
+
+ search_btn = self.find('a[name=search] span', By.CSS_SELECTOR, cb, strict=True)
+ opt_s = "select[name=list] option[value='%s']" % value
+ option = self.find(opt_s, By.CSS_SELECTOR, cb)
+ if not option:
+ # try to search
+ self.fill_textbox('filter', value, cb)
+
+ search_btn.click()
+ self.wait_for_request()
+ option = self.find(opt_s, By.CSS_SELECTOR, cb, strict=True)
+
+ option.click()
+
+ # Chrome does not close search area on click
+ if search_btn.is_displayed():
+ self.driver.switch_to_active_element().send_keys(Keys.RETURN)
+
+ self.wait()
+
+
+ def get_undo_button(self, field, parent):
+ """
+ Get field undo button
+ """
+
+ if not parent:
+ parent = self.get_form()
+ s = "div[name='%s'].field span.undo" % (field)
+ undo = self.find(s, By.CSS_SELECTOR, parent, strict=True)
+ return undo
+
+ def get_rows(self, parent=None):
+ """
+ Return all rows of search table.
+ """
+
+ if not parent:
+ parent = self.get_form()
+
+ # select table rows
+ s = 'table.search-table tbody tr'
+ rows = self.find(s, By.CSS_SELECTOR, parent, all=True)
+ return rows
+
+ def navigate_to_row_record(self, row, pkey_column=None):
+ s = 'a'
+ if pkey_column:
+ s = "div[name='%s'] a" % pkey_column
+ link = self.find(s, By.CSS_SELECTOR, row, strict=True)
+ link.click()
+ self.wait_for_request(0.4)
+ self.wait_for_request()
+
+ def get_table_selector(self, name=None):
+ s = "table"
+ if name:
+ s += "[name='%s']" % name
+ s +='.search-table'
+ return s
+
+ def select_record(self, pkey, parent=None, table_name=None):
+ """
+ Select record with given pkey in search table.
+ """
+ if not parent:
+ parent = self.get_form()
+
+ s = self.get_table_selector(table_name)
+ s += " tbody td input[value='%s']" % pkey
+ checkbox = self.find(s, By.CSS_SELECTOR, parent, strict=True)
+ checkbox.click()
+ self.wait()
+
+ def has_record(self, pkey, parent=None, table_name=None):
+ if not parent:
+ parent = self.get_form()
+
+ s = self.get_table_selector(table_name)
+ s += " tbody td input[value='%s']" % pkey
+ checkbox = self.find(s, By.CSS_SELECTOR, parent)
+ return checkbox is not None
+
+ def navigate_to_record(self, pkey, parent=None, table_name=None, entity=None, facet='search'):
+ """
+ Clicks on record with given pkey in search table and thus cause
+ navigation to the record.
+ """
+
+ if entity:
+ self.navigate_to_entity(entity, facet)
+
+ if not parent:
+ parent = self.get_facet()
+
+ s = self.get_table_selector(table_name)
+ s += " tbody"
+ table = self.find(s, By.CSS_SELECTOR, parent, strict=True)
+ link = self.find(pkey, By.LINK_TEXT, table, strict=True)
+ link.click()
+ self.wait_for_request()
+
+ def delete_record(self, pkeys, fields=None, parent=None, table_name=None):
+ """
+ Delete records with given pkeys in currently opened search table.
+ """
+ if type(pkeys) is not list:
+ pkeys = [pkeys]
+
+ # select
+ selected = False
+ for pkey in pkeys:
+ delete = self.has_record(pkey, parent, table_name)
+ if delete:
+ self.select_record(pkey, parent, table_name)
+ selected = True
+
+ # exec and confirm
+ if selected:
+ if table_name and parent:
+ s = self.get_table_selector(table_name)
+ table = self.find(s, By.CSS_SELECTOR, parent, strict=True)
+ self.action_button_click('remove', table)
+ else:
+ self.facet_button_click('remove')
+ if fields:
+ self.fill_fields(fields)
+ self.dialog_button_click('ok')
+ self.wait_for_request(n=2)
+ self.wait()
+
+
+ def delete(self, entity, data_list, facet='search', navigate=True):
+ """
+ Delete entity records:
+ """
+ if navigate:
+ self.navigate_to_entity(entity, facet)
+ for data in data_list:
+ pkey = data.get('pkey')
+ fields = data.get('del')
+ self.delete_record(pkey, fields)
+
+ def fill_fields(self, fields, parent=None, undo=False):
+ """
+ Fill dialog or facet inputs with give data.
+
+ Expected format:
+ [
+ ('widget_type', 'key', value'),
+ ('widget_type', 'key2', value2'),
+ ]
+ """
+
+ if not parent:
+ parent = self.get_form()
+
+ for field in fields:
+ type = field[0]
+ key = field[1]
+ val = field[2]
+
+ if undo:
+ self.assert_undo_button(key, False, parent)
+
+ if type == 'textbox':
+ self.fill_textbox(key, val, parent)
+ elif type == 'textarea':
+ self.fill_textarea(key, val, parent)
+ elif type == 'password':
+ self.fill_password(key, val, parent)
+ elif type == 'radio':
+ self.check_option(key, val, parent)
+ elif type == 'checkbox':
+ self.check_option(key, parent=parent)
+ elif type == 'combobox':
+ self.select_combobox(key, val, parent)
+ elif type == 'add_table_record':
+ self.add_table_record(key, val, parent)
+ elif type == 'add_table_association':
+ self.add_table_associations(key, val, parent)
+ elif type == 'table':
+ self.select_record(val, parent, key)
+ self.wait()
+ if undo:
+ self.assert_undo_button(key, True, parent)
+
+ def find_record(self, entity, data, facet='search', dummy='XXXXXXX'):
+
+ self.assert_facet(entity, facet)
+
+
+ facet = self.get_facet()
+ search_field_s = '.search-filter input[name=filter]'
+ key = data.get('pkey')
+
+ self.fill_text(search_field_s, dummy, facet)
+ self.action_button_click('find', facet)
+ self.wait_for_request(n=2)
+ self.assert_record(key, negative=True)
+
+ self.fill_text(search_field_s, key, facet)
+ self.action_button_click('find', facet)
+ self.wait_for_request(n=2)
+ self.assert_record(key)
+
+ self.fill_text(search_field_s, '', facet)
+ self.action_button_click('find', facet)
+ self.wait_for_request(n=2)
+
+ def add_record(self, entity, data, facet='search', facet_btn='add',
+ dialog_btn='add', delete=False, pre_delete=True,
+ dialog_name='add', navigate=True):
+ """
+ Add records.
+
+ Expected data format:
+ {
+ 'pkey': 'key',
+ add: [
+ ('widget_type', 'key', 'value'),
+ ('widget_type', 'key2', 'value2'),
+ ],
+ }
+ """
+ pkey = data['pkey']
+
+ if navigate:
+ self.navigate_to_entity(entity, facet)
+
+ # check facet
+ self.assert_facet(entity, facet)
+
+ # delete if exists, ie. from previous test fail
+ if pre_delete:
+ self.delete_record(pkey, data.get('del'))
+
+ # current row count
+ self.wait_for_request(0.5)
+ count = len(self.get_rows())
+
+ # open add dialog
+ self.assert_no_dialog()
+ self.facet_button_click(facet_btn)
+ self.assert_dialog(dialog_name)
+
+ # fill dialog
+ self.fill_fields(data['add'])
+
+ # confirm dialog
+ self.dialog_button_click(dialog_btn)
+ self.wait_for_request()
+ self.wait_for_request()
+
+ # check expected error/warning/info
+ expected = ['error_4304_info']
+ dialog_info = self.get_dialog_info()
+ if dialog_info and dialog_info['name'] in expected:
+ self.dialog_button_click('ok')
+ self.wait_for_request()
+
+
+ # check for error
+ self.assert_no_error_dialog()
+ self.wait_for_request()
+ self.wait_for_request(0.4)
+
+ # check if table has more rows
+ new_count = len(self.get_rows())
+ # adjust because of paging
+ expected = count + 1
+ if count == 20:
+ expected = 20
+ self.assert_row_count(expected, new_count)
+
+ # delete record
+ if delete:
+ self.delete_record(pkey)
+ new_count = len(self.get_rows())
+ self.assert_row_count(count, new_count)
+
+ def mod_record(self, entity, data, facet='details', facet_btn='update'):
+ """
+ Mod record
+
+ Assumes that it is already on details page.
+ """
+
+ self.assert_facet(entity, facet)
+ # TODO assert pkey
+ self.assert_facet_button_enabled(facet_btn, enabled=False)
+ self.fill_fields(data['mod'], undo=True)
+ self.assert_facet_button_enabled(facet_btn)
+ self.facet_button_click(facet_btn)
+ self.wait_for_request()
+ self.wait_for_request()
+ self.assert_facet_button_enabled(facet_btn, enabled=False)
+
+ def basic_crud(self, entity, data,
+ parent_entity = None,
+ details_facet = 'details',
+ search_facet = 'search',
+ default_facet = 'details',
+ add_facet_btn='add',
+ add_dialog_btn='add',
+ add_dialog_name='add',
+ update_btn='update',
+ breadcrumb=None,
+ navigate=True,
+ delete=True):
+ """
+ Basic CRUD operation sequence.
+
+ Expected data format:
+ {
+ 'pkey': 'key',
+ 'add': [
+ ('widget_type', 'key', 'value'),
+ ('widget_type', 'key2', 'value2'),
+ ],
+ 'mod': [
+ ('widget_type', 'key', 'value'),
+ ('widget_type', 'key2', 'value2'),
+ ],
+ }
+ """
+
+ # important for nested entities. Ie. autoumount maps
+ if not parent_entity:
+ parent_entity = entity
+
+ pkey = data['pkey']
+
+ # 1. Open Search Facet
+ if navigate:
+ self.navigate_to_entity(parent_entity)
+ self.assert_facet(parent_entity, search_facet)
+ self.wait_for_request()
+
+ # 2. Add record
+ self.add_record(parent_entity, data, facet=search_facet, navigate=False)
+
+ # Find
+
+ self.find_record(parent_entity, data, search_facet)
+
+ # 3. Navigate to details facet
+ self.navigate_to_record(pkey)
+ self.assert_facet(entity, default_facet)
+ self.wait_for_request(0.5)
+ if default_facet != details_facet:
+ self.switch_to_facet(details_facet)
+ self.assert_facet(entity, details_facet)
+
+ # 4. Mod values
+ if data.get('mod'):
+ # TODO: assert values
+ self.mod_record(entity, data, details_facet, update_btn)
+ # TODO: assert modified values
+
+ if not breadcrumb:
+ self.navigate_to_entity(entity, search_facet)
+ else:
+ self.navigate_by_breadcrumb(breadcrumb)
+
+ # 5. Delete record
+ if delete:
+ self.delete_record(pkey, data.get('del'))
+
+
+ def add_table_record(self, name, data, parent=None):
+ """
+ Add record to dnsrecord table, association table and similar
+ """
+ if not parent:
+ parent = self.get_form()
+ s = self.get_table_selector(name)
+ table = self.find(s, By.CSS_SELECTOR, parent, strict=True)
+ s = "a[name=%s] span.button-label" % 'add'
+ btn = self.find(s, By.CSS_SELECTOR, table, strict=True)
+ btn.click()
+ self.wait()
+ self.fill_fields(data['fields'])
+ self.dialog_button_click('add')
+ self.wait_for_request()
+
+ def add_associations(self, pkeys, facet=None, delete=False):
+ """
+ Add associations
+ """
+
+ if facet:
+ self.switch_to_facet(facet)
+
+ self.facet_button_click('add')
+ self.wait_for_request()
+
+ for key in pkeys:
+ self.select_record(key, table_name='available')
+ self.button_click('add')
+
+ self.dialog_button_click('add')
+ self.wait_for_request()
+
+ for key in pkeys:
+ self.assert_record(key)
+ if delete:
+ self.delete_record(key)
+ self.assert_record(key, negative=True)
+
+ def add_table_associations(self, table_name, pkeys, parent=False, delete=False):
+
+ if not parent:
+ parent = self.get_form()
+
+ s = self.get_table_selector(table_name)
+ table = self.find(s, By.CSS_SELECTOR, parent, strict=True)
+
+ s = "a[name=%s] span.button-label" % 'add'
+ btn = self.find(s, By.CSS_SELECTOR, table, strict=True)
+ btn.click()
+ self.wait_for_request(0.4)
+
+ for key in pkeys:
+ self.select_record(key, table_name='available')
+ self.button_click('add')
+
+ self.dialog_button_click('add')
+ self.wait_for_request(n=2)
+
+ for key in pkeys:
+ self.assert_record(key, parent, table_name)
+ if delete:
+ self.delete_record(pkeys, None, parent, table_name)
+ for key in pkeys:
+ self.assert_record(key, parent, table_name, negative=True)
+
+ def action_list_action(self, name):
+
+ cont = self.find(".active-facet .facet-action-list", By.CSS_SELECTOR, strict=True)
+ select = self.find("select[name=action]", By.CSS_SELECTOR, cont, strict=True)
+ Select(select).select_by_value(name)
+ self.button_click('apply', cont)
+ self.wait()
+
+ def action_panel_action(self, panel_name, action):
+ s = "div[data-name='%s'].action-panel" % panel_name
+ s += " a[data-name='%s']" % action
+ link = self.find(s, By.CSS_SELECTOR, strict=True)
+ link.click()
+ self.wait()
+
+ def enable_action(self):
+ title = self.find('.active-facet div.facet-title', By.CSS_SELECTOR, strict=True)
+ self.action_list_action('enable')
+ self.wait_for_request(n=2)
+ self.assert_no_error_dialog()
+ self.assert_class(title, 'disabled', negative=True)
+
+ def disable_action(self):
+ title = self.find('.active-facet div.facet-title', By.CSS_SELECTOR, strict=True)
+ self.action_list_action('disable')
+ self.wait_for_request(n=2)
+ self.assert_no_error_dialog()
+ self.assert_class(title, 'disabled')
+
+ def delete_action(self, entity, pkey, facet='search'):
+ self.action_list_action('delete')
+ self.wait_for_request(n=4)
+ self.assert_no_error_dialog()
+ self.assert_facet(entity, facet)
+ self.assert_record(pkey, negative=True)
+
+ def mod_rule_tables(self, tables, categories, no_categories):
+ """
+ Test functionality of rule table widgets in a facet
+ """
+
+ def get_t_vals(t):
+ table = t[0]
+ k = t[1]
+ e = []
+ if len(t) > 2:
+ e = t[2]
+ return table, k, e
+
+ t_list = [t[0] for t in tables if t[0] not in no_categories]
+
+ # add values
+ for t in tables:
+ table, keys, exts = get_t_vals(t)
+ # add one by one to test for #3711
+ for key in keys:
+ self.add_table_associations(table, [key])
+
+ #disable tables
+ for cat in categories:
+ self.check_option(cat, 'all')
+
+ # update
+ self.assert_rule_tables_enabled(t_list, False)
+ self.facet_button_click('update')
+ self.wait_for_request(n=3, d=0.3)
+ self.assert_rule_tables_enabled(t_list, False)
+
+ p = self.get_form()
+ # now tables in categories should be empty, check it
+ for t in tables:
+ table, keys, exts = get_t_vals(t)
+ if table in no_categories:
+ # clear the rest
+ self.delete_record(keys, None, p, table)
+ continue
+ for key in keys:
+ self.assert_record(key, p, table, negative=True)
+
+ # enable tables
+ for cat in categories:
+ self.check_option(cat, '')
+ self.assert_rule_tables_enabled(t_list, True)
+ self.facet_button_click('update')
+ self.wait_for_request(n=3, d=0.3)
+ self.assert_rule_tables_enabled(t_list, True)
+
+ for t in tables:
+ table, keys, exts = get_t_vals(t)
+ # add multiple at once and test table delete button
+ self.add_table_associations(table, keys, delete=True)
+
+
+ def skip(self, reason):
+ raise nose.SkipTest(reason)
+
+ def assert_text(self, selector, value, parent=None):
+ """
+ Assert read-only text value in details page or in a form
+ """
+ if not parent:
+ parent = self.get_form()
+
+ el = self.find(selector, By.CSS_SELECTOR, parent, strict=True)
+ text = el.text.strip()
+ value = value.strip()
+ assert text == value, "Invalid value: '%s' Expected: %s" % (text, value)
+
+ def assert_text_field(self, name, value, parent=None, element='label'):
+ """
+ Assert read-only text value in details page or in a form
+ """
+ s = "div[name='%s'] %s[name='%s']" % (name, element, name)
+ self.assert_text(s, value, parent)
+
+ def assert_no_dialog(self):
+ """
+ Assert that no dialog is opened
+ """
+ dialogs = self.get_dialogs()
+ assert not dialogs, 'Invalid state: dialog opened'
+
+ def assert_dialog(self, name=None):
+ """
+ Assert that one dialog is opened or a dialog with given name
+ """
+ dialogs = self.get_dialogs(name)
+ assert len(dialogs) == 1, 'No or more than one dialog opened'
+
+
+ def assert_no_error_dialog(self):
+ """
+ Assert that no error dialog is opened
+ """
+ dialog = self.get_last_error_dialog()
+ ok = dialog is None
+ if not ok:
+ msg = self.find('p', By.CSS_SELECTOR, dialog).text
+ assert ok, 'Unexpected error: %s' % msg
+
+ def assert_row_count(self, expected, current):
+ """
+ Assert that row counts match
+ """
+ assert expected == current, "Rows don't match. Expected: %d, Got: %d" % (expected, current)
+
+ def assert_button_enabled(self, name, context_selector=None, enabled=True):
+ """
+ Assert that button is enabled or disabled
+ """
+ s = ""
+ if context_selector:
+ s = context_selector
+ s += "a[name=%s]" % name
+ facet = self.get_facet()
+ btn = self.find(s, By.CSS_SELECTOR, facet, strict=True)
+ cls = 'action-button-disabled'
+ has_cls = cls in btn.get_attribute("class").split(' ')
+ valid = enabled ^ has_cls
+ assert btn.is_displayed(), 'Button is not displayed'
+ assert valid, 'Button has incorrect enabled state.'
+
+ def assert_facet_button_enabled(self, name, enabled=True):
+ """
+ Assert that facet button is enabled or disabled
+ """
+ self.assert_button_enabled(name, ".facet-controls ", enabled)
+
+ def assert_table_button_enabled(self, name, table_name, enabled=True):
+ """
+ Assert that button in table is enabled/disabled
+ """
+ s = "table[name='%s'] " % table_name
+ self.assert_button_enabled(name, s, enabled)
+
+
+ def assert_facet(self, entity, facet=None):
+ """
+ Assert that current facet is correct
+ """
+ info = self.get_facet_info()
+ if not facet is None:
+ assert info["name"] == facet, "Invalid facet. Expected: %s, Got: %s " % (facet, info["name"])
+ assert info["entity"] == entity, "Invalid entity. Expected: %s, Got: %s " % (entity, info["entity"])
+
+ def assert_undo_button(self, field, visible=True, parent=None):
+ """
+ Assert that undo button is or is not visible
+ """
+ undo = self.get_undo_button(field, parent)
+ state = undo.is_displayed()
+ assert state == visible, 'Undo button has invalid state. Field: %s' % field
+
+ def assert_visible(self, selector, parent=None, negative=False):
+ """
+ Assert that element defined by selector is visible
+ """
+ if not parent:
+ parent = self.get_form()
+ el = self.find(selector, By.CSS_SELECTOR, parent, strict=True)
+ visible = el.is_displayed()
+ if negative:
+ assert not visible, "Element visible: %s" % selector
+ else:
+ assert visible, "Element not visible: %s" % selector
+
+
+ def assert_record(self, pkey, parent=None, table_name=None, negative=False):
+ """
+ Assert that record is in current search table
+ """
+ has = self.has_record(pkey, parent, table_name)
+ if negative:
+ assert not has, "Record exists when it shouldn't: %s" % pkey
+ else:
+ assert has, 'Record does not exist: %s' % pkey
+
+ def assert_indirect_record(self, pkey, entity, facet, negative=False, switch=True, lower=True):
+ """
+ Switch to indirect facet and assert record.
+
+ Lowers the key by default.
+ """
+ if switch:
+ self.switch_to_facet(facet)
+ radio_name = "%s-%s-type-radio" % (entity, facet.replace('_', '-'))
+ self.check_option(radio_name, 'indirect')
+ self.wait_for_request(n=2)
+ key = pkey
+ if lower:
+ key = key.lower()
+ self.assert_record(key)
+
+ def assert_class(self, element, cls, negative=False):
+ """
+ Assert that element has certain class
+ """
+ valid = cls in element.get_attribute('class').split(' ')
+ if negative:
+ assert not valid, "Element contains unwanted class: %s" % cls
+ else:
+ assert valid, "Element doesn't contain required class: %s" % cls
+
+ def assert_rule_tables_enabled(self, tables, enabled):
+ for table in tables:
+ self.assert_table_button_enabled('add', table, enabled)