Skip to content

Commit

Permalink
Initial end-to-end tests for separate quota per object store
Browse files Browse the repository at this point in the history
  • Loading branch information
jmchilton committed Mar 16, 2023
1 parent 0a34ae2 commit b0bbf02
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 46 deletions.
8 changes: 6 additions & 2 deletions client/src/components/User/DiskUsage/Quota/QuotaUsageBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,15 @@ defineExpose({
</span>
{{ storageSourceText }}
</component>
<component :is="usageTag" v-if="!compact">
<component
:is="usageTag"
v-if="!compact"
:data-quota-usage="quotaUsage.totalDiskUsageInBytes"
class="quota-usage">
<b>{{ quotaUsage.niceTotalDiskUsage }}</b>
<span v-if="quotaHasLimit"> of {{ quotaUsage.niceQuota }}</span> used
</component>
<span v-if="quotaHasLimit && !compact" class="quota-percent-text">
<span v-if="quotaHasLimit && !compact" class="quota-percent-text" :data-quota-percent="quotaUsage.quotaPercent">
{{ quotaUsage.quotaPercent }}{{ percentOfDiskQuotaUsedText }}
</span>
<b-progress
Expand Down
12 changes: 11 additions & 1 deletion client/src/utils/navigation/navigation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,10 @@ object_store_details:
selectors:
stored_by_name: '.display-os-by-name'
badge_of_type: '.object-store-badge-wrapper [data-badge-type="${type}"]'

usage_details: '.quota-usage-bar'
usage_bytes: '.quota-usage'
usage_percent: '.quota-percent-text'
usage_progress: '.quota-usage-bar .progress'

history_panel:
menu:
Expand Down Expand Up @@ -781,6 +784,13 @@ admin:
search_results: '#shed-search-results'
upgrade_notification: '#repository-table .badge'

quota:
selectors:
add_new: '.quotas .manage-table-actions .action-button'
items: '.quotas tbody tr td'
add_form: "[url='/admin/create_quota']"
add_form_submit: "[url='/admin/create_quota'] #submit"

index:
selectors:
datatypes: '#admin-link-datatypes'
Expand Down
2 changes: 1 addition & 1 deletion lib/galaxy/navigation/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def description(self) -> 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]:
Expand Down
21 changes: 17 additions & 4 deletions lib/galaxy/selenium/has_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
56 changes: 56 additions & 0 deletions lib/galaxy/selenium/navigates_galaxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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")
Expand Down
110 changes: 72 additions & 38 deletions test/integration_selenium/test_objectstore_selection.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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"

0 comments on commit b0bbf02

Please sign in to comment.