summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVlad Okhrimenko <vokhrimenko@mirantis.com>2014-07-31 14:40:32 +0300
committerTimur Sufiev <tsufiev@mirantis.com>2016-04-20 07:48:27 +0000
commitf623281b2979294eec7ad010e61e2ad9de2ea598 (patch)
treeb51f773004b6a25c1650e4833aa16edb212a8c7d
parent5d6003971f1c41abecd19c40b1384f9efa5210de (diff)
Provide integration test for managing a project's members
A new MembershipMenuRegion was implemented specifically for the task of dealing with Users being assigned to a Projects. It may be re-used in other tests as well, which deal with the same membership concept (e.g., Flavor Access control). The checker method .is_the_current_page() is changed to act either as a boolean method, or as an internal assert method. Co-Authored-By: Timur Sufiev <tsufiev@mirantis.com> Implements blueprint: horizon-integration-tests-coverage Change-Id: Ie661a1522951e1e86c461c6ec284fcf4a3e6d6fb
Notes
Notes (review): Code-Review+1: Timur Sufiev <tsufiev@mirantis.com> Code-Review+1: Sergei Chipiga <schipiga@mirantis.com> Code-Review+2: Rob Cresswell <robert.cresswell@outlook.com> Code-Review+2: Richard Jones <r1chardj0n3s@gmail.com> Workflow+1: Richard Jones <r1chardj0n3s@gmail.com> Verified+2: Jenkins Submitted-by: Jenkins Submitted-at: Wed, 04 May 2016 10:11:23 +0000 Reviewed-on: https://review.openstack.org/168935 Project: openstack/horizon Branch: refs/heads/master
-rw-r--r--openstack_dashboard/test/integration_tests/config.py7
-rw-r--r--openstack_dashboard/test/integration_tests/horizon.conf6
-rw-r--r--openstack_dashboard/test/integration_tests/pages/identity/projectspage.py34
-rw-r--r--openstack_dashboard/test/integration_tests/pages/pageobject.py15
-rw-r--r--openstack_dashboard/test/integration_tests/regions/menus.py113
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_projects.py35
6 files changed, 200 insertions, 10 deletions
diff --git a/openstack_dashboard/test/integration_tests/config.py b/openstack_dashboard/test/integration_tests/config.py
index c4acee3..73d04ea 100644
--- a/openstack_dashboard/test/integration_tests/config.py
+++ b/openstack_dashboard/test/integration_tests/config.py
@@ -47,6 +47,13 @@ IdentityGroup = [
47 cfg.StrOpt('admin_home_project', 47 cfg.StrOpt('admin_home_project',
48 default='admin', 48 default='admin',
49 help="Project to keep all objects belonging to an admin user."), 49 help="Project to keep all objects belonging to an admin user."),
50 cfg.StrOpt('default_keystone_role',
51 default='Member',
52 help="Name of default role every user gets in his new project"),
53 cfg.StrOpt('default_keystone_admin_role',
54 default='admin',
55 help="Name of the role that grants admin rights to a user in "
56 "his project"),
50] 57]
51 58
52ImageGroup = [ 59ImageGroup = [
diff --git a/openstack_dashboard/test/integration_tests/horizon.conf b/openstack_dashboard/test/integration_tests/horizon.conf
index 2707381..808958a 100644
--- a/openstack_dashboard/test/integration_tests/horizon.conf
+++ b/openstack_dashboard/test/integration_tests/horizon.conf
@@ -60,6 +60,12 @@ admin_password=secretadmin
60# Project in which an admin user creates everything by default 60# Project in which an admin user creates everything by default
61admin_home_project=admin 61admin_home_project=admin
62 62
63# Name of default role every user gets in his new project
64default_keystone_role=Member
65
66# Name of the role that grants admin rights to a user in his project
67default_keystone_admin_role=admin
68
63[network] 69[network]
64# The cidr block to allocate tenant ipv4 subnets from (string value) 70# The cidr block to allocate tenant ipv4 subnets from (string value)
65tenant_network_cidr=10.100.0.0/16 71tenant_network_cidr=10.100.0.0/16
diff --git a/openstack_dashboard/test/integration_tests/pages/identity/projectspage.py b/openstack_dashboard/test/integration_tests/pages/identity/projectspage.py
index b282102..e377dc5 100644
--- a/openstack_dashboard/test/integration_tests/pages/identity/projectspage.py
+++ b/openstack_dashboard/test/integration_tests/pages/identity/projectspage.py
@@ -12,19 +12,31 @@
12 12
13from openstack_dashboard.test.integration_tests.pages import basepage 13from openstack_dashboard.test.integration_tests.pages import basepage
14from openstack_dashboard.test.integration_tests.regions import forms 14from openstack_dashboard.test.integration_tests.regions import forms
15from openstack_dashboard.test.integration_tests.regions import menus
15from openstack_dashboard.test.integration_tests.regions import tables 16from openstack_dashboard.test.integration_tests.regions import tables
16 17
17 18
19class ProjectForm(forms.TabbedFormRegion):
20 FIELDS = (("name", "description", "enabled"),
21 {'members': menus.MembershipMenuRegion})
22
23 def __init__(self, driver, conf, tab=0):
24 super(ProjectForm, self).__init__(
25 driver, conf, field_mappings=self.FIELDS, default_tab=tab)
26
27
18class ProjectsTable(tables.TableRegion): 28class ProjectsTable(tables.TableRegion):
19 name = 'tenants' 29 name = 'tenants'
20 CREATE_PROJECT_FORM_FIELDS = (("name", "description", "enabled"),)
21 30
22 @tables.bind_table_action('create') 31 @tables.bind_table_action('create')
23 def create_project(self, create_button): 32 def create_project(self, create_button):
24 create_button.click() 33 create_button.click()
25 return forms.TabbedFormRegion( 34 return ProjectForm(self.driver, self.conf)
26 self.driver, self.conf, 35
27 field_mappings=self.CREATE_PROJECT_FORM_FIELDS) 36 @tables.bind_row_action('update')
37 def update_members(self, members_button, row):
38 members_button.click()
39 return ProjectForm(self.driver, self.conf, tab=1)
28 40
29 @tables.bind_table_action('delete') 41 @tables.bind_table_action('delete')
30 def delete_project(self, delete_button): 42 def delete_project(self, delete_button):
@@ -72,3 +84,17 @@ class ProjectsPage(basepage.BaseNavigationPage):
72 def get_project_id_from_row(self, name): 84 def get_project_id_from_row(self, name):
73 row = self._get_row_with_project_name(name) 85 row = self._get_row_with_project_name(name)
74 return row.cells[self.PROJECT_ID_TABLE_NAME_COLUMN].text 86 return row.cells[self.PROJECT_ID_TABLE_NAME_COLUMN].text
87
88 def allocate_user_to_project(self, user_name, roles, project_name):
89 row = self._get_row_with_project_name(project_name)
90 members_form = self.projects_table.update_members(row)
91 members_form.members.allocate_member(user_name)
92 members_form.members.allocate_member_roles(user_name, roles)
93 members_form.submit()
94
95 def get_user_roles_at_project(self, user_name, project_name):
96 row = self._get_row_with_project_name(project_name)
97 members_form = self.projects_table.update_members(row)
98 roles = members_form.members.get_member_allocated_roles(user_name)
99 members_form.cancel()
100 return set(roles)
diff --git a/openstack_dashboard/test/integration_tests/pages/pageobject.py b/openstack_dashboard/test/integration_tests/pages/pageobject.py
index cf2e44d..89c3b0f 100644
--- a/openstack_dashboard/test/integration_tests/pages/pageobject.py
+++ b/openstack_dashboard/test/integration_tests/pages/pageobject.py
@@ -29,11 +29,14 @@ class PageObject(basewebobject.BaseWebObject):
29 def page_title(self): 29 def page_title(self):
30 return self.driver.title 30 return self.driver.title
31 31
32 def is_the_current_page(self): 32 def is_the_current_page(self, do_assert=False):
33 self.assertIn(self._page_title, self.page_title, 33 found_expected_title = self.page_title.startswith(self._page_title)
34 "Expected to find %s in page title, instead found: %s" 34 if do_assert:
35 % (self._page_title, self.page_title)) 35 self.assertTrue(
36 return True 36 found_expected_title,
37 "Expected to find %s in page title, instead found: %s"
38 % (self._page_title, self.page_title))
39 return found_expected_title
37 40
38 @property 41 @property
39 def login_url(self): 42 def login_url(self):
@@ -87,4 +90,4 @@ class PageObject(basewebobject.BaseWebObject):
87 90
88 def go_to_login_page(self): 91 def go_to_login_page(self):
89 self.driver.get(self.login_url) 92 self.driver.get(self.login_url)
90 self.is_the_current_page() 93 self.is_the_current_page(do_assert=True)
diff --git a/openstack_dashboard/test/integration_tests/regions/menus.py b/openstack_dashboard/test/integration_tests/regions/menus.py
index a780859..2dd4573 100644
--- a/openstack_dashboard/test/integration_tests/regions/menus.py
+++ b/openstack_dashboard/test/integration_tests/regions/menus.py
@@ -259,3 +259,116 @@ class ProjectDropDownRegion(DropDownMenuRegion):
259 else: 259 else:
260 raise exceptions.NoSuchElementException( 260 raise exceptions.NoSuchElementException(
261 "Not found element with text: %s" % name) 261 "Not found element with text: %s" % name)
262
263
264class MembershipMenuRegion(baseregion.BaseRegion):
265 _available_members_locator = (
266 by.By.CSS_SELECTOR, 'ul.available_members > ul.btn-group')
267
268 _allocated_members_locator = (
269 by.By.CSS_SELECTOR, 'ul.members > ul.btn-group')
270
271 _add_remove_member_sublocator = (
272 by.By.CSS_SELECTOR, 'li > a[href="#add_remove"]')
273
274 _member_name_sublocator = (
275 by.By.CSS_SELECTOR, 'li.member > span.display_name')
276
277 _member_roles_widget_sublocator = (by.By.CSS_SELECTOR, 'li.role_options')
278
279 _member_roles_widget_open_subsublocator = (by.By.CSS_SELECTOR, 'a.btn')
280
281 _member_roles_widget_roles_subsublocator = (
282 by.By.CSS_SELECTOR, 'ul.role_dropdown > li')
283
284 def _get_member_name(self, element):
285 return element.find_element(*self._member_name_sublocator).text
286
287 @property
288 def available_members(self):
289 return {self._get_member_name(el): el for el in
290 self._get_elements(*self._available_members_locator)}
291
292 @property
293 def allocated_members(self):
294 return {self._get_member_name(el): el for el in
295 self._get_elements(*self._allocated_members_locator)}
296
297 def allocate_member(self, name, available_members=None):
298 # NOTE(tsufiev): available_members here (and allocated_members below)
299 # are meant to be used for performance optimization to reduce the
300 # amount of calls to selenium by reusing still valid element reference
301 if available_members is None:
302 available_members = self.available_members
303
304 available_members[name].find_element(
305 *self._add_remove_member_sublocator).click()
306
307 def deallocate_member(self, name, allocated_members=None):
308 if allocated_members is None:
309 allocated_members = self.allocated_members
310
311 allocated_members[name].find_element(
312 *self._add_remove_member_sublocator).click()
313
314 def _get_member_roles_widget(self, name, allocated_members=None):
315 if allocated_members is None:
316 allocated_members = self.allocated_members
317
318 return allocated_members[name].find_element(
319 *self._member_roles_widget_sublocator)
320
321 def _get_member_all_roles(self, name, allocated_members=None):
322 roles_widget = self._get_member_roles_widget(name, allocated_members)
323 return roles_widget.find_elements(
324 *self._member_roles_widget_roles_subsublocator)
325
326 @staticmethod
327 def _is_role_selected(role):
328 return 'selected' in role.get_attribute('class').split()
329
330 @staticmethod
331 def _get_hidden_text(role):
332 return role.get_attribute('textContent')
333
334 def get_member_available_roles(self, name, allocated_members=None,
335 strip=True):
336 roles = self._get_member_all_roles(name, allocated_members)
337 return [(self._get_hidden_text(role).strip() if strip else role)
338 for role in roles if not self._is_role_selected(role)]
339
340 def get_member_allocated_roles(self, name, allocated_members=None,
341 strip=True):
342 roles = self._get_member_all_roles(name, allocated_members)
343 return [(self._get_hidden_text(role).strip() if strip else role)
344 for role in roles if self._is_role_selected(role)]
345
346 def open_member_roles_dropdown(self, name, allocated_members=None):
347 widget = self._get_member_roles_widget(name, allocated_members)
348 button = widget.find_element(
349 *self._member_roles_widget_open_subsublocator)
350 button.click()
351
352 def _switch_member_roles(self, name, roles2toggle, method,
353 allocated_members=None):
354 self.open_member_roles_dropdown(name, allocated_members)
355 roles = method(name, allocated_members, False)
356 roles2toggle = set(roles2toggle)
357 for role in roles:
358 role_name = role.text.strip()
359 if role_name in roles2toggle:
360 role.click()
361 roles2toggle.remove(role_name)
362 if not roles2toggle:
363 break
364
365 def allocate_member_roles(self, name, roles2add, allocated_members=None):
366 self._switch_member_roles(
367 name, roles2add, self.get_member_available_roles,
368 allocated_members=allocated_members)
369
370 def deallocate_member_roles(self, name, roles2remove,
371 allocated_members=None):
372 self._switch_member_roles(
373 name, roles2remove, self.get_member_allocated_roles,
374 allocated_members=allocated_members)
diff --git a/openstack_dashboard/test/integration_tests/tests/test_projects.py b/openstack_dashboard/test/integration_tests/tests/test_projects.py
index 5ccec67..bb8f41f 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_projects.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_projects.py
@@ -9,6 +9,7 @@
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations 10# License for the specific language governing permissions and limitations
11# under the License. 11# under the License.
12
12from openstack_dashboard.test.integration_tests import helpers 13from openstack_dashboard.test.integration_tests import helpers
13from openstack_dashboard.test.integration_tests.regions import messages 14from openstack_dashboard.test.integration_tests.regions import messages
14 15
@@ -36,3 +37,37 @@ class TestCreateDeleteProject(helpers.AdminTestCase):
36 self.assertFalse( 37 self.assertFalse(
37 self.projects_page.find_message_and_dismiss(messages.ERROR)) 38 self.projects_page.find_message_and_dismiss(messages.ERROR))
38 self.assertFalse(self.projects_page.is_project_present(PROJECT_NAME)) 39 self.assertFalse(self.projects_page.is_project_present(PROJECT_NAME))
40
41
42class TestModifyProject(helpers.AdminTestCase):
43
44 def setUp(self):
45 super(TestModifyProject, self).setUp()
46 self.projects_page = self.home_pg.go_to_identity_projectspage()
47 self.projects_page.create_project(PROJECT_NAME)
48 self.assertTrue(
49 self.projects_page.find_message_and_dismiss(messages.SUCCESS))
50
51 def test_add_member(self):
52 admin_name = self.CONFIG.identity.admin_username
53 regular_role_name = self.CONFIG.identity.default_keystone_role
54 admin_role_name = self.CONFIG.identity.default_keystone_admin_role
55 roles2add = {regular_role_name, admin_role_name}
56
57 self.projects_page.allocate_user_to_project(
58 admin_name, roles2add, PROJECT_NAME)
59 self.assertTrue(
60 self.projects_page.find_message_and_dismiss(messages.SUCCESS))
61 self.assertFalse(
62 self.projects_page.find_message_and_dismiss(messages.ERROR))
63
64 user_roles = self.projects_page.get_user_roles_at_project(
65 admin_name, PROJECT_NAME)
66 self.assertEqual(roles2add, user_roles,
67 "The requested roles haven't been set for the user!")
68
69 def tearDown(self):
70 if not self.projects_page.is_the_current_page():
71 self.home_pg.go_to_identity_projectspage()
72 self.projects_page.delete_project(PROJECT_NAME)
73 super(TestModifyProject, self).tearDown()