From b0bbf02d6ad9c2dd49281b9684ae0571a0083fce Mon Sep 17 00:00:00 2001 From: John Chilton Date: Tue, 14 Mar 2023 08:43:14 -0400 Subject: [PATCH] Initial end-to-end tests for separate quota per object store --- .../User/DiskUsage/Quota/QuotaUsageBar.vue | 8 +- client/src/utils/navigation/navigation.yml | 12 +- lib/galaxy/navigation/components.py | 2 +- lib/galaxy/selenium/has_driver.py | 21 +++- lib/galaxy/selenium/navigates_galaxy.py | 56 +++++++++ .../test_objectstore_selection.py | 110 ++++++++++++------ 6 files changed, 163 insertions(+), 46 deletions(-) diff --git a/client/src/components/User/DiskUsage/Quota/QuotaUsageBar.vue b/client/src/components/User/DiskUsage/Quota/QuotaUsageBar.vue index ce3dcca4dc81..a1319b2474d2 100644 --- a/client/src/components/User/DiskUsage/Quota/QuotaUsageBar.vue +++ b/client/src/components/User/DiskUsage/Quota/QuotaUsageBar.vue @@ -60,11 +60,15 @@ defineExpose({ {{ storageSourceText }} - + {{ quotaUsage.niceTotalDiskUsage }} of {{ quotaUsage.niceQuota }} used - + {{ quotaUsage.quotaPercent }}{{ percentOfDiskQuotaUsedText }} str: @property @abc.abstractmethod def component_locator(self) -> LocatorT: - """Return a (by, selector) Selenium elment locator tuple for this selector.""" + """Return a (by, selector) Selenium element locator tuple for this selector.""" @property def selenium_locator(self) -> Tuple[str, str]: diff --git a/lib/galaxy/selenium/has_driver.py b/lib/galaxy/selenium/has_driver.py index 1d122111b048..2dcb4fd954fd 100644 --- a/lib/galaxy/selenium/has_driver.py +++ b/lib/galaxy/selenium/has_driver.py @@ -11,7 +11,10 @@ Union, ) -from selenium.common.exceptions import TimeoutException as SeleniumTimeoutException +from selenium.common.exceptions import ( + NoSuchElementException, + TimeoutException as SeleniumTimeoutException, +) from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys @@ -76,8 +79,15 @@ def assert_selector_absent(self, selector: str): def find_elements(self, selector_template: Target) -> List[WebElement]: return self.driver.find_elements(*selector_template.element_locator) - def assert_absent(self, selector_template: Target): - assert len(self.find_elements(selector_template)) == 0 + def assert_absent(self, selector_template: Target) -> None: + elements = self.find_elements(selector_template) + if len(elements) != 0: + description = selector_template.description + any_displayed = False + for element in elements: + any_displayed = any_displayed or element.is_displayed() + msg = f"Expected DOM elements [{elements}] to be empty for selector target {description} - any actually displayed? [{any_displayed}]" + raise AssertionError(msg) def element_absent(self, selector_template: Target) -> bool: return len(self.find_elements(selector_template)) == 0 @@ -239,7 +249,10 @@ def click_selector(self, selector: str): def fill(self, form: WebElement, info: dict): for key, value in info.items(): - input_element = form.find_element(By.NAME, key) + try: + input_element = form.find_element(By.NAME, key) + except NoSuchElementException: + input_element = form.find_element(By.ID, key) input_element.send_keys(value) def click_submit(self, form: WebElement): diff --git a/lib/galaxy/selenium/navigates_galaxy.py b/lib/galaxy/selenium/navigates_galaxy.py index fb451c3fe36c..eaf81c0ad07d 100644 --- a/lib/galaxy/selenium/navigates_galaxy.py +++ b/lib/galaxy/selenium/navigates_galaxy.py @@ -393,6 +393,11 @@ def history_panel_wait_for_hid_ok(self, hid, allowed_force_refreshes=0): def history_panel_wait_for_hid_deferred(self, hid, allowed_force_refreshes=0): return self.history_panel_wait_for_hid_state(hid, "deferred", allowed_force_refreshes=allowed_force_refreshes) + def wait_for_hid_ok_and_open_details(self, hid): + self.history_panel_wait_for_hid_ok(hid) + self.history_panel_click_item_title(hid=hid) + self.history_panel_item_view_dataset_details(hid) + def history_panel_item_component(self, history_item=None, hid=None, multi_history_panel=False): assert hid return self.content_item_by_attributes(hid=hid, multi_history_panel=multi_history_panel) @@ -1078,6 +1083,41 @@ def navigate_to_pages(self): def admin_open(self): self.components.masthead.admin.wait_for_and_click() + def create_quota( + self, + name: Optional[str] = None, + description: Optional[str] = None, + amount: Optional[str] = None, + quota_source_label: Optional[str] = None, + user: Optional[str] = None, + ): + admin_component = self.components.admin + + self.admin_open() + quota_link = admin_component.index.quotas + quota_link.wait_for_and_click() + quota_component = admin_component.quota + + quota_component.add_new.wait_for_and_click() + form = quota_component.add_form.wait_for_visible() + + name = name or self._get_random_name() + description = description or f"quota description for {name}" + amount = amount or "" + self.fill( + form, + { + "name": name, + "description": description, + "amount": amount, + }, + ) + if quota_source_label: + self.select2_set_value("#quota_source_label", quota_source_label) + if user: + self.select2_set_value("#in_users", user) + quota_component.add_form_submit.wait_for_and_click() + def select_dataset_from_lib_import_modal(self, filenames): for name in filenames: self.components.libraries.folder.select_import_dir_item(name=name).wait_for_and_click() @@ -1376,6 +1416,22 @@ def datasource_tool_open(self, tool_id): self.driver.execute_script("arguments[0].scrollIntoView(true);", tool_element) tool_link.wait_for_and_click() + def run_environment_test_tool(self, inttest_value="42", select_storage: Optional[str] = None): + self.home() + self.tool_open("environment_variables") + if select_storage: + self.components.tool_form.storage_options.wait_for_and_click() + self.select_storage(select_storage) + self.tool_set_value("inttest", inttest_value) + self.tool_form_execute() + + def select_storage(self, storage_id: str) -> None: + selection_component = self.components.preferences.object_store_selection + selection_component.option_buttons.wait_for_present() + button = selection_component.option_button(object_store_id=storage_id) + button.wait_for_and_click() + selection_component.option_buttons.wait_for_absent_or_hidden() + def create_page_and_edit(self, name=None, slug=None, screenshot_name=None): name = self.create_page(name=name, slug=slug, screenshot_name=screenshot_name) self.click_grid_popup_option(name, "Edit content") diff --git a/test/integration_selenium/test_objectstore_selection.py b/test/integration_selenium/test_objectstore_selection.py index cbc3b2ac4d64..3e96196d1b42 100644 --- a/test/integration_selenium/test_objectstore_selection.py +++ b/test/integration_selenium/test_objectstore_selection.py @@ -1,8 +1,5 @@ import string -from typing import ( - Optional, - TYPE_CHECKING, -) +from typing import TYPE_CHECKING from galaxy_test.driver.integration_util import ConfiguresObjectStores from galaxy_test.selenium.framework import managed_history @@ -96,10 +93,8 @@ def handle_galaxy_config_kwds(cls, config): @selenium_test @managed_history def test_0_tools_to_default(self): - self._run_environment_test_tool() - self.history_panel_wait_for_hid_ok(1) - self.history_panel_click_item_title(hid=1) - self.history_panel_item_view_dataset_details(1) + self.run_environment_test_tool() + self.wait_for_hid_ok_and_open_details(1) details = self.components.object_store_details text = details.stored_by_name.wait_for_text() assert "High Performance Storage" in text @@ -109,11 +104,8 @@ def test_0_tools_to_default(self): @selenium_test @managed_history def test_1_tools_override_run(self): - self._run_environment_test_tool(select_storage="second") - - self.history_panel_wait_for_hid_ok(1) - self.history_panel_click_item_title(hid=1) - self.history_panel_item_view_dataset_details(1) + self.run_environment_test_tool(select_storage="second") + self.wait_for_hid_ok_and_open_details(1) details = self.components.object_store_details text = details.stored_by_name.wait_for_text() assert "Second Tier Storage" in text @@ -125,11 +117,9 @@ def test_2_user_override(self): self.navigate_to_user_preferences() preferences = self.components.preferences preferences.preferred_storage.wait_for_and_click() - self._select_storage("second") - self._run_environment_test_tool() - self.history_panel_wait_for_hid_ok(1) - self.history_panel_click_item_title(hid=1) - self.history_panel_item_view_dataset_details(1) + self.select_storage("second") + self.run_environment_test_tool() + self.wait_for_hid_ok_and_open_details(1) details = self.components.object_store_details text = details.stored_by_name.wait_for_text() assert "Second Tier Storage" in text @@ -145,30 +135,74 @@ def test_3_user_un_override(self): self.navigate_to_user_preferences() preferences = self.components.preferences preferences.preferred_storage.wait_for_and_click() - self._select_storage("__null__") + self.select_storage("__null__") - self._run_environment_test_tool() - self.history_panel_wait_for_hid_ok(1) - self.history_panel_click_item_title(hid=1) - self.history_panel_item_view_dataset_details(1) + self.run_environment_test_tool() + self.wait_for_hid_ok_and_open_details(1) details = self.components.object_store_details text = details.stored_by_name.wait_for_text() assert "High Performance Storage" in text details.badge_of_type(type="faster").wait_for_present() details.badge_of_type(type="more_stable").wait_for_present() - def _run_environment_test_tool(self, inttest_value="42", select_storage: Optional[str] = None): - self.home() - self.tool_open("environment_variables") - if select_storage: - self.components.tool_form.storage_options.wait_for_and_click() - self._select_storage(select_storage) - self.tool_set_value("inttest", inttest_value) - self.tool_form_execute() - - def _select_storage(self, storage_id: str) -> None: - selection_component = self.components.preferences.object_store_selection - selection_component.option_buttons.wait_for_present() - button = selection_component.option_button(object_store_id=storage_id) - button.wait_for_and_click() - selection_component.option_buttons.wait_for_absent_or_hidden() + +class TestMultipleQuotasSeleniumIntegration(SeleniumIntegrationTestCase, ConfiguresObjectStores): + dataset_populator: "SeleniumSessionDatasetPopulator" + run_as_admin = True + + @classmethod + def handle_galaxy_config_kwds(cls, config): + cls._configure_object_store(MSI_EXAMPLE_OBJECT_STORE_CONFIG_TEMPLATE, config) + config["enable_quotas"] = True + + @selenium_test + def test_multiple_quota_sources_for_user(self): + expected_bytes = 17 + + # create a user to create a quota for... + user_email = self._get_random_email("quota_user") + self.register(user_email) + self.logout() + + # give them a quota on second_tier of 10 times the + # size of the output of the test tool. + + self.admin_login() + quota_name = self._get_random_name(prefix="secondquota") + self.create_quota( + name=quota_name, + amount=f"{expected_bytes * 10} B", + quota_source_label="second_tier", + user=user_email, + ) + admin_component = self.components.admin + quota_component = admin_component.quota + quota_component.items.wait_for_element_count_of_at_least(1) + self.logout() + + # run the tool twice - once in the default object store without + # quota configured and once in second_tier storage with a quota + # configured. + self.submit_login(user_email) + self.run_environment_test_tool() + self.run_environment_test_tool(select_storage="second") + + # Assert no quota information on an object store without + # quota configured and check the usage and percent on the + # the dataset stored in an object store with a configured + # quota. + + details = self.components.object_store_details + self.wait_for_hid_ok_and_open_details(1) + details.usage_percent.assert_absent() + + self.wait_for_hid_ok_and_open_details(2) + + usage_summary_el = details.usage_details.wait_for_visible() + assert usage_summary_el.get_attribute("quota-source-label") == "second_tier" + + bytes_el = details.usage_bytes.wait_for_visible() + assert bytes_el.get_attribute("data-quota-usage") == f"{expected_bytes}" + + percent_el = details.usage_percent.wait_for_visible() + assert percent_el.get_attribute("data-quota-percent") == "10"