diff --git a/samplesheets/templates/samplesheets/irods_access_tickets.html b/samplesheets/templates/samplesheets/irods_access_tickets.html index 31790db7..b07ee17a 100644 --- a/samplesheets/templates/samplesheets/irods_access_tickets.html +++ b/samplesheets/templates/samplesheets/irods_access_tickets.html @@ -75,8 +75,7 @@

iRODS Access Tickets

Project Sheets + class="btn btn-primary" role="button"> Create Ticket @@ -101,9 +100,11 @@

iRODS Access Tickets

{% for ticket in object_list %} - - - + + + {{ ticket.get_display_name }} - {{ ticket.ticket }} + + {{ ticket.ticket }} + {% if ticket.user %} {% get_user_html ticket.user as user_html %}{{ user_html | safe }} @@ -124,8 +127,10 @@

iRODS Access Tickets

N/A {% endif %} {{ ticket.get_date_created }} - - {% if ticket.date_expires %} + + {% if not ticket.is_active %} + Expired + {% elif ticket.date_expires %} {{ ticket.get_date_expires }} {% else %} Never @@ -157,7 +162,7 @@

iRODS Access Tickets

{% include 'projectroles/_pagination.html' with pg_small=False %} {% endif %} {% else %} -
+
No iRODS access tickets available for this project.
{% endif %} @@ -167,11 +172,9 @@

iRODS Access Tickets

