diff --git a/openstack_dashboard/test/integration_tests/basewebobject.py b/openstack_dashboard/test/integration_tests/basewebobject.py index 8436e9b34d..79001f5f53 100644 --- a/openstack_dashboard/test/integration_tests/basewebobject.py +++ b/openstack_dashboard/test/integration_tests/basewebobject.py @@ -49,9 +49,12 @@ class BaseWebObject(unittest.TestCase): except Exception: return False - def _is_text_visible(self, element, text): + def _is_text_visible(self, element, text, strict=True): try: - return element.text == text + if strict: + return element.text == text + else: + return text in element.text except Exception: return False diff --git a/openstack_dashboard/test/integration_tests/pages/admin/system/metadatadefinitionspage.py b/openstack_dashboard/test/integration_tests/pages/admin/system/metadatadefinitionspage.py new file mode 100644 index 0000000000..115ea6c9c8 --- /dev/null +++ b/openstack_dashboard/test/integration_tests/pages/admin/system/metadatadefinitionspage.py @@ -0,0 +1,127 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json + +from openstack_dashboard.test.integration_tests.pages import basepage +from openstack_dashboard.test.integration_tests.regions import forms +from openstack_dashboard.test.integration_tests.regions import tables + + +class MetadatadefinitionsTable(tables.TableRegion): + name = "namespaces" + CREATE_NAMESPACE_FORM_FIELDS = ( + "source_type", "direct_input", "metadef_file", "public", "protected") + + @tables.bind_table_action('import') + def import_namespace(self, create_button): + create_button.click() + return forms.FormRegion( + self.driver, + self.conf, + field_mappings=self.CREATE_NAMESPACE_FORM_FIELDS) + + @tables.bind_table_action('delete') + def delete_namespace(self, delete_button): + delete_button.click() + return forms.BaseFormRegion(self.driver, self.conf) + + +class MetadatadefinitionsPage(basepage.BaseNavigationPage): + + NAMESPACE_TABLE_NAME_COLUMN = 'display_name' + NAMESPACE_TABLE_DESCRIPTION_COLUMN = 'description' + NAMESPACE_TABLE_RESOURCE_TYPES_COLUMN = 'resource_type_names' + NAMESPACE_TABLE_PUBLIC_COLUMN = 'public' + NAMESPACE_TABLE_PROTECTED_COLUMN = 'protected' + + boolean_mapping = {True: 'Yes', False: 'No'} + + def __init__(self, driver, conf): + super(MetadatadefinitionsPage, self).__init__(driver, conf) + self._page_title = "Metadata Definitions" + + def _get_row_with_namespace_name(self, name): + return self.namespaces_table.get_row( + self.NAMESPACE_TABLE_NAME_COLUMN, + name) + + @property + def namespaces_table(self): + return MetadatadefinitionsTable(self.driver, self.conf) + + def json_load_template(self, namespace_template_name): + """Read template for namespace creation + :param namespace_template_name: Path to template + :return = json data container + """ + try: + with open(namespace_template_name, 'r') as template: + json_template = json.load(template) + except Exception: + raise EOFError("Can not read template file: [{0}]".format( + namespace_template_name)) + return json_template + + def import_namespace( + self, namespace_source_type, namespace_json_container, + is_public=True, is_protected=False): + + create_namespace_form = self.namespaces_table.import_namespace() + create_namespace_form.source_type.value = namespace_source_type + if namespace_source_type == 'raw': + json_template_dump = json.dumps(namespace_json_container) + create_namespace_form.direct_input.text = json_template_dump + elif namespace_source_type == 'file': + metadeffile = namespace_json_container + create_namespace_form.metadef_file.choose(metadeffile) + + if is_public: + create_namespace_form.public.mark() + if is_protected: + create_namespace_form.protected.mark() + + create_namespace_form.submit() + + def delete_namespace(self, name): + row = self._get_row_with_namespace_name(name) + row.mark() + confirm_delete_namespaces_form = \ + self.namespaces_table.delete_namespace() + confirm_delete_namespaces_form.submit() + + def is_namespace_present(self, name): + return bool(self._get_row_with_namespace_name(name)) + + def is_public_set_correct(self, name, exp_value, row=None): + if type(exp_value) != bool: + raise ValueError('Expected value "exp_value" is not boolean') + if not row: + row = self._get_row_with_namespace_name(name) + cell = row.cells[self.NAMESPACE_TABLE_PUBLIC_COLUMN] + return self._is_text_visible(cell, self.boolean_mapping[exp_value]) + + def is_protected_set_correct(self, name, exp_value, row=None): + if type(exp_value) != bool: + raise ValueError('Expected value "exp_value" is not boolean') + if not row: + row = self._get_row_with_namespace_name(name) + cell = row.cells[self.NAMESPACE_TABLE_PROTECTED_COLUMN] + return self._is_text_visible(cell, self.boolean_mapping[exp_value]) + + def is_resource_type_set_correct(self, name, expected_resources, row=None): + if not row: + row = self._get_row_with_namespace_name(name) + cell = row.cells[self.NAMESPACE_TABLE_RESOURCE_TYPES_COLUMN] + return all( + [self._is_text_visible(cell, res, strict=False) + for res in expected_resources]) diff --git a/openstack_dashboard/test/integration_tests/pages/navigation.py b/openstack_dashboard/test/integration_tests/pages/navigation.py index bf74893a7c..95fee0924b 100644 --- a/openstack_dashboard/test/integration_tests/pages/navigation.py +++ b/openstack_dashboard/test/integration_tests/pages/navigation.py @@ -169,7 +169,10 @@ class Navigation(object): "Flavors", "Images", "Networks", - "Routers" + "Routers", + "Defaults", + "Metadata Definitions", + "System Information" ) }, }, diff --git a/openstack_dashboard/test/integration_tests/regions/forms.py b/openstack_dashboard/test/integration_tests/regions/forms.py index 88b97ae1d3..839d90955f 100644 --- a/openstack_dashboard/test/integration_tests/regions/forms.py +++ b/openstack_dashboard/test/integration_tests/regions/forms.py @@ -136,22 +136,6 @@ class TextInputFormFieldRegion(BaseTextFormFieldRegion): 'div > input[type=text], div > input[type=None]' -class FileInputFormFieldRegion(BaseFormFieldRegion): - """Text input box.""" - - _element_locator_str_suffix = 'div > input[type=file]' - - @property - def path(self): - return self.element.text - - @path.setter - def path(self, path): - # clear does not work on this kind of element - # because it is not user editable - self.element.send_keys(path) - - class PasswordInputFormFieldRegion(BaseTextFormFieldRegion): """Password text input box.""" diff --git a/openstack_dashboard/test/integration_tests/tests/test-data/empty_namespace.json b/openstack_dashboard/test/integration_tests/tests/test-data/empty_namespace.json new file mode 100644 index 0000000000..6e7c7307ce --- /dev/null +++ b/openstack_dashboard/test/integration_tests/tests/test-data/empty_namespace.json @@ -0,0 +1,21 @@ +{ + "namespace": "1A_TestNamespace", + "display_name": "1A_TestNamespace", + "description": "Description for TestNamespace", + "resource_type_associations": [ + { + "name": "OS::Nova::Flavor" + }, + { + "name": "OS::Glance::Image" + } + ], + "properties": { + "prop1": { + "default": "20", + "type": "integer", + "description": "More info here", + "title": "My property1" + } + } +} \ No newline at end of file diff --git a/openstack_dashboard/test/integration_tests/tests/test_metadata_definitions.py b/openstack_dashboard/test/integration_tests/tests/test_metadata_definitions.py new file mode 100644 index 0000000000..e84746152c --- /dev/null +++ b/openstack_dashboard/test/integration_tests/tests/test_metadata_definitions.py @@ -0,0 +1,158 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os + +from openstack_dashboard.test.integration_tests import helpers +from openstack_dashboard.test.integration_tests.regions import messages + + +class TestMetadataDefinitions(helpers.AdminTestCase): + + NAMESPACE_TEMPLATE_PATH = os.path.join( + os.path.dirname(__file__), + 'test-data/empty_namespace.json') + + PUBLIC = 'public' + PROTECTED = 'protected' + + def namespace_create_with_checks( + self, namespace_name, page, template_json_container, + expected_namespace_res=None, template_source_type='raw', + is_public=True, is_protected=False, template_path=None, + checks=(PUBLIC, PROTECTED)): + """Create NameSpace and run checks + :param namespace_name: Display name of namespace in template + :param page: Connection point + :param template_json_container: JSON container with NameSpace content + :param expected_namespace_res: Resources from template + :param template_source_type: 'raw' or 'file' + :param is_public: True or False + :param is_protected: If True- you can't delete it from GUI + :param checks: Put name of columns if you need to check actual + representation of 'public' and/or 'protected' value. + To disable leave it empty: '' OR put None + :param template_path: Full path to NameSpace template file + :return: Nothing + """ + if template_source_type == 'file': + template_json_container = template_path + + page.import_namespace( + namespace_json_container=template_json_container, + is_public=is_public, + is_protected=is_protected, + namespace_source_type=template_source_type) + # Checks + self.assertTrue(page.find_message_and_dismiss(messages.SUCCESS)) + self.assertFalse(page.find_message_and_dismiss(messages.ERROR)) + self.assertTrue(page.is_namespace_present(namespace_name)) + row = page._get_row_with_namespace_name(namespace_name) + if checks: + if self.PUBLIC in checks: + self.assertTrue( + page.is_public_set_correct(namespace_name, is_public, row)) + elif self.PROTECTED in checks: + self.assertTrue( + page.is_protected_set_correct(namespace_name, is_protected, + row)) + if expected_namespace_res: + self.assertTrue(page.is_resource_type_set_correct( + namespace_name, expected_namespace_res, row)) + + def namespace_delete_with_checks(self, namespace_name, page): + """Delete NameSpace and run checks + :param namespace_name: Display name of namespace in template + :param page: Connection point + :return: Nothing + """ + page.delete_namespace(name=namespace_name) + # Checks + self.assertTrue(page.find_message_and_dismiss(messages.SUCCESS)) + self.assertFalse(page.find_message_and_dismiss(messages.ERROR)) + self.assertFalse(page.is_namespace_present(namespace_name)) + + def test_namespace_create_delete(self): + """Tests the NameSpace creation and deletion functionality: + * Actions: + * 1) Login to Horizon Dashboard as admin user. + * 2) Navigate to Admin -> System -> Metadata Definitions. + * 3) Click "Import Namespace" button. Wait for Create Network dialog. + * 4) Enter settings for new Namespace: + * - Namespace Definition Source: 'raw' or 'file' + * - Namespace JSON location; + * - Public: Yes or No; + * - Protected: No (check box not enabled). + * 5) Press "Import Namespace" button. + * 6) Check that new Namespace was successfully created. + * 7) Check that new Namespace is present in the table. + * 8) Check that values in table on page "Metadata Definitions" are + * the same as in Namespace JSON template and as in step (4): + * - Name + * - Public + * - Protected + * - Resource Types + * 9) Select Namespace in table and press "Delete Namespace" button. + * 10) In "Confirm Delete Namespace" window press "Delete Namespace". + * 11) Check that new Namespace was successfully deleted. + * 12) Check that new Namespace is not present in the table. + """ + namespaces_page = self.home_pg.go_to_system_metadatadefinitionspage() + + template_json_container = namespaces_page.json_load_template( + namespace_template_name=self.NAMESPACE_TEMPLATE_PATH) + # Get name from template file + namespace_name = template_json_container['display_name'] + # Get resources from template to check representation in GUI + namespace_res_type = \ + template_json_container['resource_type_associations'] + namespace_res_type = \ + [x['name'] for x in namespace_res_type] + + # Create / Delete NameSpaces with checks + kwargs = {'namespace_name': namespace_name, + 'page': namespaces_page, + 'is_protected': False, + 'template_json_container': template_json_container, + 'template_path': self.NAMESPACE_TEMPLATE_PATH} + + self.namespace_create_with_checks( + template_source_type='raw', + is_public=True, + expected_namespace_res=namespace_res_type, + checks=(self.PUBLIC, self.PROTECTED), + **kwargs) + self.namespace_delete_with_checks(namespace_name, namespaces_page) + + self.namespace_create_with_checks( + template_source_type='raw', + is_public=False, + expected_namespace_res=None, + checks=(self.PUBLIC,), + **kwargs) + self.namespace_delete_with_checks(namespace_name, namespaces_page) + + self.namespace_create_with_checks( + template_source_type='file', + is_public=True, + expected_namespace_res=namespace_res_type, + checks=(self.PUBLIC, self.PROTECTED), + **kwargs) + self.namespace_delete_with_checks(namespace_name, namespaces_page) + + self.namespace_create_with_checks( + template_source_type='file', + is_public=False, + expected_namespace_res=None, + checks=(self.PUBLIC,), + **kwargs) + self.namespace_delete_with_checks(namespace_name, namespaces_page)