diff options
author | Petr Vobornik <pvoborni@redhat.com> | 2013-05-24 13:48:53 +0200 |
---|---|---|
committer | Petr Vobornik <pvoborni@redhat.com> | 2013-07-16 13:15:59 +0200 |
commit | 2a9be928556e58a69b7ce7a3b7f0aebd8f4c23bc (patch) | |
tree | 6520c97ee694acde785ceb1bc945b80f209137c2 /ipatests | |
parent | e3cddab940a77424b1bb56b75bba10d177fbc1cc (diff) | |
download | freeipa-2a9be928556e58a69b7ce7a3b7f0aebd8f4c23bc.tar.gz freeipa-2a9be928556e58a69b7ce7a3b7f0aebd8f4c23bc.tar.xz freeipa-2a9be928556e58a69b7ce7a3b7f0aebd8f4c23bc.zip |
Upstream Web UI tests
Documentation: http://www.freeipa.org/page/Web_UI_Integration_Tests
https://fedorahosted.org/freeipa/ticket/3744
Diffstat (limited to 'ipatests')
32 files changed, 4025 insertions, 0 deletions
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) |