{% block javascript %} {{ block.super }} - {# Tour content #} - {% endblock javascript %} diff --git a/samplesheets/templates/samplesheets/irodsaccessticket_form.html b/samplesheets/templates/samplesheets/irodsaccessticket_form.html index 7829bc73..c94c38fc 100644 --- a/samplesheets/templates/samplesheets/irodsaccessticket_form.html +++ b/samplesheets/templates/samplesheets/irodsaccessticket_form.html @@ -18,7 +18,7 @@

{% if object.pk %}Update{% else %}Create{% endif %} iRODS Access Ticket

{% if not object.pk %} -
+
Anyone with an iRODS ticket will gain read access to the associated collection and its subcollections. They are also able to pass on the ticket link and grant access to others. Enabling iRODS tickets to diff --git a/samplesheets/tests/test_models.py b/samplesheets/tests/test_models.py index 51d133c5..335b3852 100644 --- a/samplesheets/tests/test_models.py +++ b/samplesheets/tests/test_models.py @@ -35,6 +35,7 @@ GenericMaterial, ISATab, IrodsAccessTicket, + IrodsDataRequest, NOT_AVAILABLE_STR, CONFIG_LABEL_CREATE, ISA_META_ASSAY_PLUGIN, @@ -416,6 +417,37 @@ def make_irods_ticket( return obj +class IrodsDataRequestMixin: + """Helpers for IrodsDataRequest model creation""" + + @classmethod + def make_irods_data_request( + cls, + project, + action, + path, + status, + target_path='', + status_info='', + description='', + user=None, + ): + """Create an iRODS data request object in the database""" + values = { + 'project': project, + 'action': action, + 'path': path, + 'status': status, + 'target_path': target_path, + 'status_info': status_info, + 'user': user, + 'description': description, + } + obj = IrodsDataRequest(**values) + obj.save() + return obj + + # Test classes ----------------------------------------------------------------- @@ -1516,3 +1548,6 @@ def test_get_date_expires(self): self.ticket.get_date_expires(), localtime(self.ticket.date_expires).strftime('%Y-%m-%d'), ) + + +# TODO: Add IrodsDataRequest tests diff --git a/samplesheets/tests/test_ui.py b/samplesheets/tests/test_ui.py index 2b832fb3..cc88d169 100644 --- a/samplesheets/tests/test_ui.py +++ b/samplesheets/tests/test_ui.py @@ -3,8 +3,10 @@ import json from cubi_isa_templates import _TEMPLATES as ISA_TEMPLATES +from datetime import timedelta from django.urls import reverse +from django.utils import timezone from django.utils.http import urlencode from selenium.common.exceptions import NoSuchElementException @@ -20,11 +22,14 @@ from samplesheets.constants import HIDDEN_SHEET_TEMPLATE_FIELDS from samplesheets.models import ( ISATab, - IrodsDataRequest, IRODS_DATA_REQUEST_STATUS_CHOICES, Investigation, ) from samplesheets.tests.test_io import SampleSheetIOMixin, SHEET_DIR +from samplesheets.tests.test_models import ( + IrodsAccessTicketMixin, + IrodsDataRequestMixin, +) from samplesheets.tests.test_sheet_config import ( SheetConfigMixin, CONFIG_PATH_DEFAULT, @@ -48,12 +53,10 @@ CONFIG_DATA_UPDATED = json.load(fp) -class TestProjectSheetsVueAppBase( - SampleSheetIOMixin, SheetConfigMixin, TestUIBase -): - """Base view for the project sheets vue app UI""" +class TestProjectSheetsUIBase(SampleSheetIOMixin, SheetConfigMixin, TestUIBase): + """Base view samplesheets view UI tests""" - def _setup_investigation(self, config_data=None): + def setup_investigation(self, config_data=None): """Setup Investigation, Study and Assay""" self.investigation = self.import_isa_from_file(SHEET_PATH, self.project) if config_data: @@ -68,6 +71,10 @@ def _setup_investigation(self, config_data=None): self.study = self.investigation.studies.first() self.assay = self.study.assays.first() + +class TestProjectSheetsView(IrodsDataRequestMixin, TestProjectSheetsUIBase): + """Tests for the project sheets view UI""" + def _login_and_render(self, user, wait_elem=DEFAULT_WAIT_ID, url_suffix=''): """Login into the sheets view and wait for it to render""" url = ( @@ -79,92 +86,9 @@ def _login_and_render(self, user, wait_elem=DEFAULT_WAIT_ID, url_suffix=''): ) self.login_and_redirect(user=user, url=url, wait_elem=wait_elem) - def _get_route(self): - """Return the Vue.js route part (query string) of the current URL""" - return self.selenium.current_url.split('#')[1] - - @classmethod - def _get_enabled_state(cls, elem): - """Return enabled state of an a/button element regardless of its type""" - # HACK for issue #499 - # if elem.tag_name == 'button': - # return - if elem.tag_name == 'button': - return elem.is_enabled() - elif elem.tag_name == 'a': - return False if 'disabled' in elem.get_attribute('class') else True - - def _start_editing(self, wait_for_study=True): - """Enable edit mode in the UI""" - op_div = self.selenium.find_element(By.ID, 'sodar-ss-op-dropdown') - op_div.click() - WebDriverWait(self.selenium, self.wait_time).until( - ec.presence_of_element_located((By.ID, 'sodar-ss-op-item-edit')) - ) - elem = op_div.find_element(By.ID, 'sodar-ss-op-item-edit') - # NOTE: Must use execute_script() due to bootstrap-vue wrapping - self.selenium.execute_script("arguments[0].click();", elem) - WebDriverWait(self.selenium, self.wait_time).until( - ec.presence_of_element_located( - (By.ID, 'sodar-ss-vue-btn-edit-finish') - ) - ) - if wait_for_study: - WebDriverWait(self.selenium, self.wait_time).until( - ec.presence_of_element_located((By.ID, 'sodar-ss-grid-study')) - ) - - def _stop_editing(self, wait_for_study=True): - """Finish editing and exit from edit mode""" - self.selenium.find_element( - By.ID, 'sodar-ss-vue-btn-edit-finish' - ).click() - WebDriverWait(self.selenium, self.wait_time).until( - ec.presence_of_element_located((By.ID, 'sodar-ss-op-dropdown')) - ) - if wait_for_study: - WebDriverWait(self.selenium, self.wait_time).until( - ec.presence_of_element_located((By.ID, 'sodar-ss-grid-study')) - ) - - @classmethod - def _make_irods_data_request( - cls, - project, - action, - path, - status, - target_path='', - status_info='', - description='', - user=None, - ): - """Create an iRODS access ticket object in the database""" - values = { - 'project': project, - 'action': action, - 'path': path, - 'status': status, - 'target_path': target_path, - 'status_info': status_info, - 'user': user, - 'description': description, - } - obj = IrodsDataRequest(**values) - obj.save() - return obj - - def setUp(self): - super().setUp() - self.default_user = self.user_contributor - - -class TestProjectSheetsView(TestProjectSheetsVueAppBase): - """Tests for the project sheets view UI""" - def setUp(self): super().setUp() - self._setup_investigation() + self.setup_investigation() def test_render(self): """Test rendering view with study and assay grids""" @@ -225,10 +149,11 @@ def test_render_alert(self): irods_backend = get_backend_api('omics_irods') self.investigation.irods_status = True self.investigation.save() - # TODO: Use model helper instead (see #1088) - IrodsDataRequest.objects.create( + self.make_irods_data_request( project=self.project, + action='delete', path=irods_backend.get_path(self.assay) + '/test/xxx.bam', + status='ACTIVE', user=self.user_contributor, ) users = [ @@ -273,7 +198,7 @@ def test_render_alert(self): # NOTE: For further vue app tests, see samplesheets/vueapp/tests -class TestSheetTemplateCreateFormView(TestProjectSheetsVueAppBase): +class TestSheetTemplateCreateFormView(TestProjectSheetsUIBase): """Tests for the sheet template creation view UI""" def test_render_hidden_fields(self): @@ -296,12 +221,12 @@ def test_render_hidden_fields(self): pass # This is ok -class TestSheetVersionListView(TestProjectSheetsVueAppBase): +class TestSheetVersionListView(TestProjectSheetsUIBase): """Tests for the sheet version list view UI""" def setUp(self): super().setUp() - self._setup_investigation() + self.setup_investigation() self.url = reverse( 'samplesheets:versions', kwargs={'project': self.project.sodar_uuid} ) @@ -309,10 +234,10 @@ def setUp(self): def test_list(self): """Test UI rendering for list items""" self.assert_element_exists( - [self.default_user], self.url, 'sodar-ss-version-list', True + [self.user_contributor], self.url, 'sodar-ss-version-list', True ) self.assert_element_exists( - [self.default_user], self.url, 'sodar-ss-version-alert', False + [self.user_contributor], self.url, 'sodar-ss-version-alert', False ) def test_list_no_versions(self): @@ -321,10 +246,13 @@ def test_list_no_versions(self): investigation_uuid=self.investigation.sodar_uuid ).delete() self.assert_element_exists( - [self.default_user], self.url, 'sodar-ss-version-list', True + [self.user_contributor], self.url, 'sodar-ss-version-list', True ) self.assert_element_exists( - [self.default_user], self.url, 'sodar-ss-version-list-item', False + [self.user_contributor], + self.url, + 'sodar-ss-version-list-item', + False, ) def test_list_buttons(self): @@ -345,11 +273,129 @@ def test_list_buttons(self): ) -# TODO: Add UI tests for iRODS access ticket views +class TestIrodsAccessTicketListView( + IrodsAccessTicketMixin, TestProjectSheetsUIBase +): + """Tests for iRODS access ticket list view UI""" + + def setUp(self): + super().setUp() + self.setup_investigation() + self.url = reverse( + 'samplesheets:irods_tickets', + kwargs={'project': self.project.sodar_uuid}, + ) + + def test_render_empty(self): + """Test rendering empty list view""" + self.login_and_redirect(self.user_contributor, self.url) + self.assertIsNotNone( + self.selenium.find_element(By.ID, 'sodar-ss-ticket-alert-empty') + ) + with self.assertRaises(NoSuchElementException): + self.selenium.find_element(By.ID, 'sodar-ss-ticket-table') + + def test_render_ticket(self): + """Test rendering list view with ticket""" + self.make_irods_ticket( + study=self.study, + assay=self.assay, + path='/sodarZone/some/path', + user=self.user_contributor, + ) + self.login_and_redirect(self.user_contributor, self.url) + with self.assertRaises(NoSuchElementException): + self.selenium.find_element(By.ID, 'sodar-ss-ticket-alert-empty') + self.assertIsNotNone( + self.selenium.find_element(By.ID, 'sodar-ss-ticket-table') + ) + self.assertEqual( + len( + self.selenium.find_elements( + By.CLASS_NAME, 'sodar-ss-ticket-item' + ) + ), + 1, + ) + elem = self.selenium.find_element( + By.CLASS_NAME, 'sodar-ss-ticket-item-title' + ).find_element(By.TAG_NAME, 'a') + self.assertNotIn('text-strikethrough', elem.get_attribute('class')) + elem = self.selenium.find_element( + By.CLASS_NAME, 'sodar-ss-ticket-item-expiry' + ) + self.assertEqual(elem.text, 'Never') + self.assertNotIn('text-danger', elem.get_attribute('class')) + + def test_render_expired(self): + """Test rendering with expired ticket""" + self.make_irods_ticket( + study=self.study, + assay=self.assay, + path='/sodarZone/some/path', + user=self.user_contributor, + date_expires=timezone.localtime() - timedelta(days=1), + ) + self.login_and_redirect(self.user_contributor, self.url) + elem = self.selenium.find_element( + By.CLASS_NAME, 'sodar-ss-ticket-item-title' + ).find_element(By.TAG_NAME, 'a') + self.assertIn('text-strikethrough', elem.get_attribute('class')) + elem = self.selenium.find_element( + By.CLASS_NAME, 'sodar-ss-ticket-item-expiry' + ) + self.assertEqual(elem.text, 'Expired') + self.assertIn('text-danger', elem.get_attribute('class')) + + +class TestIrodsAccessTicketCreateView(TestProjectSheetsUIBase): + """Tests for iRODS access ticket create view UI""" + + def setUp(self): + super().setUp() + self.setup_investigation() + self.url = reverse( + 'samplesheets:irods_ticket_create', + kwargs={'project': self.project.sodar_uuid}, + ) + + def test_render(self): + """Test rendering iRODS access ticket create view""" + self.login_and_redirect(self.user_contributor, self.url) + self.assertIsNotNone( + self.selenium.find_element(By.ID, 'sodar-ss-alert-ticket-create') + ) + + +class TestIrodsAccessTicketUpdateView( + IrodsAccessTicketMixin, TestProjectSheetsUIBase +): + """Tests for iRODS access ticket update view UI""" + + def setUp(self): + super().setUp() + self.setup_investigation() + self.ticket = self.make_irods_ticket( + study=self.study, + assay=self.assay, + path='/sodarZone/some/path', + user=self.user_contributor, + date_expires=timezone.localtime() - timedelta(days=1), + ) + self.url = reverse( + 'samplesheets:irods_ticket_update', + kwargs={'irodsaccessticket': self.ticket.sodar_uuid}, + ) + + def test_render(self): + """Test rendering iRODS access ticket update view""" + self.login_and_redirect(self.user_contributor, self.url) + with self.assertRaises(NoSuchElementException): + self.selenium.find_element(By.ID, 'sodar-ss-alert-ticket-create') -class TestIrodsRequestCreateView(TestProjectSheetsVueAppBase): - """Tests for irods request create view UI""" +class TestIrodsRequestCreateView(TestProjectSheetsUIBase): + """Tests for iRODS request create view UI""" def setUp(self): super().setUp() @@ -361,11 +407,16 @@ def setUp(self): def test_render_form(self): """Test UI rendering for iRODS request create view""" self.assert_element_exists( - [self.default_user], self.url, 'sodar-ss-btn-import-submit', True + [self.user_contributor], + self.url, + 'sodar-ss-btn-import-submit', + True, ) -class TestIrodsRequestUpdateView(TestProjectSheetsVueAppBase): +class TestIrodsRequestUpdateView( + IrodsDataRequestMixin, TestProjectSheetsUIBase +): """Tests for irods request update view UI""" def setUp(self): @@ -374,13 +425,13 @@ def setUp(self): self.description = 'description' self.status = IRODS_DATA_REQUEST_STATUS_CHOICES[0][0] self.path = '/some/path/to/a/file' - self.irods_data_request = self._make_irods_data_request( + self.irods_data_request = self.make_irods_data_request( project=self.project, action=self.action, status=self.status, path=self.path, description=self.description, - user=self.default_user, + user=self.user_contributor, ) self.url = reverse( 'samplesheets:irods_request_update', @@ -390,12 +441,17 @@ def setUp(self): def test_render_form(self): """Test UI rendering for iRODS request update view""" self.assert_element_exists( - [self.default_user], self.url, 'sodar-ss-btn-import-submit', True + [self.user_contributor], + self.url, + 'sodar-ss-btn-import-submit', + True, ) -class TestIrodsRequestDeleteView(TestProjectSheetsVueAppBase): - """Tests for irods request delete view UI""" +class TestIrodsRequestDeleteView( + IrodsDataRequestMixin, TestProjectSheetsUIBase +): + """Tests for iRODS request delete view UI""" def setUp(self): super().setUp() @@ -403,13 +459,13 @@ def setUp(self): self.description = 'description' self.status = IRODS_DATA_REQUEST_STATUS_CHOICES[0][0] self.path = '/some/path/to/a/file' - self.irods_data_request = self._make_irods_data_request( + self.irods_data_request = self.make_irods_data_request( project=self.project, action=self.action, status=self.status, path=self.path, description=self.description, - user=self.default_user, + user=self.user_contributor, ) self.url = reverse( 'samplesheets:irods_request_delete', @@ -417,15 +473,19 @@ def setUp(self): ) def test_render(self): - """Test UI rendering for iRODS request delete view""" """Test UI rendering for iRODS request delete view""" self.assert_element_exists( - [self.default_user], self.url, 'sodar-ss-btn-confirm-delete', True + [self.user_contributor], + self.url, + 'sodar-ss-btn-confirm-delete', + True, ) -class TestIrodsRequestAcceptView(TestProjectSheetsVueAppBase): - """Tests for irods request accept view UI""" +class TestIrodsRequestAcceptView( + IrodsDataRequestMixin, TestProjectSheetsUIBase +): + """Tests for iRODS request accept view UI""" def setUp(self): super().setUp() @@ -433,13 +493,13 @@ def setUp(self): self.description = 'description' self.status = IRODS_DATA_REQUEST_STATUS_CHOICES[0][0] self.path = '/some/path/to/a/file' - self.irods_data_request = self._make_irods_data_request( + self.irods_data_request = self.make_irods_data_request( project=self.project, action=self.action, status=self.status, path=self.path, description=self.description, - user=self.default_user, + user=self.user_contributor, ) self.url = reverse( 'samplesheets:irods_request_accept', @@ -453,8 +513,8 @@ def test_render(self): ) -class TestIrodsRequestListView(TestProjectSheetsVueAppBase): - """Tests for irods request reject view UI""" +class TestIrodsRequestListView(IrodsDataRequestMixin, TestProjectSheetsUIBase): + """Tests for iRODS request reject view UI""" def setUp(self): super().setUp() @@ -462,13 +522,13 @@ def setUp(self): self.description = 'description' self.status = IRODS_DATA_REQUEST_STATUS_CHOICES[0][0] self.path = '/some/path/to/a/file' - self.irods_data_request = self._make_irods_data_request( + self.irods_data_request = self.make_irods_data_request( project=self.project, action=self.action, status=self.status, path=self.path, description=self.description, - user=self.default_user, + user=self.user_contributor, ) self.url = reverse( 'samplesheets:irods_requests', @@ -476,9 +536,9 @@ def setUp(self): ) def test_render(self): - """Test UI rendering for listing irods requests""" + """Test UI rendering for listing iRODS requests""" self.assert_element_exists( - [self.default_user], self.url, 'sodar-ss-request-table', True + [self.user_contributor], self.url, 'sodar-ss-request-table', True )