diff --git a/Makefile b/Makefile index a545271e0d..97b2640aaf 100644 --- a/Makefile +++ b/Makefile @@ -53,6 +53,13 @@ pytest: ## Run functional tests -e SELENOID_HOST="${SELENOID_HOST}" -e SELENOID_PORT="${SELENOID_PORT}" \ ci.arenadata.io/functest:3.8.6.slim.buster-x64 /bin/sh -e ./pytest.sh +pytest_release: ## Run functional tests on release + docker pull ci.arenadata.io/functest:3.8.6.slim.buster.firefox-x64 + docker run -i --rm --shm-size=4g -v /var/run/docker.sock:/var/run/docker.sock --network=host -v $(CURDIR)/:/adcm -w /adcm/ \ + -e BUILD_TAG=${BUILD_TAG} -e ADCM_TAG=$(subst /,_,$(BRANCH_NAME)) -e ADCMPATH=/adcm/ -e PYTHONPATH=${PYTHONPATH}:python/ \ + -e SELENOID_HOST="${SELENOID_HOST}" -e SELENOID_PORT="${SELENOID_PORT}" \ + ci.arenadata.io/functest:3.8.6.slim.buster.firefox-x64 /bin/sh -e ./pytest.sh --firefox + ng_tests: ## Run Angular tests docker pull ci.arenadata.io/functest:3.8.6.slim.buster-x64 docker run -i --rm -v $(CURDIR)/:/adcm -w /adcm/web/src ci.arenadata.io/functest:3.8.6.slim.buster-x64 /bin/sh -c "export CHROME_BIN=/usr/bin/google-chrome; npm install && ng test --watch=false" diff --git a/pytest.sh b/pytest.sh index cb14d09ad3..f8a5182561 100755 --- a/pytest.sh +++ b/pytest.sh @@ -20,7 +20,7 @@ find . -name "__pycache__" -type d -delete { # try pytest tests/ui_tests tests/functional -s -v -n auto --maxfail 30 \ --showlocals --alluredir ./allure-results/ --durations=20 \ - --reruns 2 --remote-executor-host "$SELENOID_HOST" --timeout=360 && + --reruns 2 --remote-executor-host "$SELENOID_HOST" --timeout=360 "$@" && chmod -R o+xw allure-results } || { # catch chmod -R o+xw allure-results diff --git a/python/api/cluster_serial.py b/python/api/cluster_serial.py index 67728e54e8..5abd30fb9d 100644 --- a/python/api/cluster_serial.py +++ b/python/api/cluster_serial.py @@ -379,6 +379,7 @@ def get_kwargs(self, obj): prototype_id = serializers.SerializerMethodField() display_name = serializers.CharField(read_only=True) description = serializers.CharField(read_only=True) + state = serializers.CharField(read_only=True) url = MyUrlField(read_only=True, view_name='cluster-service-component-details') def get_prototype_id(self, obj): diff --git a/python/cm/issue.py b/python/cm/issue.py index 350e723e65..348ea45e3e 100644 --- a/python/cm/issue.py +++ b/python/cm/issue.py @@ -273,7 +273,7 @@ def check_component_req(service, component): for shc in get_components_with_requires(): for r in shc[2].prototype.requires: if not check_component_req(r['service'], r['component']): - ref = f'component "{shc[2].component.name}" of service "{shc[0].prototype.name}"' + ref = f'component "{shc[2].prototype.name}" of service "{shc[0].prototype.name}"' msg = 'no required component "{}" of service "{}" for {}' err('COMPONENT_CONSTRAINT_ERROR', msg.format(r['component'], r['service'], ref)) diff --git a/tests/conftest.py b/tests/conftest.py index 36177bd60a..2007118b32 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,6 +10,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # pylint: disable=W0621 +from typing import Optional import allure import json @@ -18,6 +19,9 @@ import sys import tempfile +from _pytest.python import Function +from allure_commons.model2 import TestResult, Parameter +from allure_pytest.listener import AllureListener from selenium.common.exceptions import WebDriverException from adcm_client.wrappers.docker import ADCM @@ -65,6 +69,43 @@ def pytest_generate_tests(metafunc): metafunc.parametrize('browser', browsers, scope='session') +@pytest.hookimpl(hookwrapper=True, tryfirst=True) +def pytest_runtest_setup(item: Function): + """ + Pytest hook that overrides test parameters + In case of adss tests, parameters in allure report don't make sense unlike test ID + So, we remove all parameters in allure report but add one parameter with test ID + """ + yield + _override_allure_test_parameters(item) + + +def _override_allure_test_parameters(item: Function): + """ + Overrides all pytest parameters in allure report with test ID + """ + listener = _get_listener_by_item_if_present(item) + if listener: + test_result: TestResult = listener.allure_logger.get_test(None) + test_result.parameters = [Parameter(name="ID", value=item.callspec.id)] + + +def _get_listener_by_item_if_present(item: Function) -> Optional[AllureListener]: + """ + Find AllureListener instance in pytest pluginmanager + """ + if hasattr(item, "callspec"): + listener: AllureListener = next( + filter( + lambda x: isinstance(x, AllureListener), + item.config.pluginmanager._name2plugin.values(), # pylint: disable=protected-access + ), + None, + ) + return listener + return None + + @pytest.fixture(scope="session") def web_driver(browser): """ diff --git a/tests/functional/test_locked_objects.py b/tests/functional/test_locked_objects.py index 8ec304eef9..b5310e4ea4 100644 --- a/tests/functional/test_locked_objects.py +++ b/tests/functional/test_locked_objects.py @@ -9,223 +9,189 @@ # 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. -from time import sleep +from typing import Union import allure -import coreapi import pytest +from adcm_client.objects import Provider, Cluster, Host, ADCMClient, Service from adcm_pytest_plugin import utils -from adcm_pytest_plugin.docker_utils import DockerWrapper # pylint: disable=W0611, W0621 -from tests.library import steps -from tests.library.errorcodes import TASK_ERROR -from tests.library.utils import get_action_by_name, filter_action_by_name, wait_until +from adcm_pytest_plugin.utils import random_string @pytest.fixture() -def adcm(image, request, adcm_credentials): - repo, tag = image - dw = DockerWrapper() - adcm = dw.run_adcm(image=repo, tag=tag, pull=False) - adcm.api.auth(**adcm_credentials) - yield adcm - adcm.stop() +def prepared_cluster(sdk_client_fs: ADCMClient) -> Cluster: + uploaded_bundle = sdk_client_fs.upload_from_fs( + utils.get_data_dir(__file__, "locked_when_action_running") + ) + return uploaded_bundle.cluster_prototype().cluster_create(name=random_string()) @pytest.fixture() -def client(adcm): - return adcm.api.objects +def hostprovider(sdk_client_fs: ADCMClient) -> Provider: + provider_bundle = sdk_client_fs.upload_from_fs( + utils.get_data_dir(__file__, "host_bundle_on_any_level") + ) + return provider_bundle.provider_prototype().provider_create(random_string()) @pytest.fixture() -def prepared_cluster(client): - bundle = utils.get_data_dir(__file__, 'locked_when_action_running') - steps.upload_bundle(client, bundle) - return client.cluster.create(prototype_id=client.stack.cluster.list()[0]['id'], - name=utils.random_string()) - - -@pytest.fixture() -def hostprovider(client): - steps.upload_bundle(client, utils.get_data_dir(__file__, 'host_bundle_on_any_level')) - return steps.create_hostprovider(client) - - -@pytest.fixture() -def host(client): - host_bundle = utils.get_data_dir(__file__, 'host_bundle_on_any_level') - steps.upload_bundle(client, host_bundle) - return steps.create_host_w_default_provider(client, 'localhost') - - -def test_cluster_must_be_locked_when_action_running(client, prepared_cluster): - with allure.step('Run action: lock cluster'): - cluster = prepared_cluster - client.cluster.action.run.create( - action_id=get_action_by_name(client, cluster, 'lock-cluster')['id'], - cluster_id=cluster['id']) - with allure.step('Check if cluster is locked'): - assert client.cluster.read(cluster_id=cluster['id'])['state'] == 'locked' - - -def test_run_new_action_on_locked_cluster_must_throws_exception(client, prepared_cluster): - with allure.step('Run first action: lock cluster'): - cluster = prepared_cluster - lock_action = get_action_by_name(client, cluster, 'lock-cluster') - install_action = get_action_by_name(client, cluster, 'install') - client.cluster.action.run.create( - action_id=lock_action['id'], - cluster_id=cluster['id']) - with allure.step('Run second action: install'): - with pytest.raises(coreapi.exceptions.ErrorMessage) as e: - client.cluster.action.run.create( - action_id=install_action['id'], - cluster_id=cluster['id']) - with allure.step('Check error that object is locked'): - TASK_ERROR.equal(e) - assert "object is locked" in e.value.error['desc'] - - -def test_service_in_cluster_must_be_locked_when_cluster_action_running(client, prepared_cluster): - with allure.step('Create service and run action: lock cluster'): - cluster = prepared_cluster - service = client.cluster.service.create(cluster_id=cluster['id'], - prototype_id=client.stack.service.list()[0]['id']) - client.cluster.action.run.create( - action_id=get_action_by_name(client, cluster, 'lock-cluster')['id'], - cluster_id=cluster['id']) - with allure.step('Check if service is locked'): - assert client.cluster.service.read(cluster_id=cluster['id'], - service_id=service['id'])['state'] == 'locked' - - -def test_host_in_cluster_must_be_locked_when_cluster_action_running(client, prepared_cluster, host): - with allure.step('Create host and run action: lock cluster'): - cluster = prepared_cluster - client.cluster.host.create(cluster_id=cluster['id'], host_id=host['id']) - client.cluster.action.run.create( - action_id=get_action_by_name(client, cluster, 'lock-cluster')['id'], - cluster_id=cluster['id']) - with allure.step('Check if host is locked'): - assert client.cluster.host.read(cluster_id=cluster['id'], - host_id=host['id'])['state'] == 'locked' - - -def test_host_must_be_locked_when_host_action_running(client, host): - with allure.step('Run host action: action locker'): - client.host.action.run.create( - action_id=filter_action_by_name( - client.host.action.list(host_id=host['id']), 'action-locker' - )[0]['id'], - host_id=host['id'] - ) - with allure.step('Check if host is locked'): - assert client.host.read(host_id=host['id'])['state'] == 'locked' - - -def test_cluster_must_be_locked_when_located_host_action_running(client, prepared_cluster, host): - with allure.step('Create host and run action: action locker'): - client.cluster.host.create(cluster_id=prepared_cluster['id'], host_id=host['id']) - client.host.action.run.create( - action_id=filter_action_by_name( - client.host.action.list(host_id=host['id']), 'action-locker')[0]['id'], - host_id=host['id']) - with allure.step('Check if host and cluster are locked'): - assert client.cluster.host.read(cluster_id=prepared_cluster['id'], - host_id=host['id'])['state'] == 'locked' - assert client.cluster.read(cluster_id=prepared_cluster['id'])['state'] == 'locked' - - -def test_cluster_service_locked_when_located_host_action_running(client, prepared_cluster, host): - with allure.step('Create host and service'): - client.cluster.host.create(cluster_id=prepared_cluster['id'], host_id=host['id']) - service = client.cluster.service.create(cluster_id=prepared_cluster['id'], - prototype_id=client.stack.service.list()[0]['id']) - with allure.step('Run action: action locker'): - client.host.action.run.create( - action_id=filter_action_by_name( - client.host.action.list(host_id=host['id']), 'action-locker')[0]['id'], - host_id=host['id']) - with allure.step('Check if host, cluster and service are locked'): - assert client.cluster.host.read(cluster_id=prepared_cluster['id'], - host_id=host['id'])['state'] == 'locked' - assert client.cluster.read(cluster_id=prepared_cluster['id'])['state'] == 'locked' - assert client.cluster.service.read(cluster_id=prepared_cluster['id'], - service_id=service['id'])['state'] == 'locked' - - -def test_run_service_action_locked_all_objects_in_cluster(client, prepared_cluster, host): - with allure.step('Create host and service'): - client.cluster.host.create(cluster_id=prepared_cluster['id'], host_id=host['id']) - service = client.cluster.service.create(cluster_id=prepared_cluster['id'], - prototype_id=client.stack.service.list()[0]['id']) - with allure.step('Run action: service lock'): - client.cluster.service.action.run.create( - action_id=filter_action_by_name( - client.cluster.service.action.list(cluster_id=prepared_cluster['id'], - service_id=service['id']), - 'service-lock')[0]['id'], - cluster_id=prepared_cluster['id'], - service_id=service['id']) - sleep(1) - with allure.step('Check if host, cluster and service are locked'): - assert client.cluster.service.read(cluster_id=prepared_cluster['id'], - service_id=service['id'])['state'] == 'locked' - assert client.cluster.read(cluster_id=prepared_cluster['id'])['state'] == 'locked' - assert client.cluster.host.read(cluster_id=prepared_cluster['id'], - host_id=host['id'])['state'] == 'locked' - - -def test_cluster_should_be_unlocked_when_ansible_task_killed(client, prepared_cluster): - with allure.step('Run cluster action: lock terminate'): - task = client.cluster.action.run.create( - action_id=get_action_by_name(client, prepared_cluster, 'lock-terminate')['id'], - cluster_id=prepared_cluster['id']) - with allure.step('Check if cluster is locked and then terminate_failed'): - assert client.cluster.read(cluster_id=prepared_cluster['id'])['state'] == 'locked' - wait_until(client, task) - assert client.cluster.read(cluster_id=prepared_cluster['id'])['state'] == 'terminate_failed' - - -def test_host_should_be_unlocked_when_ansible_task_killed(client, prepared_cluster, host): - with allure.step('Create host'): - client.cluster.host.create(cluster_id=prepared_cluster['id'], host_id=host['id']) - with allure.step('Run action: lock terminate'): - task = client.cluster.action.run.create( - action_id=get_action_by_name(client, prepared_cluster, 'lock-terminate')['id'], - cluster_id=prepared_cluster['id']) - with allure.step('Check if host is locked and then created'): - assert client.host.read(host_id=host['id'])['state'] == 'locked' - wait_until(client, task) - assert client.host.read(host_id=host['id'])['state'] == 'created' - - -def test_service_should_be_unlocked_when_ansible_task_killed(client, prepared_cluster): - with allure.step('Create service'): - service = client.cluster.service.create(cluster_id=prepared_cluster['id'], - prototype_id=client.stack.service.list()[0]['id']) - with allure.step('Run action: lock-terminate'): - task = client.cluster.action.run.create( - action_id=get_action_by_name(client, prepared_cluster, 'lock-terminate')['id'], - cluster_id=prepared_cluster['id']) - with allure.step('Check if service is locked and then created'): - assert client.cluster.service.read(cluster_id=prepared_cluster['id'], - service_id=service['id'])['state'] == 'locked' - wait_until(client, task) - assert client.cluster.service.read(cluster_id=prepared_cluster['id'], - service_id=service['id'])['state'] == 'created' - - -def test_hostprovider_must_be_unlocked_when_his_task_finished(client, hostprovider): - with allure.step('Run action: action locker and create hostprovider'): - action_id = filter_action_by_name( - client.provider.action.list(provider_id=hostprovider['id']), 'action-locker')[0]['id'] - task = client.provider.action.run.create( - action_id=action_id, - provider_id=hostprovider['id'] - ) - with allure.step('Check if provider is locked and then created'): - assert client.provider.read(provider_id=hostprovider['id'])['state'] == 'locked' - wait_until(client, task) - assert client.provider.read(provider_id=hostprovider['id'])['state'] == 'created' +def host(hostprovider: Provider) -> Host: + return hostprovider.host_create(random_string()) + + +def _check_locked_object(obj: Union[Cluster, Service, Provider, Host]): + """ + Assert that object state is 'locked' and action list is empty + """ + obj.reread() + assert obj.state == "locked" + assert ( + obj.action_list() == [] + ), f"{obj.__class__.__name__} action list not empty. {obj.__class__.__name__} not locked" + + +def test_cluster_must_be_locked_when_action_running(prepared_cluster: Cluster): + with allure.step("Run action: lock-cluster for cluster"): + prepared_cluster.action_run(name="lock-cluster") + with allure.step("Check if cluster state is 'locked'"): + prepared_cluster.reread() + _check_locked_object(prepared_cluster) + + +def test_run_new_action_on_locked_cluster_must_throws_exception( + prepared_cluster: Cluster, +): + with allure.step("Run action: lock-cluster for cluster"): + prepared_cluster.action_run(name="lock-cluster") + with allure.step("Check that cluster state is 'locked'"): + _check_locked_object(prepared_cluster) + + +def test_service_in_cluster_must_be_locked_when_cluster_action_running( + prepared_cluster: Cluster, +): + with allure.step("Add service and run action: lock-cluster for cluster"): + added_service = prepared_cluster.service_add(name="bookkeeper") + prepared_cluster.action_run(name="lock-cluster") + with allure.step("Check if service state is 'locked'"): + _check_locked_object(added_service) + + +def test_host_in_cluster_must_be_locked_when_cluster_action_running( + prepared_cluster: Cluster, host: Host +): + with allure.step("Add host and run action: lock-cluster for cluster"): + prepared_cluster.host_add(host) + prepared_cluster.action_run(name="lock-cluster") + with allure.step("Check if host state is 'locked'"): + _check_locked_object(prepared_cluster) + + +def test_host_must_be_locked_when_host_action_running(host): + with allure.step("Run host action: action-locker"): + host.action_run(name="action-locker") + with allure.step("Check if host state is 'locked'"): + _check_locked_object(host) + + +def test_cluster_must_be_locked_when_located_host_action_running( + prepared_cluster: Cluster, host: Host +): + with allure.step("Add host and run action: action-locker"): + prepared_cluster.host_add(host) + host.action_run(name="action-locker") + with allure.step("Check if host and cluster states are 'locked'"): + _check_locked_object(prepared_cluster) + _check_locked_object(host) + + +def test_cluster_service_locked_when_located_host_action_running( + prepared_cluster: Cluster, host: Host +): + with allure.step("Add host and service"): + prepared_cluster.host_add(host) + added_service = prepared_cluster.service_add(name="bookkeeper") + with allure.step("Run action: action-locker for host"): + host.action_run(name="action-locker") + with allure.step("Check if host, cluster and service states are 'locked'"): + _check_locked_object(prepared_cluster) + _check_locked_object(host) + _check_locked_object(added_service) + + +def test_run_service_action_locked_all_objects_in_cluster( + prepared_cluster: Cluster, host: Host +): + with allure.step("Add host and service"): + prepared_cluster.host_add(host) + added_service = prepared_cluster.service_add(name="bookkeeper") + with allure.step("Run action: service-lock for service"): + added_service.action_run(name="service-lock") + with allure.step("Check if host, cluster and service states are 'locked'"): + _check_locked_object(prepared_cluster) + _check_locked_object(host) + _check_locked_object(added_service) + + +def test_cluster_should_be_unlocked_when_ansible_task_killed(prepared_cluster: Cluster): + with allure.step("Run cluster action: lock-terminate for cluster"): + task = prepared_cluster.action_run(name="lock-terminate") + with allure.step("Check if cluster state is 'locked' and then 'terminate_failed'"): + _check_locked_object(prepared_cluster) + task.wait() + prepared_cluster.reread() + assert prepared_cluster.state == "terminate_failed" + + +def test_host_should_be_unlocked_when_ansible_task_killed( + prepared_cluster: Cluster, host: Host +): + with allure.step("Add host"): + prepared_cluster.host_add(host) + with allure.step("Run action: lock-terminate for cluster"): + task = prepared_cluster.action_run(name="lock-terminate") + + with allure.step("Check if host state is 'locked' and then is 'created'"): + _check_locked_object(host) + task.wait() + host.reread() + assert host.state == "created" + + +def test_service_should_be_unlocked_when_ansible_task_killed(prepared_cluster: Cluster): + with allure.step("Add service"): + added_service = prepared_cluster.service_add(name="bookkeeper") + with allure.step("Run action: lock-terminate for cluster"): + task = prepared_cluster.action_run(name="lock-terminate") + with allure.step("Check if service state is 'locked' and then is 'created'"): + _check_locked_object(added_service) + task.wait() + added_service.reread() + assert added_service.state == "created" + + +def test_hostprovider_must_be_unlocked_when_his_task_finished(hostprovider: Provider): + with allure.step("Run action: action-locker for hostprovider"): + task = hostprovider.action_run(name="action-locker") + with allure.step("Check if provider state is 'locked' and then is 'created'"): + _check_locked_object(hostprovider) + task.wait() + hostprovider.reread() + assert hostprovider.state == "created" + + +def test_host_and_hostprovider_must_be_unlocked_when_his_task_finished( + hostprovider: Provider, host: Host +): + with allure.step("Run action: action-locker for hostprovider"): + task = hostprovider.action_run(name="action-locker") + with allure.step("Check if provider state is 'locked' and then is 'created'"): + _check_locked_object(hostprovider) + _check_locked_object(host) + task.wait() + hostprovider.reread() + host.reread() + assert hostprovider.state == "created" + assert host.state == "created" diff --git a/tests/ui_tests/app/configuration.py b/tests/ui_tests/app/configuration.py index 7972199d53..457a7ec0ad 100644 --- a/tests/ui_tests/app/configuration.py +++ b/tests/ui_tests/app/configuration.py @@ -22,8 +22,12 @@ class Configuration(BasePage): def __init__(self, driver, url=None): super().__init__(driver) - if url: - self.get(url, "config") + self.url = url + if self.url: + self.get(self.url, "config") + self._wait_for_page_loaded() + + def _wait_for_page_loaded(self): self._wait_element_present(ConfigurationLocators.app_conf_form, 15) # 30 seconds timeout here is caused by possible long load of config page self._wait_element_present(ConfigurationLocators.load_marker, 30) @@ -154,6 +158,14 @@ def get_structure_values(field) -> list: def get_field_value(input_field): return input_field.get_attribute("value") + @staticmethod + def get_field_input(field): + return field.find_element(*Common.mat_input_element) + + @staticmethod + def get_field_checkbox(field): + return field.find_element(*Common.mat_checkbox) + def get_field_groups(self): return self.driver.find_elements(*ConfigurationLocators.field_group) @@ -211,6 +223,10 @@ def activate_group_by_name(self, group_name): if 'mat-checked' not in toogle.get_attribute("class"): toogle.click() + @allure.step('Get group by name: {group_name}') + def get_group_by_name(self, group_name): + return self._get_group_element_by_name(group_name) + @allure.step('Save configuration') def save_configuration(self): self._getelement(ConfigurationLocators.config_save_button).click() @@ -219,7 +235,7 @@ def save_configuration(self): def click_advanced(self): self._click_element(Common.mat_checkbox, name="Advanced") - @allure.step('Click advanced') + @allure.step('Get form field text') def get_form_field_text(self, form_field_element): self._wait_element_present_in_sublement(form_field_element, Common.mat_form_field) form_field = form_field_element.find_elements(*Common.mat_form_field)[0] @@ -324,3 +340,8 @@ def check_that_fields_and_group_are_invisible(self): assert not field.is_displayed(), field.get_attribute("class") group_names = self.get_group_elements() assert not group_names, group_names + + @allure.step('Refresh configuration') + def refresh(self): + self.driver.refresh() + self._wait_for_page_loaded() diff --git a/tests/ui_tests/test_save_configuration_button.py b/tests/ui_tests/test_save_configuration_button.py new file mode 100644 index 0000000000..36914d7020 --- /dev/null +++ b/tests/ui_tests/test_save_configuration_button.py @@ -0,0 +1,324 @@ +# pylint: disable=W0621 +import itertools +import os +import shutil + +import allure +import pytest +import yaml +from adcm_client.objects import ADCMClient, Service, Host, Cluster, Bundle, Provider +from adcm_pytest_plugin.utils import random_string, get_data_dir + +from tests.ui_tests.app.configuration import Configuration +from tests.ui_tests.utils import ( + ClusterDefinition, + ProviderDefinition, + HostDefinition, + ServiceDefinition, + FieldDefinition, + GroupDefinition, + BundleObjectDefinition, +) + +GROUP_NAME = "group" +CLUSTER_NAME = "cluster" +SERVICE_NAME = "service" +PROVIDER_NAME = "provider" +HOST_NAME = "host" +PROPERTY_NAME = "property" + +CONFIG_DATA = { + "string": "some_string_value", + "text": "_".join("some_string_value" * 17), + "integer": 42, + "boolean": True, +} + +CONFIG_USE_GROUP = "CONFIG_USE_GROUP" +CONFIG_USE_ADVANCED = "CONFIG_USE_ADVANCED" + +CONFIG_OPTS = [CONFIG_USE_GROUP, CONFIG_USE_ADVANCED] + + +def _generate_bundle_config(bundle_type, entity_type, prop_types): + params = [] + ids = [] + + for mask in itertools.product([True, False], repeat=len(CONFIG_OPTS)): + selected_opts = [opt for (i, opt) in enumerate(CONFIG_OPTS) if mask[i]] + + # bundle + bundle_proto = [] + if bundle_type == "cluster_service": + bundle_proto.append(ClusterDefinition(name=CLUSTER_NAME, version="0.1-cluster")) + bundle_proto.append(ServiceDefinition(name=SERVICE_NAME, version="0.1-service")) + elif bundle_type == "provider_host": + bundle_proto.append(ProviderDefinition(name=PROVIDER_NAME, version="0.1-provider")) + bundle_proto.append(HostDefinition(name=HOST_NAME, version="0.1-host")) + + # config + config_proto = [ + FieldDefinition(prop_type=prop_type, prop_name=f"{prop_type}_{PROPERTY_NAME}") + for prop_type in prop_types if prop_type in ["string", "text", "boolean", "integer"] + ] # scalar types + + if "structure" in prop_types: + struct_property = FieldDefinition( + prop_type="structure", prop_name=f"structure_{PROPERTY_NAME}" + ) + struct_property["yspec"] = "struct_conf.yaml" + config_proto.append(struct_property) + + if CONFIG_USE_GROUP in selected_opts: + config_proto = [GroupDefinition(name=GROUP_NAME).add_fields(*config_proto)] + if CONFIG_USE_ADVANCED in selected_opts: + for k, _ in enumerate(config_proto): + config_proto[k].set_advanced(True) + + # assign config + for k, v in enumerate(bundle_proto): + if v["type"] == entity_type: + bundle_proto[k]["config"] = config_proto + + ids.append( + "-".join(selected_opts).lower() + if len(selected_opts) > 0 + else "default" + ) + params.append( + { + "opts": (selected_opts, prop_types), + "bundle": BundleObjectDefinition.to_dict(bundle_proto), + } + ) + + return pytest.mark.parametrize("bundle_content", params, ids=ids, indirect=True) + + +@pytest.fixture() +def bundle_content(request, tmp_path): + struct_filename = "struct_conf.yaml" + struct_path_src = os.path.join(get_data_dir(__file__), struct_filename) + struct_path_dest = os.path.join(tmp_path, struct_filename) + shutil.copyfile( + struct_path_src, + struct_path_dest, + ) + + bundle_filename = "config.yaml" + bundle_path = os.path.join(tmp_path, bundle_filename) + with allure.step("Dump YAML config to file"): + with open(bundle_path, "w") as stream: + yaml.dump(request.param["bundle"], stream, sort_keys=False) + allure.attach.file( + bundle_path, + name=bundle_filename, + attachment_type=allure.attachment_type.YAML, + ) + + yield request.param["opts"], tmp_path + + os.remove(bundle_path) + os.remove(struct_path_dest) + + +@pytest.fixture() +def bundle(bundle_content, sdk_client_fs: ADCMClient) -> Bundle: + """Assume request.param to be path to the bundle""" + _, bundle_path = bundle_content + return sdk_client_fs.upload_from_fs(bundle_path) + + +@pytest.fixture() +def cluster(bundle: Bundle) -> Cluster: + return bundle.cluster_create(name=CLUSTER_NAME) + + +@pytest.fixture() +def cluster_config_page(app_fs, cluster: Cluster, login_to_adcm): + return Configuration( + app_fs.driver, + "{}/cluster/{}/config".format( + app_fs.adcm.url, cluster.cluster_id + ) + ) + + +@pytest.fixture() +def service(cluster: Cluster, sdk_client_fs: ADCMClient) -> Service: + cluster.service_add(name=SERVICE_NAME) + return cluster.service(name=SERVICE_NAME) + + +@pytest.fixture() +def service_config_page(app_fs, service: Service, login_to_adcm) -> Configuration: + return Configuration( + app_fs.driver, + "{}/cluster/{}/service/{}/config".format( + app_fs.adcm.url, service.cluster_id, service.id + ), + ) + + +@pytest.fixture() +def provider(bundle: Bundle) -> Provider: + return bundle.provider_create(name=PROVIDER_NAME + random_string()) + + +@pytest.fixture() +def provider_config_page(app_fs, provider: Provider, login_to_adcm) -> Configuration: + return Configuration( + app_fs.driver, + "{}/provider/{}/config".format( + app_fs.adcm.url, provider.provider_id + ), + ) + + +@pytest.fixture() +def host(provider: Provider) -> Host: + return provider.host_create(fqdn=f"{HOST_NAME}_{random_string()}") + + +@pytest.fixture() +def host_config_page(app_fs, host: Host, login_to_adcm) -> Configuration: + return Configuration( + app_fs.driver, + "{}/host/{}/config".format(app_fs.adcm.url, host.id), + ) + + +def _get_test_value(value_type): + return CONFIG_DATA[value_type] if value_type in CONFIG_DATA else None + + +def _update_config_property(config_page: Configuration, field, field_type: str): + if field_type in ["string", "text", "integer"]: + field_input = config_page.get_field_input(field) + field_input.send_keys(_get_test_value(field_type)) + if field_type == "boolean": + config_page.get_field_checkbox(field).click() + if field_type == "structure": + nested_field = config_page.get_form_field(field) + field_input = config_page.get_field_input(nested_field) + # hardcoded field value, see struct_conf.yaml + field_input.send_keys(_get_test_value("string")) + + assert config_page.save_button_status() + + +def _test_save_configuration_button( + config_page: Configuration, prop_types: list, group_name=None, use_advanced=False +): + if use_advanced: + config_page.click_advanced() + if group_name: + config_page.activate_group_by_name(group_name) + + assert len(config_page.get_app_fields()) == len( + prop_types + ), "Unexpected count of fields" + with allure.step("Update config properties"): + for field_type, field in zip(prop_types, config_page.get_app_fields()): + _update_config_property(config_page, field, field_type) + + config_page.save_configuration() + config_page.refresh() + + if use_advanced: + config_page.click_advanced() + if group_name: + group = config_page.get_group_by_name(group_name) + config_page.assert_group_status(group) + + with allure.step("Check config properties values"): + for field_type, field in zip(prop_types, config_page.get_app_fields()): + value_to_check = _get_test_value(field_type) + if field_type == "boolean": + assert ( + config_page.get_checkbox_element_status( + config_page.get_field_checkbox(field) + ) + == value_to_check + ) + else: + if field_type == "structure": + # workaround + field_type = "string" + field = config_page.get_form_field(field) + value_to_check = _get_test_value(field_type) + config_page.assert_field_content_equal( + field_type, field, value_to_check + ) + + +def _get_default_props_list() -> list: + return [ + "string", + "text", + "boolean", + "integer", + "structure", + ] + + +@_generate_bundle_config( + bundle_type="cluster_service", + entity_type="cluster", + prop_types=_get_default_props_list(), +) +def test_cluster_configuration_save_button(bundle_content, bundle, cluster_config_page): + (selected_opts, prop_types), _ = bundle_content + _test_save_configuration_button( + cluster_config_page, + group_name=GROUP_NAME if CONFIG_USE_GROUP in selected_opts else None, + use_advanced=CONFIG_USE_ADVANCED in selected_opts, + prop_types=prop_types, + ) + + +@_generate_bundle_config( + bundle_type="cluster_service", + entity_type="service", + prop_types=_get_default_props_list(), +) +def test_service_configuration_save_button(bundle_content, bundle, service_config_page): + (selected_opts, prop_types), _ = bundle_content + _test_save_configuration_button( + service_config_page, + group_name=GROUP_NAME if CONFIG_USE_GROUP in selected_opts else None, + use_advanced=CONFIG_USE_ADVANCED in selected_opts, + prop_types=prop_types, + ) + + +@_generate_bundle_config( + bundle_type="provider_host", + entity_type="provider", + prop_types=_get_default_props_list(), +) +def test_provider_configuration_save_button( + bundle_content, bundle, provider_config_page +): + (selected_opts, prop_types), _ = bundle_content + _test_save_configuration_button( + provider_config_page, + group_name=GROUP_NAME if CONFIG_USE_GROUP in selected_opts else None, + use_advanced=CONFIG_USE_ADVANCED in selected_opts, + prop_types=prop_types, + ) + + +@_generate_bundle_config( + bundle_type="provider_host", + entity_type="host", + prop_types=_get_default_props_list(), +) +def test_host_configuration_save_button(bundle_content, bundle, host_config_page): + (selected_opts, prop_types), _ = bundle_content + _test_save_configuration_button( + host_config_page, + group_name=GROUP_NAME if CONFIG_USE_GROUP in selected_opts else None, + use_advanced=CONFIG_USE_ADVANCED in selected_opts, + prop_types=prop_types, + ) diff --git a/tests/ui_tests/test_save_configuration_button_data/struct_conf.yaml b/tests/ui_tests/test_save_configuration_button_data/struct_conf.yaml new file mode 100644 index 0000000000..c2b404b669 --- /dev/null +++ b/tests/ui_tests/test_save_configuration_button_data/struct_conf.yaml @@ -0,0 +1,7 @@ +root: + match: dict + items: + some_item: string + +string: + match: string diff --git a/tests/ui_tests/utils.py b/tests/ui_tests/utils.py index bea78ef7ee..6b16daf485 100644 --- a/tests/ui_tests/utils.py +++ b/tests/ui_tests/utils.py @@ -1,15 +1,92 @@ -from adcm_client.objects import ADCMClient +# pylint: disable=too-many-ancestors + +from collections import UserDict + +from adcm_client.objects import ADCMClient, Cluster from adcm_pytest_plugin.utils import random_string from tests.ui_tests.app.configuration import Configuration import allure -@allure.step('Prepare cluster and get config') -def prepare_cluster_and_get_config(sdk_client: ADCMClient, path, app): +def prepare_cluster(sdk_client: ADCMClient, path) -> Cluster: bundle = sdk_client.upload_from_fs(path) cluster_name = "_".join(path.split("/")[-1:] + [random_string()]) cluster = bundle.cluster_create(name=cluster_name) + return cluster + + +@allure.step("Prepare cluster and get config") +def prepare_cluster_and_get_config(sdk_client: ADCMClient, path, app): + cluster = prepare_cluster(sdk_client, path) config = Configuration(app.driver, f"{app.adcm.url}/cluster/{cluster.cluster_id}/config") return cluster, config + + +class BundleObjectDefinition(UserDict): + + def __init__(self, obj_type=None, name=None, version=None): + super().__init__() + self["type"] = obj_type + self["name"] = name + if version is not None: + self["version"] = version + + def _set_ui_option(self, option, value): + if "ui_options" not in self: + self["ui_options"] = {} + self["ui_options"][option] = value + + def set_advanced(self, value): + self._set_ui_option("advanced", value) + + @classmethod + def to_dict(cls, obj) -> dict: + if isinstance(obj, cls): + obj = cls.to_dict(obj.data) + elif isinstance(obj, list): + for i, v in enumerate(obj): + obj[i] = cls.to_dict(v) + elif isinstance(obj, dict): + for k in obj: + obj[k] = cls.to_dict(obj[k]) + return obj + + +class ClusterDefinition(BundleObjectDefinition): + def __init__(self, name=None, version=None): + super().__init__(obj_type="cluster", name=name, version=version) + + +class ServiceDefinition(BundleObjectDefinition): + def __init__(self, name=None, version=None): + super().__init__(obj_type="service", name=name, version=version) + + +class ProviderDefinition(BundleObjectDefinition): + def __init__(self, name=None, version=None): + super().__init__(obj_type="provider", name=name, version=version) + + +class HostDefinition(BundleObjectDefinition): + def __init__(self, name=None, version=None): + super().__init__(obj_type="host", name=name, version=version) + + +class GroupDefinition(BundleObjectDefinition): + def __init__(self, name=None): + super().__init__(obj_type="group", name=name) + self["activatable"] = True + self["subs"] = [] + + def add_fields(self, *fields): + for t in fields: + self["subs"].append(t) + return self + + +class FieldDefinition(BundleObjectDefinition): + def __init__(self, prop_type, prop_name=None): + super().__init__(obj_type=prop_type, name=prop_name) + self["required"] = False