diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3397f4c8..c603a1f4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,8 +14,12 @@ Added - **Irodsbackend** - ``get_trash_path()`` helper (#1658) - iRODS trash statistics for siteinfo (#1658) +- **Irodsinfo** + - ``IrodsEnvRetrieveAPIView`` for retrieving iRODS environment (#1685) - **Landingzones** - Landing zone updating (#1267) + - "Nothing to do" check for landing zone validation and moving (#339) + - iRODS path clipboard copying button in iRODS collection list modal (#1282) - **Samplesheets** - User setting for study and assay table height (#1283) - Study table cache disabling (#1639) @@ -27,6 +31,9 @@ Added - Enable ``tumor_normal_dna`` ISA-Tab template (#1697) - General iRODS access ticket management for assay collections (#804, #1717) - Disabled row delete button tooltips (#1731) + - ``IrodsDataRequest`` REST API views (#1588, #1706, #1734, #1735, #1736) + - Davrods links in iRODS delete request list (#1339) + - Batch accepting and rejecting for iRODS delete requests (#1340) - **Taskflowbackend** - ``BatchCalculateChecksumTask`` iRODS task (#1634) - Automated generation of missing checksums in ``zone_move`` flow (#1634) @@ -41,10 +48,14 @@ Changed - Upgrade to python-irodsclient v1.1.8 (#1538) - **Landingzones** - Move iRODS object helpers to ``TaskflowTestMixin`` (#1699) + - Enable superuser landing zone controls for locked zones (#1607) + - Add ``DELETING`` to locked states in UI (#1657) + - Query for landing zone status in batch (#1684) - **Samplesheets** - Sample sheet table viewport background color (#1692) - Contract sheet table height to fit content (#1693) - Hide additional internal fields from ISA-Tab templates (#1698) + - Refactor ``IrodsDataRequest`` model and tests (#1706) - **Taskflowbackend** - Move iRODS object helpers from ``LandingZoneTaskflowMixin`` (#1699) - Move iRODS test cleanup to ``TaskflowTestMixin.clear_irods_test_data()`` (#1722) @@ -57,13 +68,17 @@ Fixed - Batch import tests failing from forbidden obolibrary access (#1694) - **Samplesheets** - ``perform_project_sync()`` crash with no iRODS collections created (#1687) + - iRODS delete request modification UI view permission checks failing for non-creator contributors (#1737) Removed ------- +- **Landingzones** + - Unused ``data_tables`` references from templates (#1710) - **Samplesheets** - ``SHEETS_TABLE_HEIGHT`` Django setting (#1283) - Duplicate ``IrodsAccessTicketMixin`` from ``test_views_ajax`` (#1703) + - ``IRODS_DATA_REQUEST_STATUS_CHOICES`` constant (#1706) v0.13.4 (2023-05-15) diff --git a/docs_manual/source/api_samplesheets.rst b/docs_manual/source/api_samplesheets.rst index e8369a3e..7ca30db7 100644 --- a/docs_manual/source/api_samplesheets.rst +++ b/docs_manual/source/api_samplesheets.rst @@ -15,17 +15,19 @@ API Views .. autoclass:: IrodsCollsCreateAPIView +.. autoclass:: IrodsDataRequestRetrieveAPIView + .. autoclass:: IrodsDataRequestListAPIView -.. autoclass:: IrodsRequestCreateAPIView +.. autoclass:: IrodsDataRequestCreateAPIView -.. autoclass:: IrodsRequestUpdateAPIView +.. autoclass:: IrodsDataRequestUpdateAPIView -.. autoclass:: IrodsRequestDeleteAPIView +.. autoclass:: IrodsDataRequestDeleteAPIView -.. autoclass:: IrodsRequestAcceptAPIView +.. autoclass:: IrodsDataRequestAcceptAPIView -.. autoclass:: IrodsRequestRejectAPIView +.. autoclass:: IrodsDataRequestRejectAPIView .. autoclass:: SheetImportAPIView diff --git a/samplesheets/forms.py b/samplesheets/forms.py index 9db28325..e3bead9d 100644 --- a/samplesheets/forms.py +++ b/samplesheets/forms.py @@ -26,6 +26,8 @@ ISATab, IrodsAccessTicket, IrodsDataRequest, + IRODS_REQUEST_STATUS_ACTIVE, + IRODS_REQUEST_STATUS_FAILED, ) @@ -35,6 +37,9 @@ TPL_DIR_FIELD = '__output_dir' +# Mixins and Helpers ----------------------------------------------------------- + + class MultipleFileInput(forms.ClearableFileInput): allow_multiple_selected = True @@ -53,6 +58,72 @@ def clean(self, data, initial=None): return result +class IrodsDataRequestValidateMixin: + """Validation helpers for iRODS data requests""" + + def validate_request_path(self, irods_backend, project, instance, path): + """ + Validate path for IrodsAccessRequest. + + :param irods_backend: IrodsAPI object + :param project: Project object + :param instance: Existing instance in case of update or None + :param path: Full iRODS path to a collection or a data object (string) + :raises: ValueError if path is incorrect + """ + old_request = IrodsDataRequest.objects.filter( + path=path, + status__in=[ + IRODS_REQUEST_STATUS_ACTIVE, + IRODS_REQUEST_STATUS_FAILED, + ], + ).first() + if old_request and old_request != instance: + raise ValueError(ERROR_MSG_EXISTING) + + path_re = re.compile( + '^' + irods_backend.get_projects_path() + '/[0-9a-f]{2}/' + '(?P[0-9a-f-]{36})/' + + settings.IRODS_SAMPLE_COLL + + '/study_(?P[0-9a-f-]{36})/' + 'assay_(?P[0-9a-f-]{36})/.+$' + ) + match = re.search(path_re, path) + if not match: + raise ValueError(ERROR_MSG_INVALID_PATH) + + project_path = irods_backend.get_path(project) + if not path.startswith(project_path): + raise ValueError('Path does not belong in project') + + try: + Study.objects.get( + sodar_uuid=match.group('study_uuid'), + investigation__project__sodar_uuid=match.group('project_uuid'), + ) + except Study.DoesNotExist: + raise ValueError('Study not found in project with UUID') + try: + Assay.objects.get( + sodar_uuid=match.group('assay_uuid'), + study__sodar_uuid=match.group('study_uuid'), + ) + except Assay.DoesNotExist: + raise ValueError('Assay not found in this project with UUID') + + with irods_backend.get_session() as irods: + if path and not ( + irods.data_objects.exists(path) + or irods.collections.exists(path) + ): + raise ValueError( + 'Path to collection or data object doesn\'t exist in iRODS', + ) + + +# Forms ------------------------------------------------------------------------ + + class SheetImportForm(forms.Form): """ Form for importing an ISA investigation from an ISA-Tab archive or @@ -154,7 +225,7 @@ def save(self, *args, **kwargs): class SheetTemplateCreateForm(forms.Form): - """Form for creating sample sheets from an ISA-Tab template.""" + """Form for creating sample sheets from an ISA-Tab template""" @classmethod def _get_tsv_data(cls, path, file_names): @@ -378,84 +449,34 @@ def clean(self): return self.cleaned_data -class IrodsRequestForm(forms.ModelForm): - """Form for the iRODS delete request creation and editing""" +class IrodsDataRequestForm(IrodsDataRequestValidateMixin, forms.ModelForm): + """Form for iRODS data request creation and editing""" class Meta: model = IrodsDataRequest fields = ['path', 'description'] - def __init__(self, *args, **kwargs): + def __init__(self, project=None, *args, **kwargs): super().__init__(*args, **kwargs) + if project: + self.project = Project.objects.filter(sodar_uuid=project).first() self.fields['description'].required = False def clean(self): cleaned_data = super().clean() irods_backend = get_backend_api('omics_irods') - # Remove trailing slashes as irodspython client does not recognize - # this as a collection - cleaned_data['path'] = cleaned_data['path'].rstrip('/') - - old_request = IrodsDataRequest.objects.filter( - path=cleaned_data['path'], status__in=['ACTIVE', 'FAILED'] - ).first() - if old_request and old_request != self.instance: - self.add_error('path', ERROR_MSG_EXISTING) - return cleaned_data - - path_re = re.compile( - '^' + irods_backend.get_projects_path() + '/[0-9a-f]{2}/' - '(?P[0-9a-f-]{36})/' - + settings.IRODS_SAMPLE_COLL - + '/study_(?P[0-9a-f-]{36})/' - 'assay_(?P[0-9a-f-]{36})/.+$' - ) - match = re.search( - path_re, - cleaned_data['path'], - ) - if not match: - self.add_error('path', ERROR_MSG_INVALID_PATH) - else: - try: - cleaned_data['project'] = Project.objects.get( - sodar_uuid=match.group('project_uuid') - ) - except Project.DoesNotExist: - self.add_error('path', 'Project not found') - try: - Study.objects.get( - sodar_uuid=match.group('study_uuid'), - investigation__project__sodar_uuid=match.group( - 'project_uuid' - ), - ) - except Study.DoesNotExist: - self.add_error('path', 'Study not found in project with UUID') - try: - Assay.objects.get( - sodar_uuid=match.group('assay_uuid'), - study__sodar_uuid=match.group('study_uuid'), - ) - except Assay.DoesNotExist: - self.add_error( - 'path', 'Assay not found in this project with UUID' - ) - - with irods_backend.get_session() as irods: - if 'path' in cleaned_data and not ( - irods.data_objects.exists(cleaned_data['path']) - or irods.collections.exists(cleaned_data['path']) - ): - self.add_error( - 'path', - 'Path to collection or data object doesn\'t exist in iRODS', - ) + cleaned_data['path'] = irods_backend.sanitize_path(cleaned_data['path']) + try: + self.validate_request_path( + irods_backend, self.project, self.instance, cleaned_data['path'] + ) + except Exception as ex: + self.add_error('path', str(ex)) return cleaned_data -class IrodsRequestAcceptForm(forms.Form): - """Form accepting an iRODS delete request.""" +class IrodsDataRequestAcceptForm(forms.Form): + """Form for accepting an iRODS data request""" confirm = forms.BooleanField(required=True) @@ -468,7 +489,7 @@ def __init__(self, *args, **kwargs): class SheetVersionEditForm(forms.ModelForm): - """Form for editing a saved ISA-Tab version of the sample sheets.""" + """Form for editing a saved ISA-Tab version of sample sheets""" class Meta: model = ISATab diff --git a/samplesheets/migrations/0021_update_irodsdatarequest.py b/samplesheets/migrations/0021_update_irodsdatarequest.py new file mode 100644 index 00000000..330f7213 --- /dev/null +++ b/samplesheets/migrations/0021_update_irodsdatarequest.py @@ -0,0 +1,40 @@ +# Generated by Django 3.2.19 on 2023-07-27 13:52 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('projectroles', '0028_populate_finder_role'), + ('samplesheets', '0020_update_irodsaccessticket'), + ] + + operations = [ + migrations.AlterModelManagers( + name='irodsaccessticket', + managers=[ + ], + ), + migrations.AlterField( + model_name='irodsdatarequest', + name='action', + field=models.CharField(default='DELETE', help_text='Action to be performed', max_length=64), + ), + migrations.AlterField( + model_name='irodsdatarequest', + name='description', + field=models.CharField(blank=True, help_text='Request description (optional)', max_length=1024, null=True), + ), + migrations.AlterField( + model_name='irodsdatarequest', + name='project', + field=models.ForeignKey(help_text='Project to which the iRODS data request belongs', on_delete=django.db.models.deletion.CASCADE, related_name='irods_data_request', to='projectroles.project'), + ), + migrations.AlterField( + model_name='irodsdatarequest', + name='status', + field=models.CharField(default='ACTIVE', help_text='Status of the request', max_length=16), + ), + ] diff --git a/samplesheets/models.py b/samplesheets/models.py index 791b9e4c..55c6c393 100644 --- a/samplesheets/models.py +++ b/samplesheets/models.py @@ -67,11 +67,11 @@ 'REPLACE': 'Replacing a previous ISA-Tab', } -IRODS_DATA_REQUEST_STATUS_CHOICES = [ - ('ACTIVE', 'active'), - ('ACCEPTED', 'accepted'), - ('FAILED', 'failed'), -] +IRODS_REQUEST_ACTION_DELETE = 'DELETE' +IRODS_REQUEST_STATUS_ACCEPTED = 'ACCEPTED' +IRODS_REQUEST_STATUS_ACTIVE = 'ACTIVE' +IRODS_REQUEST_STATUS_FAILED = 'FAILED' +IRODS_REQUEST_STATUS_REJECTED = 'REJECTED' # ISA-Tab SODAR metadata comment key for assay plugin override ISA_META_ASSAY_PLUGIN = 'SODAR Assay Plugin' @@ -1266,16 +1266,16 @@ class Meta: Project, null=False, related_name='irods_data_request', - help_text='Project to which the iRODS delete request belongs', + help_text='Project to which the iRODS data request belongs', on_delete=models.CASCADE, ) - #: Action to be performed (default currently supported) + #: Action to be performed (only DELETE is currently supported) action = models.CharField( max_length=64, unique=False, blank=False, - default='delete', + default=IRODS_REQUEST_ACTION_DELETE, help_text='Action to be performed', ) @@ -1309,8 +1309,7 @@ class Meta: status = models.CharField( max_length=16, null=False, - choices=IRODS_DATA_REQUEST_STATUS_CHOICES, - default=IRODS_DATA_REQUEST_STATUS_CHOICES[0][0], + default=IRODS_REQUEST_STATUS_ACTIVE, help_text='Status of the request', ) @@ -1321,7 +1320,10 @@ class Meta: #: Request description (optional) description = models.CharField( - max_length=1024, help_text='Request description' + blank=True, + null=True, + max_length=1024, + help_text='Request description (optional)', ) #: DateTime of request creation @@ -1349,6 +1351,33 @@ def __repr__(self): ) return 'IrodsDataRequest({})'.format(', '.join(repr(v) for v in values)) + # Saving and validation + + def save(self, *args, **kwargs): + """Custom validation and saving method""" + self._validate_action() + self._validate_status() + super().save(*args, **kwargs) + + def _validate_action(self): + """Validate the action field""" + if self.action != IRODS_REQUEST_ACTION_DELETE: + raise ValidationError( + 'This model currently only supports the action "{}"'.format( + IRODS_REQUEST_ACTION_DELETE + ) + ) + + def _validate_status(self): + """Validate the status field""" + if self.status not in [ + IRODS_REQUEST_STATUS_ACCEPTED, + IRODS_REQUEST_STATUS_ACTIVE, + IRODS_REQUEST_STATUS_FAILED, + IRODS_REQUEST_STATUS_REJECTED, + ]: + raise ValidationError('Unknown status "{}"'.format(self.status)) + # Custom row-level functions def get_display_name(self): diff --git a/samplesheets/serializers.py b/samplesheets/serializers.py index 1eeed1e0..94f87c6c 100644 --- a/samplesheets/serializers.py +++ b/samplesheets/serializers.py @@ -1,20 +1,16 @@ """API view model serializers for the samplesheets app""" -import re - -from django.conf import settings - from rest_framework import serializers # Projectroles dependency -from projectroles.models import Project from projectroles.plugins import get_backend_api from projectroles.serializers import ( SODARProjectModelSerializer, SODARNestedListSerializer, + SODARUserSerializer, ) -from samplesheets.forms import ERROR_MSG_EXISTING, ERROR_MSG_INVALID_PATH +from samplesheets.forms import IrodsDataRequestValidateMixin from samplesheets.models import Investigation, Study, Assay, IrodsDataRequest @@ -94,64 +90,43 @@ class Meta: read_only_fields = fields -class IrodsRequestSerializer(SODARProjectModelSerializer): +class IrodsDataRequestSerializer( + IrodsDataRequestValidateMixin, SODARProjectModelSerializer +): """Serializer for the IrodsDataRequest model""" + user = SODARUserSerializer(read_only=True) + class Meta: model = IrodsDataRequest - fields = ['path', 'description'] + fields = [ + 'project', + 'action', + 'path', + 'target_path', + 'user', + 'status', + 'status_info', + 'description', + 'date_created', + 'sodar_uuid', + ] + read_only_fields = [ + f for f in fields if f not in ['path', 'description'] + ] def validate_path(self, value): irods_backend = get_backend_api('omics_irods') - # Remove trailing slashes as irodspython client does not recognize - # this as a collection path = irods_backend.sanitize_path(value) - path_re = re.compile( - '^' + irods_backend.get_projects_path() + '/[0-9a-f]{2}/' - '(?P[0-9a-f-]{36})/' - + settings.IRODS_SAMPLE_COLL - + '/study_(?P[0-9a-f-]{36})/' - 'assay_(?P[0-9a-f-]{36})/.+$' - ) - - old_request = IrodsDataRequest.objects.filter( - path=path, status__in=['ACTIVE', 'FAILED'] - ).first() - if old_request and old_request != self.instance: - raise serializers.ValidationError(ERROR_MSG_EXISTING) - - match = re.search(path_re, path) - if not match: - raise serializers.ValidationError(ERROR_MSG_INVALID_PATH) - try: - Project.objects.get(sodar_uuid=match.group('project_uuid')) - except Project.DoesNotExist: - raise serializers.ValidationError('Project not found') - try: - Study.objects.get( - sodar_uuid=match.group('study_uuid'), - investigation__project__sodar_uuid=match.group('project_uuid'), - ) - except Study.DoesNotExist: - raise serializers.ValidationError( - 'Study not found in project with UUID' - ) try: - Assay.objects.get( - sodar_uuid=match.group('assay_uuid'), - study__sodar_uuid=match.group('study_uuid'), - ) - except Assay.DoesNotExist: - raise serializers.ValidationError( - 'Assay not found in this project with UUID' + self.validate_request_path( + irods_backend, self.context['project'], self.instance, path ) - - with irods_backend.get_session() as irods: - if not ( - irods.data_objects.exists(path) - or irods.collections.exists(path) - ): - raise serializers.ValidationError( - 'Path to collection or data object doesn\'t exist in iRODS' - ) + except Exception as ex: + raise serializers.ValidationError(str(ex)) return value + + def create(self, validated_data): + validated_data['project'] = self.context['project'] + validated_data['user'] = self.context['request'].user + return super().create(validated_data) diff --git a/samplesheets/tests/test_models.py b/samplesheets/tests/test_models.py index 335b3852..0e90a876 100644 --- a/samplesheets/tests/test_models.py +++ b/samplesheets/tests/test_models.py @@ -4,21 +4,21 @@ # TODO: Test validation rules and uniqueness constraints import altamisa -import pytz +import os import re from datetime import timedelta from django.conf import settings +from django.core.exceptions import ValidationError from django.forms.models import model_to_dict from django.utils import timezone -from django.utils.datetime_safe import datetime -from django.utils.timezone import localtime from test_plus.test import TestCase # Projectroles dependency from projectroles.models import SODAR_CONSTANTS +from projectroles.plugins import get_backend_api from projectroles.tests.test_models import ( ProjectMixin, RoleMixin, @@ -39,13 +39,13 @@ NOT_AVAILABLE_STR, CONFIG_LABEL_CREATE, ISA_META_ASSAY_PLUGIN, + IRODS_REQUEST_ACTION_DELETE, + IRODS_REQUEST_STATUS_ACTIVE, ) from samplesheets.utils import get_alt_names -# Local constants -------------------------------------------------------------- - - +# Local constants DEFAULT_PARSER_VERSION = altamisa.__version__ INV_IDENTIFIER = 'Investigation identifier' @@ -122,6 +122,11 @@ PLUGIN_NAME_DNA_SEQ = 'samplesheets_assay_dna_sequencing' PLUGIN_NAME_GENERIC_RAW = 'samplesheets_assay_generic_raw' +IRODS_TICKET_LABEL = 'Ticket' +IRODS_TICKET_PATH = '/sodarZone/path/to/irods/collection' +IRODS_TICKET_STR = 'taihic7Ieengu1Ch' +IRODS_REQUEST_DESC = 'Request description' + # Helper mixins ---------------------------------------------------------------- @@ -169,9 +174,7 @@ def make_investigation( 'headers': headers, 'active': True, } # NOTE: Must explicitly set active to True - obj = Investigation(**values) - obj.save() - return obj + return Investigation.objects.create(**values) @classmethod def make_study( @@ -204,9 +207,7 @@ def make_study( 'comments': comments, 'headers': headers, } - obj = Study(**values) - obj.save() - return obj + return Study.objects.create(**values) @classmethod def make_protocol( @@ -237,9 +238,7 @@ def make_protocol( 'comments': comments, 'headers': headers, } - obj = Protocol(**values) - obj.save() - return obj + return Protocol.objects.create(**values) @classmethod def make_assay( @@ -266,9 +265,7 @@ def make_assay( 'comments': comments, 'headers': headers, } - obj = Assay(**values) - obj.save() - return obj + return Assay.objects.create(**values) @classmethod def make_material( @@ -303,9 +300,7 @@ def make_material( 'headers': headers, 'comments': comments, } - obj = GenericMaterial(**values) - obj.save() - return obj + return GenericMaterial.objects.create(**values) @classmethod def make_process( @@ -342,9 +337,7 @@ def make_process( 'comments': comments, 'headers': headers, } - obj = Process(**values) - obj.save() - return obj + return Process.objects.create(**values) @classmethod def set_configuration(cls, investigation, config_name): @@ -381,9 +374,7 @@ def make_isatab( 'extra_data': extra_data, 'description': description, } - obj = ISATab(**values) - obj.save() - return obj + return ISATab.objects.create(**values) class IrodsAccessTicketMixin: @@ -412,16 +403,14 @@ def make_irods_ticket( 'user': user, 'date_expires': date_expires, } - obj = IrodsAccessTicket(**values) - obj.save() - return obj + return IrodsAccessTicket.objects.create(**values) class IrodsDataRequestMixin: """Helpers for IrodsDataRequest model creation""" @classmethod - def make_irods_data_request( + def make_irods_request( cls, project, action, @@ -432,7 +421,7 @@ def make_irods_data_request( description='', user=None, ): - """Create an iRODS data request object in the database""" + """Create an IrodsDataRequest object in the database""" values = { 'project': project, 'action': action, @@ -440,12 +429,10 @@ def make_irods_data_request( 'status': status, 'target_path': target_path, 'status_info': status_info, - 'user': user, 'description': description, + 'user': user, } - obj = IrodsDataRequest(**values) - obj.save() - return obj + return IrodsDataRequest.objects.create(**values) # Test classes ----------------------------------------------------------------- @@ -1004,7 +991,6 @@ class TestDataFile(TestSampleSheetBase): def setUp(self): super().setUp() - # Set up DATA GenericMaterial self.material = self.make_material( item_type='DATA', @@ -1383,27 +1369,15 @@ def test_get_name_no_archive(self): class TestIrodsAccessTicket(IrodsAccessTicketMixin, TestSampleSheetBase): """Tests for the IrodsAccessTicket model""" - def _get_expiry_today(self): - return ( - datetime.now() - .replace(hour=0, minute=0, second=0, microsecond=0) - .astimezone(pytz.utc) - ) - def setUp(self): super().setUp() - self.path = '/path/to/some/trackhub' - self.label = 'Some Ticket' - self.ticket_str = 'abcdef' - self.date_expires = None self.ticket = self.make_irods_ticket( study=self.study, assay=self.assay, - ticket=self.ticket_str, - path=self.path, - label=self.label, + ticket=IRODS_TICKET_STR, + path=IRODS_TICKET_PATH, + label=IRODS_TICKET_LABEL, user=self.user_owner, - date_expires=self.date_expires, ) def test_initialization(self): @@ -1412,12 +1386,12 @@ def test_initialization(self): 'id': self.ticket.pk, 'study': self.study.pk, 'assay': self.assay.pk, - 'label': self.label, - 'ticket': self.ticket_str, - 'path': self.path, + 'label': IRODS_TICKET_LABEL, + 'ticket': IRODS_TICKET_STR, + 'path': IRODS_TICKET_PATH, 'user': self.user_owner.pk, 'sodar_uuid': self.ticket.sodar_uuid, - 'date_expires': self.date_expires, + 'date_expires': None, } self.assertEqual(model_to_dict(self.ticket), expected) @@ -1446,16 +1420,16 @@ def test__repr__(self): ) self.assertEqual(repr(self.ticket), expected) - def test_get_display_name_one_assay(self): - """Test get_display_name()""" + def test_get_display_name(self): + """Test get_display_name() with single assay""" expected = '{} / {}'.format( self.ticket.get_coll_name(), self.ticket.get_label(), ) self.assertEqual(self.ticket.get_display_name(), expected) - def test_get_display_name_two_assays(self): - """Test get_display_name()""" + def test_get_display_name_multiple_assays(self): + """Test get_display_name() with multiple assays""" self.make_assay( file_name=ASSAY2_FILE_NAME, study=self.study, @@ -1481,35 +1455,27 @@ def test_get_webdav_link(self): m.group(1) + settings.IRODS_WEBDAV_USER_ANON + ':' - + self.ticket_str + + IRODS_TICKET_STR + '@' + url - + self.path + + IRODS_TICKET_PATH ) self.assertEqual(self.ticket.get_webdav_link(), expected) def test_is_active_no_expiry_date(self): - """Test is_active()""" - self.ticket.date_expires = None - self.ticket.save() + """Test is_active() with no expiry date""" + # Expiry date is None by default self.assertTrue(self.ticket.is_active()) def test_is_active_expired(self): - """Test is_active()""" - self.ticket.date_expires = self._get_expiry_today() - timedelta(days=1) + """Test is_active() with expired ticket""" + self.ticket.date_expires = timezone.now() - timedelta(days=1) self.ticket.save() self.assertFalse(self.ticket.is_active()) - def test_is_active_expires_today(self): - """Test is_active()""" - # Ugly timezone conversion - self.ticket.date_expires = self._get_expiry_today() - self.ticket.save() - self.assertFalse(self.ticket.is_active()) - - def test_is_active_expires_tomorrow(self): - """Test is_active()""" - self.ticket.date_expires = self._get_expiry_today() + timedelta(days=1) + def test_is_active_not_expired(self): + """Test is_active() with active ticket""" + self.ticket.date_expires = timezone.now() + timedelta(days=1) self.ticket.save() self.assertTrue(self.ticket.is_active()) @@ -1517,15 +1483,15 @@ def test_get_coll_name(self): """Test get_coll_name()""" self.assertEqual( self.ticket.get_coll_name(), - self.path.split('/')[-1], + IRODS_TICKET_PATH.split('/')[-1], ) def test_get_label(self): """Test get_label()""" - self.assertEqual(self.ticket.get_label(), self.label) + self.assertEqual(self.ticket.get_label(), IRODS_TICKET_LABEL) def test_get_label_none(self): - """Test get_label()""" + """Test get_label() with no label set""" self.ticket.label = None self.ticket.save() self.assertEqual( @@ -1537,17 +1503,130 @@ def test_get_date_created(self): """Test get_date_created()""" self.assertEqual( self.ticket.get_date_created(), - localtime(self.ticket.date_created).strftime('%Y-%m-%d %H:%M'), + timezone.localtime(self.ticket.date_created).strftime( + '%Y-%m-%d %H:%M' + ), ) def test_get_date_expires(self): """Test get_date_expires()""" - self.ticket.date_expires = self._get_expiry_today() + self.ticket.date_expires = timezone.now() self.ticket.save() self.assertEqual( self.ticket.get_date_expires(), - localtime(self.ticket.date_expires).strftime('%Y-%m-%d'), + timezone.localtime(self.ticket.date_expires).strftime('%Y-%m-%d'), ) -# TODO: Add IrodsDataRequest tests +class TestIrodsDataRequest(IrodsDataRequestMixin, TestSampleSheetBase): + """Tests for the IrodsDataRequest model""" + + def setUp(self): + super().setUp() + self.irods_backend = get_backend_api('omics_irods') + self.request_path = os.path.join( + self.irods_backend.get_path(self.assay), 'file.txt' + ) + self.request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=self.request_path, + status=IRODS_REQUEST_STATUS_ACTIVE, + description=IRODS_REQUEST_DESC, + user=self.user_owner, + ) + + def test_initialization(self): + """Test IrodsDataRequest initialization""" + expected = { + 'id': self.request.pk, + 'project': self.project.pk, + 'action': IRODS_REQUEST_ACTION_DELETE, + 'path': self.request_path, + 'status': IRODS_REQUEST_STATUS_ACTIVE, + 'target_path': '', + 'status_info': '', + 'description': IRODS_REQUEST_DESC, + 'user': self.user_owner.pk, + 'sodar_uuid': self.request.sodar_uuid, + } + self.assertEqual(model_to_dict(self.request), expected) + + def test__str__(self): + """Test IrodsDataRequest __str__()""" + expected = '{}: {} {}'.format( + self.request.project.title, + self.request.action, + self.request.get_short_path(), + ) + self.assertEqual(str(self.request), expected) + + def test__repr__(self): + """Test IrodsDataRequest __repr__()""" + expected = 'IrodsDataRequest({})'.format( + ', '.join( + repr(v) + for v in [ + self.request.project.title, + self.assay.get_display_name(), + self.request.action, + self.request_path, + self.user_owner.username, + ] + ) + ) + self.assertEqual(repr(self.request), expected) + + def test_validate_action(self): + """Test _validate_action()""" + with self.assertRaises(ValidationError): + self.request.action = 'MOVE' + self.request.save() + + def test_validate_status(self): + """Test _validate_status()""" + with self.assertRaises(ValidationError): + self.request.status = 'NOT A VALID STATUS' + self.request.save() + + def test_get_display_name(self): + """Test get_display_name()""" + expected = '{} {}'.format( + IRODS_REQUEST_ACTION_DELETE.capitalize(), + self.request.get_short_path(), + ) + self.assertEqual(self.request.get_display_name(), expected) + + def test_get_date_created(self): + """Test get_date_created()""" + self.assertEqual( + self.request.get_date_created(), + timezone.localtime(self.request.date_created).strftime( + '%Y-%m-%d %H:%M' + ), + ) + + def test_get_short_path(self): + """Test get_short_path()""" + expected = '/'.join(self.request_path.split('/')[-2:]) + self.assertEqual(self.request.get_short_path(), expected) + + def test_get_assay(self): + """Test get_assay()""" + self.assertEqual(self.request.get_assay(), self.assay) + + def test_get_assay_no_assay(self): + """Test get_assay() with no assay in path""" + self.request.path = os.path.join( + self.irods_backend.get_path(self.study), 'file.txt' + ) + self.request.save() + self.assertEqual(self.request.get_assay(), None) + + def test_get_assay_name(self): + """Test get_assay_name()""" + self.assertEqual( + self.request.get_assay_name(), self.assay.get_display_name() + ) + + # NOTE: For is_data_object() and is_collection(), see test_models_taskflow diff --git a/samplesheets/tests/test_models_taskflow.py b/samplesheets/tests/test_models_taskflow.py index 85156412..8e59b0b9 100644 --- a/samplesheets/tests/test_models_taskflow.py +++ b/samplesheets/tests/test_models_taskflow.py @@ -2,9 +2,6 @@ import os -from django.forms.models import model_to_dict -from django.utils.timezone import localtime - # Projectroles dependency from projectroles.constants import SODAR_CONSTANTS @@ -12,35 +9,34 @@ from taskflowbackend.tests.base import TaskflowViewTestBase from samplesheets.models import ( - IRODS_DATA_REQUEST_STATUS_CHOICES, - IrodsDataRequest, + IRODS_REQUEST_ACTION_DELETE, + IRODS_REQUEST_STATUS_ACTIVE, ) + from samplesheets.tests.test_io import SampleSheetIOMixin, SHEET_DIR -from samplesheets.tests.test_views_taskflow import ( - SampleSheetTaskflowMixin, +from samplesheets.tests.test_models import ( + IrodsDataRequestMixin, + IRODS_REQUEST_DESC, ) +from samplesheets.tests.test_views_taskflow import SampleSheetTaskflowMixin # SODAR constants -PROJECT_ROLE_OWNER = SODAR_CONSTANTS['PROJECT_ROLE_OWNER'] -PROJECT_ROLE_DELEGATE = SODAR_CONSTANTS['PROJECT_ROLE_DELEGATE'] -PROJECT_ROLE_CONTRIBUTOR = SODAR_CONSTANTS['PROJECT_ROLE_CONTRIBUTOR'] -PROJECT_ROLE_GUEST = SODAR_CONSTANTS['PROJECT_ROLE_GUEST'] -PROJECT_TYPE_CATEGORY = SODAR_CONSTANTS['PROJECT_TYPE_CATEGORY'] PROJECT_TYPE_PROJECT = SODAR_CONSTANTS['PROJECT_TYPE_PROJECT'] # Local constants SHEET_PATH = SHEET_DIR + 'i_small.zip' -TEST_FILE_NAME = 'test1' -TEST_COLL_NAME = 'coll1' +TEST_FILE_NAME = 'test.txt' +TEST_COLL_NAME = 'coll' -class TestIrodsDataRequestBase( +class TestIrodsDataRequest( SampleSheetIOMixin, SampleSheetTaskflowMixin, + IrodsDataRequestMixin, TaskflowViewTestBase, ): - """Base test class for iRODS delete requests""" + """Tests for the IrodsAccessTicket model""" def setUp(self): super().setUp() @@ -48,187 +44,42 @@ def setUp(self): title='TestProject', type=PROJECT_TYPE_PROJECT, parent=self.category, - owner=self.user, - description='description', + owner=self.user_owner_cat, ) # Import investigation self.investigation = self.import_isa_from_file(SHEET_PATH, self.project) self.study = self.investigation.studies.first() self.assay = self.study.assays.first() - # Create iRODS collections self.make_irods_colls(self.investigation) - self.assay_path = self.irods_backend.get_path(self.assay) - self.path = os.path.join(self.assay_path, TEST_FILE_NAME) - self.path_coll = os.path.join(self.assay_path, TEST_COLL_NAME) - self.path_md5 = os.path.join(self.assay_path, f'{TEST_FILE_NAME}.md5') - + self.obj_path = os.path.join(self.assay_path, TEST_FILE_NAME) + self.coll_path = os.path.join(self.assay_path, TEST_COLL_NAME) # Create objects - self.file_obj = self.irods.data_objects.create(self.path) - self.coll = self.irods.collections.create(self.path_coll) - self.md5_obj = self.irods.data_objects.create(self.path_md5) - - # Init users (owner = user_cat, superuser = user) - self.user_delegate = self.make_user('user_delegate') - self.user_contrib = self.make_user('user_contrib') - self.user_contrib2 = self.make_user('user_contrib2') - self.user_guest = self.make_user('user_guest') - - self.make_assignment_taskflow( - self.project, self.user_delegate, self.role_delegate - ) - self.make_assignment_taskflow( - self.project, self.user_contrib, self.role_contributor - ) - self.make_assignment_taskflow( - self.project, self.user_contrib2, self.role_contributor - ) - self.make_assignment_taskflow( - self.project, self.user_guest, self.role_guest - ) + self.file_obj = self.irods.data_objects.create(self.obj_path) + self.coll = self.irods.collections.create(self.coll_path) - self.action = 'delete' - self.description = 'description' - self.status = IRODS_DATA_REQUEST_STATUS_CHOICES[0][0] - self.irods_data_request = self._make_irods_data_request( + # Create request + self.request = self.make_irods_request( project=self.project, - action=self.action, - status=self.status, - path=self.path, - description=self.description, + action=IRODS_REQUEST_ACTION_DELETE, + status=IRODS_REQUEST_STATUS_ACTIVE, + path=self.obj_path, + description=IRODS_REQUEST_DESC, user=self.user_owner_cat, ) - def tearDown(self): - self.irods.collections.get('/sodarZone/projects').remove(force=True) - super().tearDown() - - @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 - - -class TestIrodsDataRequest(TestIrodsDataRequestBase): - """Tests for the IrodsAccessTicket model""" - - def test_initialization(self): - """Test IrodsDataTicket initialization""" - expected = { - 'id': self.irods_data_request.pk, - 'project': self.project.pk, - 'path': self.path, - 'user': self.user_owner_cat.pk, - 'action': self.action, - 'status': self.status, - 'target_path': '', - 'status_info': '', - 'description': self.description, - 'sodar_uuid': self.irods_data_request.sodar_uuid, - } - self.assertDictEqual(model_to_dict(self.irods_data_request), expected) - - def test__str__(self): - self.assertEqual( - str(self.irods_data_request), - '{}: {} {}'.format( - self.project.title, - self.action, - self.irods_data_request.get_short_path(), - ), - ) - - def test__repr__(self): - self.assertEqual( - repr(self.irods_data_request), - 'IrodsDataRequest(\'{}\', \'{}\', \'{}\', \'{}\', \'{}\')'.format( - self.project.title, - self.irods_data_request.get_assay_name(), - self.action, - self.path, - self.user_owner_cat.username, - ), - ) - - def test_get_display_name(self): - self.assertEqual( - self.irods_data_request.get_display_name(), - '{} {}'.format( - self.action.capitalize(), - self.irods_data_request.get_short_path(), - ), - ) - - def test_get_date_created(self): - self.assertEqual( - self.irods_data_request.get_date_created(), - localtime(self.irods_data_request.date_created).strftime( - '%Y-%m-%d %H:%M' - ), - ) - - def test_is_data_object_true(self): - self.irods_data_request.path = self.path - self.irods_data_request.save() - self.assertTrue(self.irods_data_request.is_data_object()) - - def test_is_data_object_false(self): - self.irods_data_request.path = self.path_coll - self.irods_data_request.save() - self.assertFalse(self.irods_data_request.is_data_object()) - - def test_is_collection_true(self): - self.irods_data_request.path = self.path_coll - self.irods_data_request.save() - self.assertTrue(self.irods_data_request.is_collection()) - - def test_is_collection_false(self): - self.irods_data_request.path = self.path - self.irods_data_request.save() - self.assertFalse(self.irods_data_request.is_collection()) - - def test_get_short_path(self): - self.assertEqual( - self.irods_data_request.get_short_path(), - '{}/{}'.format( - os.path.basename(self.assay_path), os.path.basename(self.path) - ), - ) - - def test_get_assay(self): - self.assertEqual(self.irods_data_request.get_assay(), self.assay) - - def test_get_assay_name(self): - self.assertEqual( - self.irods_data_request.get_assay_name(), - self.assay.get_display_name(), - ) - - def test_get_assay_name_na(self): - self.irods_data_request.path = '/different/path' - self.irods_data_request.save() - self.assertEqual(self.irods_data_request.get_assay_name(), 'N/A') + def test_is_data_object(self): + """Test is_data_object()""" + self.assertTrue(self.request.is_data_object()) + self.request.path = self.coll_path + self.request.save() + self.assertFalse(self.request.is_data_object()) + + def test_is_collection(self): + """Test is_collection()""" + self.assertFalse(self.request.is_collection()) + self.request.path = self.coll_path + self.request.save() + self.assertTrue(self.request.is_collection()) diff --git a/samplesheets/tests/test_permissions.py b/samplesheets/tests/test_permissions.py index 66428223..9fdb4ac8 100644 --- a/samplesheets/tests/test_permissions.py +++ b/samplesheets/tests/test_permissions.py @@ -10,9 +10,16 @@ from projectroles.tests.test_permissions import TestProjectPermissionBase from projectroles.utils import build_secret -from samplesheets.models import ISATab +from samplesheets.models import ( + ISATab, + IRODS_REQUEST_ACTION_DELETE, + IRODS_REQUEST_STATUS_ACTIVE, +) from samplesheets.tests.test_io import SampleSheetIOMixin, SHEET_DIR -from samplesheets.tests.test_models import IrodsAccessTicketMixin +from samplesheets.tests.test_models import ( + IrodsAccessTicketMixin, + IrodsDataRequestMixin, +) app_settings = AppSettingAPI() @@ -25,12 +32,14 @@ REMOTE_SITE_URL = 'https://sodar.bihealth.org' REMOTE_SITE_SECRET = build_secret() INVALID_SECRET = build_secret() +IRODS_TICKET_PATH = '/sodarZone/ticket/path' +IRODS_FILE_PATH = '/sodarZone/path/test1.txt' -class TestSampleSheetsPermissions( - SampleSheetIOMixin, IrodsAccessTicketMixin, TestProjectPermissionBase +class SamplesheetsPermissionTestBase( + SampleSheetIOMixin, TestProjectPermissionBase ): - """Tests for general samplesheets view permissions""" + """Base test class for samplesheets UI view permissions""" def setUp(self): super().setUp() @@ -38,12 +47,19 @@ def setUp(self): self.study = self.investigation.studies.first() self.assay = self.study.assays.first() - def test_project_sheets(self): - """Test ProjectSheetsView permissions""" - url = reverse( + +class TestProjectSheetsView(SamplesheetsPermissionTestBase): + """Permission tests for ProjectSheetsView""" + + def setUp(self): + super().setUp() + self.url = reverse( 'samplesheets:project_sheets', kwargs={'project': self.project.sodar_uuid}, ) + + def test_get(self): + """Test ProjectSheetsView GET""" good_users = [ self.superuser, self.user_owner_cat, # Inherited @@ -56,32 +72,24 @@ def test_project_sheets(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) # Test public project self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_project_sheets_anon(self): - """Test ProjectSheetsView with anonymous guest access""" + def test_get_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - url = reverse( - 'samplesheets:project_sheets', - kwargs={'project': self.project.sodar_uuid}, - ) - self.assert_response(url, self.anonymous, 200) + self.assert_response(self.url, self.anonymous, 200) - def test_project_sheets_archive(self): - """Test ProjectSheetsView with archived project""" + def test_get_archive(self): + """Test GET with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:project_sheets', - kwargs={'project': self.project.sodar_uuid}, - ) good_users = [ self.superuser, self.user_owner_cat, @@ -94,20 +102,27 @@ def test_project_sheets_archive(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) # Test public project self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) + - def test_sheet_import(self): - """Test SheetImportView permissions""" - url = reverse( +class TestSheetImportView(SamplesheetsPermissionTestBase): + """Permission tests for SheetImportView""" + + def setUp(self): + super().setUp() + self.url = reverse( 'samplesheets:import', kwargs={'project': self.project.sodar_uuid} ) + + def test_get(self): + """Test SheetImportView GET""" good_users = [ self.superuser, self.user_owner_cat, @@ -124,27 +139,20 @@ def test_sheet_import(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_sheet_import_anon(self): - """Test SheetImportView with anonymous guest access""" + def test_get_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - url = reverse( - 'samplesheets:import', - kwargs={'project': self.project.sodar_uuid}, - ) - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) - def test_sheet_import_archive(self): - """Test SheetImportView with archived project""" + def test_get_archive(self): + """Test GET with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:import', kwargs={'project': self.project.sodar_uuid} - ) good_users = [self.superuser] bad_users = [ self.user_owner_cat, @@ -159,19 +167,16 @@ def test_sheet_import_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) - def test_sheet_import_sync(self): - """Test SheetImportView with sync enabled""" + def test_get_sync(self): + """Test GET with sync enabled""" app_settings.set( APP_NAME, 'sheet_sync_enable', True, project=self.project ) - url = reverse( - 'samplesheets:import', kwargs={'project': self.project.sodar_uuid} - ) bad_users = [ self.superuser, self.user_owner_cat, @@ -186,19 +191,16 @@ def test_sheet_import_sync(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) - def test_sheet_import_sync_archive(self): - """Test SheetImportView with sync enabled and archived project""" + def test_get_sync_archive(self): + """Test GET with sync enabled and archived project""" self.project.set_archive() app_settings.set( APP_NAME, 'sheet_sync_enable', True, project=self.project ) - url = reverse( - 'samplesheets:import', kwargs={'project': self.project.sodar_uuid} - ) bad_users = [ self.superuser, self.user_owner_cat, @@ -213,17 +215,24 @@ def test_sheet_import_sync_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) - def test_sheet_template_select(self): - """Test SheetTemplateSelectView permissions""" - self.investigation.delete() - url = reverse( + +class TestSheetTemplateSelectView(SamplesheetsPermissionTestBase): + """Permission tests for SheetTemplateSelectView""" + + def setUp(self): + super().setUp() + self.url = reverse( 'samplesheets:template_select', kwargs={'project': self.project.sodar_uuid}, ) + + def test_get(self): + """Test SheetTemplateSelectView GET""" + self.investigation.delete() good_users = [ self.superuser, self.user_owner_cat, @@ -240,29 +249,21 @@ def test_sheet_template_select(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_sheet_template_select_anon(self): - """Test SheetTemplateSelectView with anonymous guest access""" + def test_get_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - url = reverse( - 'samplesheets:template_select', - kwargs={'project': self.project.sodar_uuid}, - ) - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) - def test_sheet_template_select_archive(self): - """Test SheetTemplateSelectView with archived project""" + def test_get_archive(self): + """Test GET with archived project""" self.project.set_archive() self.investigation.delete() - url = reverse( - 'samplesheets:template_select', - kwargs={'project': self.project.sodar_uuid}, - ) good_users = [self.superuser] bad_users = [ self.user_owner_cat, @@ -277,20 +278,16 @@ def test_sheet_template_select_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) - def test_sheet_template_select_sync(self): - """Test SheetTemplateSelectView with sync enabled""" + def test_get_sync(self): + """Test GET with sync enabled""" app_settings.set( APP_NAME, 'sheet_sync_enable', True, project=self.project ) - url = reverse( - 'samplesheets:template_select', - kwargs={'project': self.project.sodar_uuid}, - ) bad_users = [ self.superuser, self.user_owner_cat, @@ -305,14 +302,17 @@ def test_sheet_template_select_sync(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) - def test_sheet_template_create(self): - """Test SheetTemplateCreateView permissions""" - self.investigation.delete() - url = ( + +class TestSheetTemplateCreateView(TestProjectPermissionBase): + """Permission tests for SheetTemplateCreateView""" + + def setUp(self): + super().setUp() + self.url = ( reverse( 'samplesheets:template_create', kwargs={'project': self.project.sodar_uuid}, @@ -320,6 +320,9 @@ def test_sheet_template_create(self): + '?' + urlencode({'sheet_tpl': 'generic'}) ) + + def test_get(self): + """Test SheetTemplateCreateView GET""" good_users = [ self.superuser, self.user_owner_cat, @@ -336,38 +339,20 @@ def test_sheet_template_create(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_sheet_template_create_anon(self): - """Test SheetTemplateCreateView with anonymous guest access""" - self.investigation.delete() - url = ( - reverse( - 'samplesheets:template_create', - kwargs={'project': self.project.sodar_uuid}, - ) - + '?' - + urlencode({'sheet_tpl': 'generic'}) - ) + def test_get_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) - def test_sheet_template_create_archive(self): - """Test SheetTemplateCreateView with archived project""" + def test_get_archive(self): + """Test GET with archived project""" self.project.set_archive() - self.investigation.delete() - url = ( - reverse( - 'samplesheets:template_create', - kwargs={'project': self.project.sodar_uuid}, - ) - + '?' - + urlencode({'sheet_tpl': 'generic'}) - ) good_users = [self.superuser] bad_users = [ self.user_owner_cat, @@ -382,24 +367,16 @@ def test_sheet_template_create_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) - def test_sheet_template_create_sync(self): - """Test SheetTemplateCreateView with sync enabled""" + def test_get_sync(self): + """Test GET with sync enabled""" app_settings.set( APP_NAME, 'sheet_sync_enable', True, project=self.project ) - url = ( - reverse( - 'samplesheets:template_create', - kwargs={'project': self.project.sodar_uuid}, - ) - + '?' - + urlencode({'sheet_tpl': 'generic'}) - ) bad_users = [ self.superuser, self.user_owner_cat, @@ -414,15 +391,25 @@ def test_sheet_template_create_sync(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) + + +class TestSheetExcelExportView(SamplesheetsPermissionTestBase): + """Permission tests for SheetExcelExportView""" - def test_sheet_export_excel_study(self): - """Test SheetExcelExportView permissions for study table""" - url = reverse( + def setUp(self): + super().setUp() + self.study_url = reverse( 'samplesheets:export_excel', kwargs={'study': self.study.sodar_uuid} ) + self.assay_url = reverse( + 'samplesheets:export_excel', kwargs={'assay': self.assay.sodar_uuid} + ) + + def test_get_study(self): + """Test SheetExcelExportView GET for study table""" good_users = [ self.superuser, self.user_owner_cat, @@ -435,29 +422,23 @@ def test_sheet_export_excel_study(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.study_url, good_users, 200) + self.assert_response(self.study_url, bad_users, 302) self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.study_url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.study_url, self.anonymous, 302) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_sheet_export_excel_study_anon(self): - """Test Excel export for study table with anonymous guest access""" - url = reverse( - 'samplesheets:export_excel', kwargs={'study': self.study.sodar_uuid} - ) + def test_get_study_anon(self): + """Test GET for study table with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 200) + self.assert_response(self.study_url, self.anonymous, 200) - def test_sheet_export_excel_study_archive(self): - """Test SheetExcelExportView for study table with archived project""" + def test_get_study_archive(self): + """Test GET for study table with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:export_excel', kwargs={'study': self.study.sodar_uuid} - ) good_users = [ self.superuser, self.user_owner_cat, @@ -470,19 +451,16 @@ def test_sheet_export_excel_study_archive(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.study_url, good_users, 200) + self.assert_response(self.study_url, bad_users, 302) self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.study_url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.study_url, self.anonymous, 302) - def test_sheet_export_excel_assay(self): - """Test SheetExcelExportView permissions for assay table""" - url = reverse( - 'samplesheets:export_excel', kwargs={'assay': self.assay.sodar_uuid} - ) + def test_get_assay(self): + """Test GET permissions for assay table""" good_users = [ self.superuser, self.user_owner_cat, @@ -495,29 +473,23 @@ def test_sheet_export_excel_assay(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.assay_url, good_users, 200) + self.assert_response(self.assay_url, bad_users, 302) self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.assay_url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.assay_url, self.anonymous, 302) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_sheet_export_excel_assay_anon(self): - """Test SheetExcelExportView with anonymous guest access""" - url = reverse( - 'samplesheets:export_excel', kwargs={'assay': self.assay.sodar_uuid} - ) + def test_get_assay_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 200) + self.assert_response(self.assay_url, self.anonymous, 200) - def test_sheet_export_excel_assay_archive(self): - """Test SheetExcelExportView for assay table with archived project""" + def test_get_assay_archive(self): + """Test GET for assay table with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:export_excel', kwargs={'assay': self.assay.sodar_uuid} - ) good_users = [ self.superuser, self.user_owner_cat, @@ -530,20 +502,27 @@ def test_sheet_export_excel_assay_archive(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.assay_url, good_users, 200) + self.assert_response(self.assay_url, bad_users, 302) self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.assay_url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.assay_url, self.anonymous, 302) + - def test_sheet_export_isa(self): - """Test SheetISAExportView permissions""" - url = reverse( +class TestSheetISAExportView(SamplesheetsPermissionTestBase): + """Permission tests for SheetISAExportView""" + + def setUp(self): + super().setUp() + self.url = reverse( 'samplesheets:export_isa', kwargs={'project': self.project.sodar_uuid}, ) + + def test_get(self): + """Test SheetISAExportView GET""" good_users = [ self.superuser, self.user_owner_cat, @@ -556,31 +535,23 @@ def test_sheet_export_isa(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_sheet_export_isa_anon(self): - """Test SheetISAExportView with anonymous guest access""" - url = reverse( - 'samplesheets:export_isa', - kwargs={'project': self.project.sodar_uuid}, - ) + def test_get_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 200) + self.assert_response(self.url, self.anonymous, 200) - def test_sheet_export_isa_archive(self): - """Test SheetISAExportView with archived project""" + def test_get_archive(self): + """Test GET with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:export_isa', - kwargs={'project': self.project.sodar_uuid}, - ) good_users = [ self.superuser, self.user_owner_cat, @@ -593,19 +564,26 @@ def test_sheet_export_isa_archive(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) + + +class TestSheetDeleteView(SamplesheetsPermissionTestBase): + """Permission tests for SheetDeleteView""" - def test_sheet_delete(self): - """Test SheetDeleteView permissions""" - url = reverse( + def setUp(self): + super().setUp() + self.url = reverse( 'samplesheets:delete', kwargs={'project': self.project.sodar_uuid} ) + + def test_get(self): + """Test SheetDeleteView GET""" good_users = [ self.superuser, self.user_owner_cat, @@ -622,26 +600,20 @@ def test_sheet_delete(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_sheet_delete_anon(self): - """Test SheetDeleteView with anonymous guest access""" - url = reverse( - 'samplesheets:delete', kwargs={'project': self.project.sodar_uuid} - ) + def test_get_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) - def test_sheet_delete_archive(self): - """Test SheetDeleteView with archived project""" + def test_get_archive(self): + """Test GET with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:delete', kwargs={'project': self.project.sodar_uuid} - ) good_users = [self.superuser] bad_users = [ self.user_owner_cat, @@ -656,16 +628,23 @@ def test_sheet_delete_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) + - def test_version_list(self): - """Test SheetVersionListView permissions""" - url = reverse( +class TestSheetVersionListView(SamplesheetsPermissionTestBase): + """Permission tests for SheetVersionListView""" + + def setUp(self): + super().setUp() + self.url = reverse( 'samplesheets:versions', kwargs={'project': self.project.sodar_uuid} ) + + def test_get(self): + """Test SheetVersionListView GET""" good_users = [ self.superuser, self.user_owner_cat, @@ -678,29 +657,23 @@ def test_version_list(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_version_list_anon(self): - """Test SheetVersionListView with anonymous guest access""" - url = reverse( - 'samplesheets:versions', kwargs={'project': self.project.sodar_uuid} - ) + def test_get_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 200) + self.assert_response(self.url, self.anonymous, 200) - def test_version_list_archive(self): - """Test SheetVersionListView permissions with archived project""" + def test_get_archive(self): + """Test GET permissions with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:versions', kwargs={'project': self.project.sodar_uuid} - ) good_users = [ self.superuser, self.user_owner_cat, @@ -713,25 +686,32 @@ def test_version_list_archive(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) - def test_version_compare(self): - """Test SheetVersionCompareView permissions""" - isa = ISATab.objects.first() - url = '{}?source={}&target={}'.format( + +class TestSheetVersionCompareView(SamplesheetsPermissionTestBase): + """Permission tests for SheetVersionCompareView""" + + def setUp(self): + super().setUp() + self.isa_version = ISATab.objects.first() + self.url = '{}?source={}&target={}'.format( reverse( 'samplesheets:version_compare', kwargs={'project': self.project.sodar_uuid}, ), - str(isa.sodar_uuid), - str(isa.sodar_uuid), + str(self.isa_version.sodar_uuid), + str(self.isa_version.sodar_uuid), ) + + def test_get(self): + """Test SheetVersionCompareView GET""" good_users = [ self.superuser, self.user_owner_cat, @@ -744,41 +724,23 @@ def test_version_compare(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_version_compare_anon(self): - """Test SheetVersionCompareView with anonymous guest access""" - isa = ISATab.objects.first() - url = '{}?source={}&target={}'.format( - reverse( - 'samplesheets:version_compare', - kwargs={'project': self.project.sodar_uuid}, - ), - str(isa.sodar_uuid), - str(isa.sodar_uuid), - ) + def test_get_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 200) + self.assert_response(self.url, self.anonymous, 200) - def test_version_compare_archive(self): - """Test SheetVersionCompareView with archived project""" + def test_get_archive(self): + """Test GET with archived project""" self.project.set_archive() - isa = ISATab.objects.first() - url = '{}?source={}&target={}'.format( - reverse( - 'samplesheets:version_compare', - kwargs={'project': self.project.sodar_uuid}, - ), - str(isa.sodar_uuid), - str(isa.sodar_uuid), - ) good_users = [ self.superuser, self.user_owner_cat, @@ -791,27 +753,34 @@ def test_version_compare_archive(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) - def test_version_compare_file(self): - """Test SheetVersionCompareFileView permissions""" - isa = ISATab.objects.first() - url = '{}?source={}&target={}&filename={}&category={}'.format( + +class TestSheetVersionCompareFileView(SamplesheetsPermissionTestBase): + """Permission tests for SheetVersionCompareFileView""" + + def setUp(self): + super().setUp() + self.isa_version = ISATab.objects.first() + self.url = '{}?source={}&target={}&filename={}&category={}'.format( reverse( 'samplesheets:version_compare_file', kwargs={'project': self.project.sodar_uuid}, ), - str(isa.sodar_uuid), - str(isa.sodar_uuid), + str(self.isa_version.sodar_uuid), + str(self.isa_version.sodar_uuid), 's_small.txt', 'studies', ) + + def test_get(self): + """Test SheetVersionCompareFileView GET""" good_users = [ self.superuser, self.user_owner_cat, @@ -824,45 +793,23 @@ def test_version_compare_file(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_version_compare_file_anon(self): - """Test SheetVersionCompareFileView with anonymous guest access""" - isa = ISATab.objects.first() - url = '{}?source={}&target={}&filename={}&category={}'.format( - reverse( - 'samplesheets:version_compare_file', - kwargs={'project': self.project.sodar_uuid}, - ), - str(isa.sodar_uuid), - str(isa.sodar_uuid), - 's_small.txt', - 'studies', - ) + def test_get_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 200) + self.assert_response(self.url, self.anonymous, 200) - def test_version_compare_file_archive(self): - """Test SheetVersionCompareFileView with archived project""" + def test_get_archive(self): + """Test GET with archived project""" self.project.set_archive() - isa = ISATab.objects.first() - url = '{}?source={}&target={}&filename={}&category={}'.format( - reverse( - 'samplesheets:version_compare_file', - kwargs={'project': self.project.sodar_uuid}, - ), - str(isa.sodar_uuid), - str(isa.sodar_uuid), - 's_small.txt', - 'studies', - ) good_users = [ self.superuser, self.user_owner_cat, @@ -875,23 +822,30 @@ def test_version_compare_file_archive(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) + - def test_version_restore(self): - """Test SheetVersionRestoreView permissions""" - isa_version = ISATab.objects.get( +class TestSheetVersionRestoreView(SamplesheetsPermissionTestBase): + """Permission tests for SheetVersionRestoreView""" + + def setUp(self): + super().setUp() + self.isa_version = ISATab.objects.get( investigation_uuid=self.investigation.sodar_uuid ) - url = reverse( + self.url = reverse( 'samplesheets:version_restore', - kwargs={'isatab': isa_version.sodar_uuid}, + kwargs={'isatab': self.isa_version.sodar_uuid}, ) + + def test_get(self): + """Test SheetVersionRestoreView GET""" good_users = [ self.superuser, self.user_owner_cat, @@ -908,34 +862,20 @@ def test_version_restore(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_version_restore_anon(self): - """Test SheetVersionRestoreView with anonymous guest access""" - isa_version = ISATab.objects.get( - investigation_uuid=self.investigation.sodar_uuid - ) - url = reverse( - 'samplesheets:version_restore', - kwargs={'isatab': isa_version.sodar_uuid}, - ) + def test_get_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) - def test_version_restore_archive(self): - """Test SheetVersionRestoreView with archived project""" + def test_get_archive(self): + """Test GET with archived project""" self.project.set_archive() - isa_version = ISATab.objects.get( - investigation_uuid=self.investigation.sodar_uuid - ) - url = reverse( - 'samplesheets:version_restore', - kwargs={'isatab': isa_version.sodar_uuid}, - ) good_users = [self.superuser] bad_users = [ self.user_owner_cat, @@ -950,20 +890,27 @@ def test_version_restore_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) - def test_version_update(self): - """Test SheetVersionUpdateView permissions""" - isa_version = ISATab.objects.get( + +class TestSheetVersionUpdateView(SamplesheetsPermissionTestBase): + """Permission tests for SheetVersionUpdateView""" + + def setUp(self): + super().setUp() + self.isa_version = ISATab.objects.get( investigation_uuid=self.investigation.sodar_uuid ) - url = reverse( + self.url = reverse( 'samplesheets:version_update', - kwargs={'isatab': isa_version.sodar_uuid}, + kwargs={'isatab': self.isa_version.sodar_uuid}, ) + + def test_get(self): + """Test SheetVersionUpdateView GET""" good_users = [ self.superuser, self.user_owner_cat, @@ -980,34 +927,20 @@ def test_version_update(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_version_update_anon(self): - """Test SheetVersionUpdateView with anonymous guest access""" - isa_version = ISATab.objects.get( - investigation_uuid=self.investigation.sodar_uuid - ) - url = reverse( - 'samplesheets:version_update', - kwargs={'isatab': isa_version.sodar_uuid}, - ) + def test_get_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) - def test_version_update_archive(self): - """Test SheetVersionUpdateView with archived project""" + def test_get_archive(self): + """Test GET with archived project""" self.project.set_archive() - isa_version = ISATab.objects.get( - investigation_uuid=self.investigation.sodar_uuid - ) - url = reverse( - 'samplesheets:version_update', - kwargs={'isatab': isa_version.sodar_uuid}, - ) good_users = [self.superuser] bad_users = [ self.user_owner_cat, @@ -1022,20 +955,27 @@ def test_version_update_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) + - def test_version_delete(self): - """Test SheetVersionDeleteView permissions""" - isa_version = ISATab.objects.get( +class TestSheetVersionDeleteView(SamplesheetsPermissionTestBase): + """Permission tests for SheetVersionDeleteView""" + + def setUp(self): + super().setUp() + self.isa_version = ISATab.objects.get( investigation_uuid=self.investigation.sodar_uuid ) - url = reverse( + self.url = reverse( 'samplesheets:version_delete', - kwargs={'isatab': isa_version.sodar_uuid}, + kwargs={'isatab': self.isa_version.sodar_uuid}, ) + + def test_get(self): + """Test SheetVersionDeleteView GET""" good_users = [ self.superuser, self.user_owner_cat, @@ -1052,34 +992,20 @@ def test_version_delete(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_version_delete_anon(self): - """Test SheetVersionDeleteView with anonymous guest access""" - isa_version = ISATab.objects.get( - investigation_uuid=self.investigation.sodar_uuid - ) - url = reverse( - 'samplesheets:version_delete', - kwargs={'isatab': isa_version.sodar_uuid}, - ) + def test_get_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) - def test_version_delete_archive(self): - """Test SheetVersionDeleteView with archived project""" + def test_get_archive(self): + """Test GET with archived project""" self.project.set_archive() - isa_version = ISATab.objects.get( - investigation_uuid=self.investigation.sodar_uuid - ) - url = reverse( - 'samplesheets:version_delete', - kwargs={'isatab': isa_version.sodar_uuid}, - ) good_users = [self.superuser] bad_users = [ self.user_owner_cat, @@ -1094,21 +1020,31 @@ def test_version_delete_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) + - def test_version_delete_batch(self): - """Test SheetVersionDeleteBatchView permissions""" - isa_version = ISATab.objects.get( +class TestSheetVersionDeleteBatchView(SamplesheetsPermissionTestBase): + """Permission tests for SheetVersionDeleteBatchView""" + + def setUp(self): + super().setUp() + self.isa_version = ISATab.objects.get( investigation_uuid=self.investigation.sodar_uuid ) - url = reverse( + self.url = reverse( 'samplesheets:version_delete_batch', kwargs={'project': self.project.sodar_uuid}, ) - data = {'confirm': '1', 'version_check': str(isa_version.sodar_uuid)} + self.post_data = { + 'confirm': '1', + 'version_check': str(self.isa_version.sodar_uuid), + } + + def test_post(self): + """Test SheetVersionDeleteBatchView POST""" good_users = [ self.superuser, self.user_owner_cat, @@ -1125,36 +1061,28 @@ def test_version_delete_batch(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200, method='POST', data=data) - self.assert_response(url, bad_users, 302, method='POST', data=data) + self.assert_response( + self.url, good_users, 200, method='POST', data=self.post_data + ) + self.assert_response( + self.url, bad_users, 302, method='POST', data=self.post_data + ) self.project.set_public() - self.assert_response(url, bad_users, 302, method='POST', data=data) + self.assert_response( + self.url, bad_users, 302, method='POST', data=self.post_data + ) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_version_delete_batch_anon(self): - """Test SheetVersionDeleteBatchView with anonymous guest access""" - isa_version = ISATab.objects.get( - investigation_uuid=self.investigation.sodar_uuid - ) - url = reverse( - 'samplesheets:version_delete_batch', - kwargs={'project': self.project.sodar_uuid}, - ) - data = {'confirm': '1', 'version_check': str(isa_version.sodar_uuid)} + def test_post_anon(self): + """Test POST with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 302, method='POST', data=data) + self.assert_response( + self.url, self.anonymous, 302, method='POST', data=self.post_data + ) - def test_version_delete_batch_archive(self): - """Test SheetVersionDeleteBatchView with archived project""" + def test_post_archive(self): + """Test POST with archived project""" self.project.set_archive() - isa_version = ISATab.objects.get( - investigation_uuid=self.investigation.sodar_uuid - ) - url = reverse( - 'samplesheets:version_delete_batch', - kwargs={'project': self.project.sodar_uuid}, - ) - data = {'confirm': '1', 'version_check': str(isa_version.sodar_uuid)} good_users = [self.superuser] bad_users = [ self.user_owner_cat, @@ -1169,38 +1097,30 @@ def test_version_delete_batch_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200, method='POST', data=data) - self.assert_response(url, bad_users, 302, method='POST', data=data) + self.assert_response( + self.url, good_users, 200, method='POST', data=self.post_data + ) + self.assert_response( + self.url, bad_users, 302, method='POST', data=self.post_data + ) self.project.set_public() - self.assert_response(url, bad_users, 302, method='POST', data=data) + self.assert_response( + self.url, bad_users, 302, method='POST', data=self.post_data + ) -class TestIrodsAccessTicketPermissions( - SampleSheetIOMixin, IrodsAccessTicketMixin, TestProjectPermissionBase -): - """Tests for iRORDS access ticket view permissions""" - - def _make_ticket(self): - """Make iRODS access ticket for testing""" - self.ticket = self.make_irods_ticket( - path='/sodarZone/some/path', - study=self.study, - assay=self.assay, - user=self.user_owner, - ) +class TestIrodsAccessTicketListView(SamplesheetsPermissionTestBase): + """Permission tests for IrodsAccessTicketListView""" def setUp(self): super().setUp() - self.investigation = self.import_isa_from_file(SHEET_PATH, self.project) - self.study = self.investigation.studies.first() - self.assay = self.study.assays.first() - - def test_ticket_list(self): - """Test IrodsAccessTicketListView permissions""" - url = reverse( + self.url = reverse( 'samplesheets:irods_tickets', kwargs={'project': self.project.sodar_uuid}, ) + + def test_get(self): + """Test IrodsAccessTicketListView GET""" good_users = [ self.superuser, self.user_owner_cat, @@ -1217,28 +1137,20 @@ def test_ticket_list(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_ticket_list_anon(self): - """Test IrodsAccessTicketListView with anonymous guest access""" - url = reverse( - 'samplesheets:irods_tickets', - kwargs={'project': self.project.sodar_uuid}, - ) + def test_get_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) - def test_ticket_list_archive(self): - """Test IrodsAccessTicketListView with archived project""" + def test_get_archive(self): + """Test GET with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:irods_tickets', - kwargs={'project': self.project.sodar_uuid}, - ) good_users = [ self.superuser, self.user_owner_cat, @@ -1255,17 +1167,24 @@ def test_ticket_list_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) + + +class TestIrodsAccessTicketCreateView(SamplesheetsPermissionTestBase): + """Permission tests for IrodsAccessTicketCreateView""" - def test_ticket_create(self): - """Test IrodsAccessTicketCreateView permissions""" - url = reverse( + def setUp(self): + super().setUp() + self.url = reverse( 'samplesheets:irods_ticket_create', kwargs={'project': self.project.sodar_uuid}, ) + + def test_get(self): + """Test IrodsAccessTicketCreateView GET""" good_users = [ self.superuser, self.user_owner_cat, @@ -1282,29 +1201,21 @@ def test_ticket_create(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_ticket_create_anon(self): - """Test IrodsAccessTicketCreateView with anonymous guest access""" - url = reverse( - 'samplesheets:irods_ticket_create', - kwargs={'project': self.project.sodar_uuid}, - ) + def test_get_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) - def test_ticket_create_archive(self): - """Test IrodsAccessTicketCreateView with archived project""" + def test_get_archive(self): + """Test GET with archived project""" # NOTE: Ticket creation should still be allowed self.project.set_archive() - url = reverse( - 'samplesheets:irods_ticket_create', - kwargs={'project': self.project.sodar_uuid}, - ) good_users = [ self.superuser, self.user_owner_cat, @@ -1321,18 +1232,32 @@ def test_ticket_create_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) + + +class TestIrodsAccessTicketUpdateView( + IrodsAccessTicketMixin, SamplesheetsPermissionTestBase +): + """Permission tests for IrodsAccessTicketUpdateView""" - def test_ticket_update(self): - """Test IrodsAccessTicketUpdateView permissions""" - self._make_ticket() - url = reverse( + def setUp(self): + super().setUp() + self.ticket = self.make_irods_ticket( + path=IRODS_TICKET_PATH, + study=self.study, + assay=self.assay, + user=self.user_owner, + ) + self.url = reverse( 'samplesheets:irods_ticket_update', kwargs={'irodsaccessticket': self.ticket.sodar_uuid}, ) + + def test_get(self): + """Test IrodsAccessTicketUpdateView GET""" good_users = [ self.superuser, self.user_owner_cat, @@ -1349,30 +1274,20 @@ def test_ticket_update(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_ticket_update_anon(self): - """Test IrodsAccessTicketUpdateView with anonymous guest access""" - self._make_ticket() - url = reverse( - 'samplesheets:irods_ticket_update', - kwargs={'irodsaccessticket': self.ticket.sodar_uuid}, - ) + def test_get_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) - def test_ticket_update_archive(self): - """Test IrodsAccessTicketUpdateView with archived project""" - self._make_ticket() + def test_get_archive(self): + """Test GET with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:irods_ticket_update', - kwargs={'irodsaccessticket': self.ticket.sodar_uuid}, - ) good_users = [ self.superuser, self.user_owner_cat, @@ -1389,18 +1304,32 @@ def test_ticket_update_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) + - def test_ticket_delete(self): - """Test IrodsAccessTicketDeleteView permissions""" - self._make_ticket() - url = reverse( +class TestIrodsAccessTicketDeleteView( + IrodsAccessTicketMixin, SamplesheetsPermissionTestBase +): + """Permission tests for IrodsAccessTicketDeleteView""" + + def setUp(self): + super().setUp() + self.ticket = self.make_irods_ticket( + path=IRODS_TICKET_PATH, + study=self.study, + assay=self.assay, + user=self.user_owner, + ) + self.url = reverse( 'samplesheets:irods_ticket_delete', kwargs={'irodsaccessticket': self.ticket.sodar_uuid}, ) + + def test_get(self): + """Test IrodsAccessTicketDeleteView GET""" good_users = [ self.superuser, self.user_owner_cat, @@ -1417,51 +1346,377 @@ def test_ticket_delete(self): self.user_no_roles, self.anonymous, ] - self.assert_response( - url, good_users, 200, cleanup_method=self._make_ticket + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) + self.project.set_public() + self.assert_response(self.url, bad_users, 302) + + @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) + def test_get_anon(self): + """Test GET with anonymous guest access""" + self.project.set_public() + self.assert_response(self.url, self.anonymous, 302) + + def test_get_archive(self): + """Test GET with archived project""" + self.project.set_archive() + good_users = [ + self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + ] + bad_users = [ + self.user_guest_cat, + self.user_finder_cat, + self.user_guest, + self.user_no_roles, + self.anonymous, + ] + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) + self.project.set_public() + self.assert_response(self.url, bad_users, 302) + + +class TestIrodsDataRequestListView(SamplesheetsPermissionTestBase): + """Permission tests for IrodsDataRequestListView""" + + def setUp(self): + super().setUp() + self.url = reverse( + 'samplesheets:irods_requests', + kwargs={'project': self.project.sodar_uuid}, ) - self.assert_response(url, bad_users, 302) + + def test_get(self): + """Test IrodsDataRequestListView GET""" + good_users = [ + self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + ] + bad_users = [ + self.user_guest_cat, + self.user_finder_cat, + self.user_guest, + self.user_no_roles, + self.anonymous, + ] + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_ticket_delete_anon(self): - """Test IrodsAccessTicketDeleteView with anonymous guest access""" - self._make_ticket() - url = reverse( - 'samplesheets:irods_ticket_delete', - kwargs={'irodsaccessticket': self.ticket.sodar_uuid}, + def test_get_anon(self): + """Test GET with anonymous guest access""" + self.project.set_public() + self.assert_response(self.url, self.anonymous, 302) + + def test_get_archive(self): + """Test GET with archived project""" + self.project.set_archive() + good_users = [self.superuser] + bad_users = [ + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + self.user_guest_cat, + self.user_finder_cat, + self.user_guest, + self.user_no_roles, + self.anonymous, + ] + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) + self.project.set_public() + self.assert_response(self.url, bad_users, 302) + + +class TestIrodsDataRequestCreateView(SamplesheetsPermissionTestBase): + """Permission tests for IrodsDataRequestCreateView""" + + def setUp(self): + super().setUp() + self.url = reverse( + 'samplesheets:irods_request_create', + kwargs={'project': self.project.sodar_uuid}, ) + + def test_get(self): + """Test IrodsDataRequestCreateView GET""" + good_users = [ + self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + ] + bad_users = [ + self.user_guest_cat, + self.user_finder_cat, + self.user_guest, + self.user_no_roles, + self.anonymous, + ] + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) + self.project.set_public() + self.assert_response(self.url, bad_users, 302) + + @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) + def test_get_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 302) + self.assert_response(self.url, self.anonymous, 302) - def test_ticket_delete_archive(self): - """Test IrodsAccessTicketDeleteView with archived project""" - self._make_ticket() + def test_get_archive(self): + """Test GET with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:irods_ticket_delete', - kwargs={'irodsaccessticket': self.ticket.sodar_uuid}, + good_users = [self.superuser] + bad_users = [ + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + self.user_guest_cat, + self.user_finder_cat, + self.user_guest, + self.user_no_roles, + self.anonymous, + ] + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) + self.project.set_public() + self.assert_response(self.url, bad_users, 302) + + +class TestIrodsDataRequestUpdateView( + IrodsDataRequestMixin, SamplesheetsPermissionTestBase +): + """Permission tests for IrodsDataRequestUpdateView""" + + def setUp(self): + super().setUp() + self.request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=IRODS_FILE_PATH, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user_contributor, ) + self.url = reverse( + 'samplesheets:irods_request_update', + kwargs={'irodsdatarequest': self.request.sodar_uuid}, + ) + + def test_get(self): + """Test IrodsDataRequestUpdateView GET""" good_users = [ self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, # Request creator + ] + bad_users = [ + self.user_contributor_cat, + self.user_guest_cat, + self.user_finder_cat, + self.user_guest, + self.user_no_roles, + self.anonymous, + ] + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) + self.project.set_public() + self.assert_response(self.url, bad_users, 302) + + @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) + def test_get_anon(self): + """Test GET with anonymous guest access""" + self.project.set_public() + self.assert_response(self.url, self.anonymous, 302) + + def test_get_archive(self): + """Test GET with archived project""" + self.project.set_archive() + good_users = [self.superuser] + bad_users = [ self.user_owner_cat, self.user_delegate_cat, self.user_contributor_cat, self.user_owner, self.user_delegate, self.user_contributor, + self.user_guest_cat, + self.user_finder_cat, + self.user_guest, + self.user_no_roles, + self.anonymous, + ] + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) + self.project.set_public() + self.assert_response(self.url, bad_users, 302) + + +class TestIrodsDataRequestAcceptView( + IrodsDataRequestMixin, SamplesheetsPermissionTestBase +): + """Permission tests for IrodsDataRequestAcceptView""" + + def setUp(self): + super().setUp() + self.request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=IRODS_FILE_PATH, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user_contributor, + ) + self.url = reverse( + 'samplesheets:irods_request_accept', + kwargs={'irodsdatarequest': self.request.sodar_uuid}, + ) + + def test_get(self): + """Test IrodsDataRequestAcceptView GET""" + good_users = [ + self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_owner, + self.user_delegate, ] bad_users = [ + self.user_contributor_cat, self.user_guest_cat, self.user_finder_cat, + self.user_contributor, self.user_guest, self.user_no_roles, self.anonymous, ] - self.assert_response( - url, good_users, 200, cleanup_method=self._make_ticket + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) + self.project.set_public() + self.assert_response(self.url, bad_users, 302) + + @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) + def test_get_anon(self): + """Test GET with anonymous guest access""" + self.project.set_public() + self.assert_response(self.url, self.anonymous, 302) + + def test_get_archive(self): + """Test GET with archived project""" + self.project.set_archive() + good_users = [self.superuser] + bad_users = [ + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + self.user_guest_cat, + self.user_finder_cat, + self.user_guest, + self.user_no_roles, + self.anonymous, + ] + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) + self.project.set_public() + self.assert_response(self.url, bad_users, 302) + + +# NOTE: Batch views always redirect, they should be tested in taskflow view +# tests instead + + +class TestIrodsDataRequestDeleteView( + IrodsDataRequestMixin, SamplesheetsPermissionTestBase +): + """Permission tests for TestIrodsDataRequestDeleteAPIView""" + + def setUp(self): + super().setUp() + self.request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=IRODS_FILE_PATH, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user_contributor, ) - self.assert_response(url, bad_users, 302) + self.url = reverse( + 'samplesheets:irods_request_delete', + kwargs={'irodsdatarequest': self.request.sodar_uuid}, + ) + + def test_get(self): + """Test TestIrodsDataRequestDeleteAPIView GET""" + good_users = [ + self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, # Request creator + ] + bad_users = [ + self.user_contributor_cat, + self.user_guest_cat, + self.user_finder_cat, + self.user_guest, + self.user_no_roles, + ] + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) + self.project.set_public() + self.assert_response(self.url, bad_users, 302) + + @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) + def test_get_anon(self): + """Test GET with anonymous guest access""" + self.project.set_public() + self.assert_response(self.url, self.anonymous, 302) + + def test_get_archive(self): + """Test GET with archived project""" + self.project.set_archive() + good_users = [self.superuser] + bad_users = [ + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + self.user_guest_cat, + self.user_finder_cat, + self.user_guest, + self.user_no_roles, + self.anonymous, + ] + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 302) self.project.set_public() - self.assert_response(url, bad_users, 302) + self.assert_response(self.url, bad_users, 302) diff --git a/samplesheets/tests/test_permissions_ajax.py b/samplesheets/tests/test_permissions_ajax.py index 2f42b7c6..c8b76b71 100644 --- a/samplesheets/tests/test_permissions_ajax.py +++ b/samplesheets/tests/test_permissions_ajax.py @@ -8,8 +8,14 @@ from projectroles.tests.test_permissions import TestProjectPermissionBase from projectroles.utils import build_secret -from samplesheets.models import ISATab +from samplesheets.models import ( + ISATab, + IrodsDataRequest, + IRODS_REQUEST_ACTION_DELETE, + IRODS_REQUEST_STATUS_ACTIVE, +) from samplesheets.tests.test_io import SampleSheetIOMixin, SHEET_DIR +from samplesheets.tests.test_models import IrodsDataRequestMixin app_settings = AppSettingAPI() @@ -21,12 +27,13 @@ REMOTE_SITE_URL = 'https://sodar.bihealth.org' REMOTE_SITE_SECRET = build_secret() INVALID_SECRET = build_secret() +IRODS_FILE_PATH = '/sodarZone/path/test1.txt' -class TestSampleSheetsAjaxPermissions( +class SampleSheetsAjaxPermissionTestBase( SampleSheetIOMixin, TestProjectPermissionBase ): - """Tests for samplesheets Ajax view permissions""" + """Base test class for samplesheets Ajax view permissions""" def setUp(self): super().setUp() @@ -34,12 +41,19 @@ def setUp(self): self.study = self.investigation.studies.first() self.assay = self.study.assays.first() - def test_context(self): - """Test SheetContextAjaxView permissions""" - url = reverse( + +class TestSheetContextAjaxView(SampleSheetsAjaxPermissionTestBase): + """Permission tests for SheetContextAjaxView""" + + def setUp(self): + super().setUp() + self.url = reverse( 'samplesheets:ajax_context', kwargs={'project': self.project.sodar_uuid}, ) + + def test_get(self): + """Test SheetContextAjaxView GET""" good_users = [ self.superuser, self.user_owner_cat, # Inherited @@ -52,32 +66,24 @@ def test_context(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 403) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 403) # Test public project self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 403) + self.assert_response(self.url, self.anonymous, 403) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_context_anon(self): - """Test SheetContextAjaxView with anonymous guest access""" - url = reverse( - 'samplesheets:ajax_context', - kwargs={'project': self.project.sodar_uuid}, - ) + def test_get_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 200) + self.assert_response(self.url, self.anonymous, 200) - def test_context_archive(self): - """Test SheetContextAjaxView with archived project""" + def test_get_archive(self): + """Test GET with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:ajax_context', - kwargs={'project': self.project.sodar_uuid}, - ) good_users = [ self.superuser, self.user_owner_cat, @@ -90,21 +96,29 @@ def test_context_archive(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 403) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 403) # Test public project self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 403) + self.assert_response(self.url, self.anonymous, 403) + - def test_study_tables(self): - """Test StudyTablesAjaxView permissions""" - url = reverse( +class TestStudyTablesAjaxView(SampleSheetsAjaxPermissionTestBase): + """Permission tests for StudyTablesAjaxView""" + + def setUp(self): + super().setUp() + self.url = reverse( 'samplesheets:ajax_study_tables', kwargs={'study': self.study.sodar_uuid}, ) + self.edit_data = {'edit': 1} + + def test_get(self): + """Test StudyTablesAjaxView GET""" good_users = [ self.superuser, self.user_owner_cat, @@ -117,31 +131,23 @@ def test_study_tables(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 403) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 403) self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 403) + self.assert_response(self.url, self.anonymous, 403) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_study_tables_anon(self): - """Test StudyTablesAjaxView with anonymous guest access""" - url = reverse( - 'samplesheets:ajax_study_tables', - kwargs={'study': self.study.sodar_uuid}, - ) + def test_get_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 200) + self.assert_response(self.url, self.anonymous, 200) - def test_study_tables_archive(self): - """Test StudyTablesAjaxView with archived project""" + def test_get_archive(self): + """Test GET with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:ajax_study_tables', - kwargs={'study': self.study.sodar_uuid}, - ) good_users = [ self.superuser, self.user_owner_cat, @@ -154,24 +160,19 @@ def test_study_tables_archive(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 403) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 403) self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 403) + self.assert_response(self.url, self.anonymous, 403) - def test_study_tables_edit(self): - """Test StudyTablesAjaxView with edit mode""" + def test_get_edit(self): + """Test GET with edit mode""" app_settings.set( 'samplesheets', 'allow_editing', True, project=self.project ) - url = reverse( - 'samplesheets:ajax_study_tables', - kwargs={'study': self.study.sodar_uuid}, - ) - get_data = {'edit': 1} good_users = [ self.superuser, self.user_owner_cat, @@ -188,36 +189,26 @@ def test_study_tables_edit(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200, data=get_data) - self.assert_response(url, bad_users, 403, data=get_data) + self.assert_response(self.url, good_users, 200, data=self.edit_data) + self.assert_response(self.url, bad_users, 403, data=self.edit_data) self.project.set_public() - self.assert_response(url, bad_users, 403, data=get_data) + self.assert_response(self.url, bad_users, 403, data=self.edit_data) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_study_tables_edit_anon(self): - """Test StudyTablesAjaxView with edit mode and anon access""" + def test_get_edit_anon(self): + """Test GET with edit mode and anon access""" app_settings.set( 'samplesheets', 'allow_editing', True, project=self.project ) - url = reverse( - 'samplesheets:ajax_study_tables', - kwargs={'study': self.study.sodar_uuid}, - ) - get_data = {'edit': 1} self.project.set_public() - self.assert_response(url, self.anonymous, 403, data=get_data) + self.assert_response(self.url, self.anonymous, 403, data=self.edit_data) - def test_study_tables_edit_archive(self): - """Test StudyTablesAjaxView with edit mode and archived project""" + def test_get_edit_archive(self): + """Test GET with edit mode and archived project""" self.project.set_archive() app_settings.set( 'samplesheets', 'allow_editing', True, project=self.project ) - url = reverse( - 'samplesheets:ajax_study_tables', - kwargs={'study': self.study.sodar_uuid}, - ) - get_data = {'edit': 1} good_users = [self.superuser] bad_users = [ self.user_owner_cat, @@ -232,21 +223,16 @@ def test_study_tables_edit_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200, data=get_data) - self.assert_response(url, bad_users, 403, data=get_data) + self.assert_response(self.url, good_users, 200, data=self.edit_data) + self.assert_response(self.url, bad_users, 403, data=self.edit_data) self.project.set_public() - self.assert_response(url, bad_users, 403, data=get_data) + self.assert_response(self.url, bad_users, 403, data=self.edit_data) - def test_study_tables_not_allowed(self): - """Test StudyTablesAjaxView with edit mode but disallowed""" + def test_get_not_allowed(self): + """Test GET with edit mode but disallowed""" app_settings.set( 'samplesheets', 'allow_editing', False, project=self.project ) - url = reverse( - 'samplesheets:ajax_study_tables', - kwargs={'study': self.study.sodar_uuid}, - ) - get_data = {'edit': 1} users = [ self.superuser, self.user_owner_cat, @@ -261,21 +247,16 @@ def test_study_tables_not_allowed(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, users, 403, data=get_data) + self.assert_response(self.url, users, 403, data=self.edit_data) self.project.set_public() - self.assert_response(url, users, 403, data=get_data) + self.assert_response(self.url, users, 403, data=self.edit_data) - def test_study_tables_not_allowed_archive(self): - """Test StudyTablesAjaxView with disallowed edit mode and archived project""" + def test_get_not_allowed_archive(self): + """Test GET with disallowed edit mode and archived project""" self.project.set_archive() app_settings.set( 'samplesheets', 'allow_editing', False, project=self.project ) - url = reverse( - 'samplesheets:ajax_study_tables', - kwargs={'study': self.study.sodar_uuid}, - ) - get_data = {'edit': 1} users = [ self.superuser, self.user_owner_cat, @@ -290,16 +271,23 @@ def test_study_tables_not_allowed_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, users, 403, data=get_data) + self.assert_response(self.url, users, 403, data=self.edit_data) self.project.set_public() - self.assert_response(url, users, 403, data=get_data) + self.assert_response(self.url, users, 403, data=self.edit_data) + - def test_study_links(self): - """Test StudyLinksAjaxView permissions""" - url = reverse( +class TestStudyLinksAjaxView(SampleSheetsAjaxPermissionTestBase): + """Permission tests for StudyLinksAjaxView""" + + def setUp(self): + super().setUp() + self.url = reverse( 'samplesheets:ajax_study_links', kwargs={'study': self.study.sodar_uuid}, ) + + def test_get(self): + """Test StudyLinksAjaxView GET""" good_users = [ self.superuser, self.user_owner_cat, @@ -312,16 +300,12 @@ def test_study_links(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 404) # No plugin - self.assert_response(url, bad_users, 403) + self.assert_response(self.url, good_users, 404) # No plugin + self.assert_response(self.url, bad_users, 403) - def test_study_links_archive(self): - """Test StudyLinksAjaxView with archived project""" + def test_get_archive(self): + """Test GET with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:ajax_study_links', - kwargs={'study': self.study.sodar_uuid}, - ) good_users = [ self.superuser, self.user_owner_cat, @@ -334,15 +318,22 @@ def test_study_links_archive(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 404) # No plugin - self.assert_response(url, bad_users, 403) + self.assert_response(self.url, good_users, 404) # No plugin + self.assert_response(self.url, bad_users, 403) + + +class TestSheetWarningsAjaxView(SampleSheetsAjaxPermissionTestBase): + """Permission tests for SheetWarningsAjaxView""" - def test_sheet_warnings(self): - """Test SheetWarningsAjaxView permissions""" - url = reverse( + def setUp(self): + super().setUp() + self.url = reverse( 'samplesheets:ajax_warnings', kwargs={'project': self.project.sodar_uuid}, ) + + def test_get(self): + """Test SheetWarningsAjaxView GET""" good_users = [ self.superuser, self.user_owner_cat, @@ -355,31 +346,23 @@ def test_sheet_warnings(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 403) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 403) self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 403) + self.assert_response(self.url, self.anonymous, 403) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_sheet_warnings_anon(self): - """Test SampleSheetWarningsAjaxView with anonymous guest access""" - url = reverse( - 'samplesheets:ajax_warnings', - kwargs={'project': self.project.sodar_uuid}, - ) + def test_get_anon(self): + """Test GET with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 200) + self.assert_response(self.url, self.anonymous, 200) - def test_sheet_warnings_archive(self): - """Test SheetWarningsAjaxView with archived project""" + def test_get_archive(self): + """Test GET with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:ajax_warnings', - kwargs={'project': self.project.sodar_uuid}, - ) good_users = [ self.superuser, self.user_owner_cat, @@ -392,20 +375,27 @@ def test_sheet_warnings_archive(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 403) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 403) self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 403) + self.assert_response(self.url, self.anonymous, 403) + + +class TestSheetCellEditAjaxView(SampleSheetsAjaxPermissionTestBase): + """Permission tests for SheetCellEditAjaxView""" - def test_cell_edit(self): - """Test SheetCellEditAjaxView permissions""" - url = reverse( + def setUp(self): + super().setUp() + self.url = reverse( 'samplesheets:ajax_edit_cell', kwargs={'project': self.project.sodar_uuid}, ) + + def test_post(self): + """Test SheetCellEditAjaxView POST""" good_users = [ self.superuser, self.user_owner_cat, @@ -422,28 +412,20 @@ def test_cell_edit(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200, method='POST') - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, good_users, 200, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') self.project.set_public() - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_cell_edit_anon(self): - """Test SheetCellEditAjaxView with anonymous guest access""" - url = reverse( - 'samplesheets:ajax_edit_cell', - kwargs={'project': self.project.sodar_uuid}, - ) + def test_post_anon(self): + """Test POST with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 403, method='POST') + self.assert_response(self.url, self.anonymous, 403, method='POST') - def test_cell_edit_archive(self): - """Test SheetCellEditAjaxView with archived project""" + def test_post_archive(self): + """Test POST with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:ajax_edit_cell', - kwargs={'project': self.project.sodar_uuid}, - ) good_users = [self.superuser] bad_users = [ self.user_owner_cat, @@ -458,17 +440,24 @@ def test_cell_edit_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200, method='POST') - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, good_users, 200, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') self.project.set_public() - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') + - def test_row_insert(self): - """Test SheetRowInsertAjaxView permissions""" - url = reverse( +class TestSheetRowInsertAjaxView(SampleSheetsAjaxPermissionTestBase): + """Permission tests for SheetRowInsertAjaxView""" + + def setUp(self): + super().setUp() + self.url = reverse( 'samplesheets:ajax_edit_row_insert', kwargs={'project': self.project.sodar_uuid}, ) + + def test_post(self): + """Test SheetRowInsertAjaxView POST""" good_users = [ self.superuser, self.user_owner_cat, @@ -485,28 +474,20 @@ def test_row_insert(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200, method='POST') - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, good_users, 200, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') self.project.set_public() - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_row_insert_anon(self): - """Test SheetRowInsertAjaxView with anonymous guest access""" - url = reverse( - 'samplesheets:ajax_edit_row_insert', - kwargs={'project': self.project.sodar_uuid}, - ) + def test_post_anon(self): + """Test POST with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 403, method='POST') + self.assert_response(self.url, self.anonymous, 403, method='POST') - def test_row_insert_archive(self): - """Test SheetRowInsertAjaxView with archived project""" + def test_post_archive(self): + """Test POST with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:ajax_edit_row_insert', - kwargs={'project': self.project.sodar_uuid}, - ) good_users = [self.superuser] bad_users = [ self.user_owner_cat, @@ -521,17 +502,24 @@ def test_row_insert_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200, method='POST') - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, good_users, 200, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') self.project.set_public() - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') + - def test_row_delete(self): - """Test SheetRowDeleteAjaxView permissions""" - url = reverse( +class TestSheetRowDeleteAjaxView(SampleSheetsAjaxPermissionTestBase): + """Permission tests for SheetRowDeleteAjaxView""" + + def setUp(self): + super().setUp() + self.url = reverse( 'samplesheets:ajax_edit_row_delete', kwargs={'project': self.project.sodar_uuid}, ) + + def test_post(self): + """Test SheetRowDeleteAjaxView POST""" good_users = [ self.superuser, self.user_owner_cat, @@ -548,28 +536,20 @@ def test_row_delete(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200, method='POST') - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, good_users, 200, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') self.project.set_public() - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_row_delete_anon(self): - """Test SheetRowDeleteAjaxView with anonymous guest access""" - url = reverse( - 'samplesheets:ajax_edit_row_delete', - kwargs={'project': self.project.sodar_uuid}, - ) + def test_post_anon(self): + """Test POST with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 403, method='POST') + self.assert_response(self.url, self.anonymous, 403, method='POST') - def test_row_delete_archive(self): - """Test SheetRowDeleteAjaxView with archived project""" + def test_post_archive(self): + """Test POST with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:ajax_edit_row_delete', - kwargs={'project': self.project.sodar_uuid}, - ) good_users = [self.superuser] bad_users = [ self.user_owner_cat, @@ -584,17 +564,24 @@ def test_row_delete_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200, method='POST') - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, good_users, 200, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') self.project.set_public() - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') + + +class TestSheetVersionSaveAjaxView(SampleSheetsAjaxPermissionTestBase): + """Permission tests for SheetVersionSaveAjaxView""" - def test_version_save(self): - """Test SheetVersionSaveAjaxView permissions""" - url = reverse( + def setUp(self): + super().setUp() + self.url = reverse( 'samplesheets:ajax_version_save', kwargs={'project': self.project.sodar_uuid}, ) + + def test_post(self): + """Test SheetVersionSaveAjaxView POST""" good_users = [ self.superuser, self.user_owner_cat, @@ -611,28 +598,20 @@ def test_version_save(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200, method='POST') - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, good_users, 200, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') self.project.set_public() - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_version_save_anon(self): - """Test SheetVersionSaveAjaxView with anonymous guest access""" - url = reverse( - 'samplesheets:ajax_version_save', - kwargs={'project': self.project.sodar_uuid}, - ) + def test_post_anon(self): + """Test POST with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 403, method='POST') + self.assert_response(self.url, self.anonymous, 403, method='POST') - def test_version_save_archive(self): - """Test SheetVersionSaveAjaxView with archived project""" + def test_post_archive(self): + """Test POST with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:ajax_version_save', - kwargs={'project': self.project.sodar_uuid}, - ) good_users = [self.superuser] bad_users = [ self.user_owner_cat, @@ -647,17 +626,24 @@ def test_version_save_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200, method='POST') - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, good_users, 200, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') self.project.set_public() - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') + - def test_edit_finish(self): - """Test SheetEditFinishAjaxView permissions""" - url = reverse( +class TestSheetEditFinishAjaxView(SampleSheetsAjaxPermissionTestBase): + """Permission tests for SheetEditFinishAjaxView""" + + def setUp(self): + super().setUp() + self.url = reverse( 'samplesheets:ajax_edit_finish', kwargs={'project': self.project.sodar_uuid}, ) + + def test_post(self): + """Test SheetEditFinishAjaxView POST""" good_users = [ self.superuser, self.user_owner_cat, @@ -674,28 +660,20 @@ def test_edit_finish(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200, method='POST') - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, good_users, 200, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') self.project.set_public() - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_edit_finish_anon(self): - """Test SheetEditFinishAjaxView with anonymous guest access""" - url = reverse( - 'samplesheets:ajax_edit_finish', - kwargs={'project': self.project.sodar_uuid}, - ) + def test_post_anon(self): + """Test POST with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 403, method='POST') + self.assert_response(self.url, self.anonymous, 403, method='POST') - def test_edit_finish_archive(self): - """Test SheetEditFinishAjaxView with archived project""" + def test_post_archive(self): + """Test POST with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:ajax_edit_finish', - kwargs={'project': self.project.sodar_uuid}, - ) good_users = [self.superuser] bad_users = [ self.user_owner_cat, @@ -710,18 +688,25 @@ def test_edit_finish_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 200, method='POST') - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, good_users, 200, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') self.project.set_public() - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') + - def test_edit_config(self): - """Test SheetEditConfigAjaxView permissions""" - url = reverse( +class TestSheetEditConfigAjaxView(SampleSheetsAjaxPermissionTestBase): + """Permission tests for SheetEditConfigAjaxView""" + + def setUp(self): + super().setUp() + self.url = reverse( 'samplesheets:ajax_config_update', kwargs={'project': self.project.sodar_uuid}, ) # TODO: Set up request data + + def test_post(self): + """Test SheetEditConfigAjaxView POST""" good_users = [ self.superuser, self.user_owner_cat, @@ -738,28 +723,21 @@ def test_edit_config(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 400, method='POST') # No fields - self.assert_response(url, bad_users, 403, method='POST') + # NOTE: We need post data for status 200 + self.assert_response(self.url, good_users, 400, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') self.project.set_public() - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_edit_config_anon(self): - """Test SheetEditConfigAjaxView with anonymous guest access""" - url = reverse( - 'samplesheets:ajax_config_update', - kwargs={'project': self.project.sodar_uuid}, - ) + def test_post_anon(self): + """Test POST with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 403, method='POST') + self.assert_response(self.url, self.anonymous, 403, method='POST') - def test_edit_config_archive(self): - """Test SheetEditConfigAjaxView with archived project""" + def test_post_archive(self): + """Test POST with archived project""" self.project.set_archive() - url = reverse( - 'samplesheets:ajax_config_update', - kwargs={'project': self.project.sodar_uuid}, - ) good_users = [self.superuser] bad_users = [ self.user_owner_cat, @@ -774,18 +752,25 @@ def test_edit_config_archive(self): self.user_no_roles, self.anonymous, ] - self.assert_response(url, good_users, 400, method='POST') # No fields - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, good_users, 400, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') self.project.set_public() - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') + - def test_display_config(self): - """Test StudyDisplayConfigAjaxView permissions""" - url = reverse( +class TestStudyDisplayConfigAjaxView(SampleSheetsAjaxPermissionTestBase): + """Permission tests for StudyDisplayConfigAjaxView""" + + def setUp(self): + super().setUp() + self.url = reverse( 'samplesheets:ajax_display_update', kwargs={'study': self.study.sodar_uuid}, ) # TODO: Set up request data + + def test_post(self): + """Test StudyDisplayConfigAjaxView POST""" good_users = [ self.superuser, self.user_owner_cat, @@ -798,31 +783,23 @@ def test_display_config(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 400, method='POST') # No config - self.assert_response(url, bad_users, 403, method='POST') + # NOTE: We need post data for status 200 + self.assert_response(self.url, good_users, 400, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') self.project.set_public() - self.assert_response(url, self.user_guest, 400, method='POST') - self.assert_response(url, self.anonymous, 403, method='POST') + self.assert_response(self.url, self.user_guest, 400, method='POST') + self.assert_response(self.url, self.anonymous, 403, method='POST') @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_display_config_anon(self): - """Test StudyDisplayConfigAjaxView with anonymous guest access""" - url = reverse( - 'samplesheets:ajax_display_update', - kwargs={'study': self.study.sodar_uuid}, - ) - # TODO: Set up request data + def test_post_anon(self): + """Test POST with anonymous guest access""" self.project.set_public() - self.assert_response(url, self.anonymous, 400, method='POST') + self.assert_response(self.url, self.anonymous, 400, method='POST') - def test_display_config_archive(self): - """Test StudyDisplayConfigAjaxView with archived project""" + def test_post_archive(self): + """Test POST with archived project""" # NOTE: This is allowed with archived projects self.project.set_archive() - url = reverse( - 'samplesheets:ajax_display_update', - kwargs={'study': self.study.sodar_uuid}, - ) good_users = [ self.superuser, self.user_owner_cat, @@ -835,26 +812,30 @@ def test_display_config_archive(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 400, method='POST') # No config - self.assert_response(url, bad_users, 403, method='POST') + self.assert_response(self.url, good_users, 400, method='POST') + self.assert_response(self.url, bad_users, 403, method='POST') self.project.set_public() - self.assert_response(url, self.user_guest, 400, method='POST') - self.assert_response(url, self.anonymous, 403, method='POST') + self.assert_response(self.url, self.user_guest, 400, method='POST') + self.assert_response(self.url, self.anonymous, 403, method='POST') - # TODO: Test IrodsRequestCreateAjaxView (see sodar_core#823) - # TODO: Test IrodsRequestDeleteAjaxView (see sodar_core#823) - def test_version_compare(self): - """Test SheetVersionCompareAjaxView permissions""" - isa = ISATab.objects.first() - url = '{}?source={}&target={}'.format( +class TestSheetVersionCompareAjaxView(SampleSheetsAjaxPermissionTestBase): + """Permission tests for SheetVersionCompareAjaxView""" + + def setUp(self): + super().setUp() + self.isa_version = ISATab.objects.first() + self.url = '{}?source={}&target={}'.format( reverse( 'samplesheets:ajax_version_compare', kwargs={'project': self.project.sodar_uuid}, ), - str(isa.sodar_uuid), - str(isa.sodar_uuid), + str(self.isa_version.sodar_uuid), + str(self.isa_version.sodar_uuid), ) + + def test_get(self): + """Test SheetVersionCompareAjaxView GET""" good_users = [ self.superuser, self.user_owner_cat, @@ -867,55 +848,262 @@ def test_version_compare(self): self.user_guest, ] bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 403) + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 403) self.project.set_public() self.assert_response( - url, [self.user_finder_cat, self.user_no_roles], 200 + self.url, [self.user_finder_cat, self.user_no_roles], 200 ) - self.assert_response(url, self.anonymous, 403) + self.assert_response(self.url, self.anonymous, 403) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_version_compare_anon(self): - """Test SheetVersionCompareAjaxView with anonymous guest access""" - isa = ISATab.objects.first() - url = '{}?source={}&target={}'.format( - reverse( - 'samplesheets:ajax_version_compare', - kwargs={'project': self.project.sodar_uuid}, - ), - str(isa.sodar_uuid), - str(isa.sodar_uuid), + def test_get_anon(self): + """Test GET with anonymous guest access""" + self.project.set_public() + self.assert_response(self.url, self.anonymous, 200) + + def test_get_archive(self): + """Test GET with archived project""" + self.project.set_archive() + good_users = [ + self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_guest_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + self.user_guest, + ] + bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] + self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, bad_users, 403) + self.project.set_public() + self.assert_response(self.url, [self.user_no_roles], 200) + self.assert_response(self.url, [self.anonymous], 403) + + +class TestIrodsDataRequestCreateAjaxView(SampleSheetsAjaxPermissionTestBase): + """Permission tests for IrodsDataRequestCreateAjaxView""" + + @classmethod + def _cleanup(cls): + IrodsDataRequest.objects.all().delete() + + def setUp(self): + super().setUp() + self.url = reverse( + 'samplesheets:ajax_irods_request_create', + kwargs={'project': self.project.sodar_uuid}, + ) + self.post_data = {'path': IRODS_FILE_PATH} + + def test_post(self): + """Test IrodsDataRequestCreateAjaxView POST""" + good_users = [ + self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + ] + bad_users = [ + self.user_guest_cat, + self.user_finder_cat, + self.user_guest, + self.user_no_roles, + self.anonymous, + ] + self.assert_response( + self.url, + good_users, + 200, + method='POST', + data=self.post_data, + cleanup_method=self._cleanup, + ) + self.assert_response( + self.url, bad_users, 403, method='POST', data=self.post_data ) self.project.set_public() - self.assert_response(url, self.anonymous, 200) + self.assert_response( + self.url, + self.user_guest, + 403, + method='POST', + data=self.post_data, + cleanup_method=self._cleanup, + ) + self.assert_response( + self.url, self.anonymous, 403, method='POST', data=self.post_data + ) - def test_version_compare_archive(self): - """Test SheetVersionCompareAjaxView with archived project""" + @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) + def test_post_anon(self): + """Test POST with anonymous guest access""" + self.project.set_public() + self.assert_response( + self.url, self.anonymous, 403, method='POST', data=self.post_data + ) + + def test_post_archive(self): + """Test POST with archived project""" self.project.set_archive() - isa = ISATab.objects.first() - url = '{}?source={}&target={}'.format( - reverse( - 'samplesheets:ajax_version_compare', - kwargs={'project': self.project.sodar_uuid}, - ), - str(isa.sodar_uuid), - str(isa.sodar_uuid), + good_users = [self.superuser] + bad_users = [ + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_guest_cat, + self.user_finder_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + self.user_guest, + self.user_no_roles, + self.anonymous, + ] + self.assert_response( + self.url, + good_users, + 200, + method='POST', + data=self.post_data, + cleanup_method=self._cleanup, + ) + self.assert_response( + self.url, bad_users, 403, method='POST', data=self.post_data + ) + self.project.set_public() + self.assert_response( + self.url, + self.user_guest, + 403, + method='POST', + data=self.post_data, + cleanup_method=self._cleanup, ) + self.assert_response( + self.url, self.anonymous, 403, method='POST', data=self.post_data + ) + + +class TestIrodsDataRequestDeleteAjaxView( + IrodsDataRequestMixin, SampleSheetsAjaxPermissionTestBase +): + """Permission tests for IrodsDataRequestDeleteAjaxView""" + + def _cleanup(self): + self._make_request() + + def _make_request(self): + self.request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=IRODS_FILE_PATH, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user_contributor, + ) + + def setUp(self): + super().setUp() + self.url = reverse( + 'samplesheets:ajax_irods_request_delete', + kwargs={'project': self.project.sodar_uuid}, + ) + self.post_data = {'path': IRODS_FILE_PATH} + self._make_request() + + def test_post(self): + """Test IrodsDataRequestDeleteAjaxView POST""" good_users = [ self.superuser, + self.user_contributor, # Request creator + ] + bad_users = [ + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_owner, + self.user_delegate, + self.user_guest_cat, + self.user_finder_cat, + self.user_guest, + self.user_no_roles, + self.anonymous, + ] + self.assert_response( + self.url, + good_users, + 200, + method='POST', + data=self.post_data, + cleanup_method=self._cleanup, + ) + self.assert_response( + self.url, bad_users, 403, method='POST', data=self.post_data + ) + self.project.set_public() + self.assert_response( + self.url, + self.user_guest, + 403, + method='POST', + data=self.post_data, + cleanup_method=self._cleanup, + ) + self.assert_response( + self.url, self.anonymous, 403, method='POST', data=self.post_data + ) + + @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) + def test_post_anon(self): + """Test POST with anonymous guest access""" + self.project.set_public() + self.assert_response( + self.url, self.anonymous, 403, method='POST', data=self.post_data + ) + + def test_post_archive(self): + """Test POST with archived project""" + self.project.set_archive() + good_users = [self.superuser] + bad_users = [ self.user_owner_cat, self.user_delegate_cat, self.user_contributor_cat, self.user_guest_cat, + self.user_finder_cat, self.user_owner, self.user_delegate, self.user_contributor, self.user_guest, + self.user_no_roles, + self.anonymous, ] - bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(url, good_users, 200) - self.assert_response(url, bad_users, 403) + self.assert_response( + self.url, + good_users, + 200, + method='POST', + data=self.post_data, + cleanup_method=self._cleanup, + ) + self.assert_response( + self.url, bad_users, 403, method='POST', data=self.post_data + ) self.project.set_public() - self.assert_response(url, [self.user_no_roles], 200) - self.assert_response(url, [self.anonymous], 403) + self.assert_response( + self.url, + self.user_guest, + 403, + method='POST', + data=self.post_data, + cleanup_method=self._cleanup, + ) + self.assert_response( + self.url, self.anonymous, 403, method='POST', data=self.post_data + ) diff --git a/samplesheets/tests/test_permissions_api.py b/samplesheets/tests/test_permissions_api.py index b75b9501..ba0e6ab2 100644 --- a/samplesheets/tests/test_permissions_api.py +++ b/samplesheets/tests/test_permissions_api.py @@ -1,5 +1,7 @@ """Tests for REST API View permissions in the samplesheets app""" +import uuid + from django.test import override_settings from django.urls import reverse @@ -9,8 +11,13 @@ from projectroles.tests.test_permissions import TestProjectPermissionBase from projectroles.tests.test_permissions_api import TestProjectAPIPermissionBase -from samplesheets.models import Investigation +from samplesheets.models import ( + Investigation, + IRODS_REQUEST_ACTION_DELETE, + IRODS_REQUEST_STATUS_ACTIVE, +) from samplesheets.tests.test_io import SampleSheetIOMixin +from samplesheets.tests.test_models import IrodsDataRequestMixin from samplesheets.tests.test_permissions import ( SHEET_PATH, REMOTE_SITE_NAME, @@ -20,10 +27,12 @@ ) +# Local constants +IRODS_FILE_PATH = '/sodarZone/path/test1.txt' + + class TestInvestigationRetrieveAPIView( SampleSheetIOMixin, - RemoteSiteMixin, - RemoteProjectMixin, TestProjectAPIPermissionBase, ): """Tests for InvestigationRetrieveAPIView permissions""" @@ -35,7 +44,7 @@ def setUp(self): self.assay = self.study.assays.first() def test_get(self): - """Test get() in InvestigationRetrieveAPIView""" + """Test InvestigationRetrieveAPIView GET""" url = reverse( 'samplesheets:api_investigation_retrieve', kwargs={'project': self.project.sodar_uuid}, @@ -65,7 +74,7 @@ def test_get(self): @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) def test_get_anon(self): - """Test get() with anonymous guest access""" + """Test GET with anonymous guest access""" url = reverse( 'samplesheets:api_investigation_retrieve', kwargs={'project': self.project.sodar_uuid}, @@ -74,7 +83,7 @@ def test_get_anon(self): self.assert_response_api(url, self.anonymous, 200) def test_get_archive(self): - """Test get() with archived project""" + """Test GET with archived project""" self.project.set_archive() url = reverse( 'samplesheets:api_investigation_retrieve', @@ -104,12 +113,7 @@ def test_get_archive(self): self.assert_response_api(url, self.anonymous, 401) -class TestSheetImportAPIView( - SampleSheetIOMixin, - RemoteSiteMixin, - RemoteProjectMixin, - TestProjectAPIPermissionBase, -): +class TestSheetImportAPIView(SampleSheetIOMixin, TestProjectAPIPermissionBase): """Tests for SheetImportAPIView permissions""" def _cleanup_import(self): @@ -126,7 +130,7 @@ def tearDown(self): super().tearDown() def test_post(self): - """Test post() in SampleSheetImportAPIView""" + """Test SampleSheetImportAPIView POST""" url = reverse( 'samplesheets:api_import', kwargs={'project': self.project.sodar_uuid}, @@ -195,7 +199,7 @@ def test_post(self): @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) def test_post_anon(self): - """Test post() with anonymous guest access""" + """Test POST with anonymous guest access""" url = reverse( 'samplesheets:api_import', kwargs={'project': self.project.sodar_uuid}, @@ -211,7 +215,7 @@ def test_post_anon(self): ) def test_post_archive(self): - """Test post() with archived project""" + """Test POST with archived project""" self.project.set_archive() url = reverse( 'samplesheets:api_import', @@ -280,8 +284,6 @@ def test_post_archive(self): class TestSheetISAExportAPIView( SampleSheetIOMixin, - RemoteSiteMixin, - RemoteProjectMixin, TestProjectAPIPermissionBase, ): """Tests for SheetISAExportAPIView permissions""" @@ -293,7 +295,7 @@ def setUp(self): self.assay = self.study.assays.first() def test_get(self): - """Test get() in SampleSheetISAExportAPIView""" + """Test SampleSheetISAExportAPIView GET""" url = reverse( 'samplesheets:api_export_zip', kwargs={'project': self.project.sodar_uuid}, @@ -319,7 +321,7 @@ def test_get(self): @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) def test_get_anon(self): - """Test get() with anonymous guest access""" + """Test GET with anonymous guest access""" url = reverse( 'samplesheets:api_export_zip', kwargs={'project': self.project.sodar_uuid}, @@ -328,7 +330,7 @@ def test_get_anon(self): self.assert_response_api(url, self.anonymous, 200) def test_get_archive(self): - """Test get() with archived project""" + """Test GET with archived project""" self.project.set_archive() url = reverse( 'samplesheets:api_export_zip', @@ -354,6 +356,310 @@ def test_get_archive(self): self.assert_response_api(url, self.anonymous, 401) +class TestIrodsDataRequestRetrieveAPIView( + IrodsDataRequestMixin, TestProjectAPIPermissionBase +): + """Tests for TestIrodsDataRequestRetrieveAPIView permissions""" + + def setUp(self): + super().setUp() + self.request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=IRODS_FILE_PATH, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user_contributor, + ) + self.url = reverse( + 'samplesheets:api_irods_request_retrieve', + kwargs={'irodsdatarequest': self.request.sodar_uuid}, + ) + + def test_get(self): + """Test IrodsDataRequestListAPIView GET""" + good_users = [ + self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + ] + bad_users = [ + self.user_guest_cat, + self.user_finder_cat, + self.user_guest, + self.user_no_roles, + ] + self.assert_response_api(self.url, good_users, 200) + self.assert_response_api(self.url, bad_users, 403) + self.assert_response_api(self.url, self.anonymous, 401) + # Test public project + self.project.set_public() + self.assert_response_api( + self.url, [self.user_finder_cat, self.user_no_roles], 403 + ) + self.assert_response_api(self.url, self.anonymous, 401) + + @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) + def test_get_anon(self): + """Test GET with anonymous guest access""" + self.project.set_public() + self.assert_response_api(self.url, self.anonymous, 401) + + def test_get_archive(self): + """Test GET with archived project""" + self.project.set_archive() + good_users = [self.superuser] + bad_users = [ + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_guest_cat, + self.user_finder_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + self.user_guest, + self.user_no_roles, + ] + self.assert_response_api(self.url, good_users, 200) + self.assert_response_api(self.url, bad_users, 403) + self.assert_response_api(self.url, self.anonymous, 401) + + +class TestIrodsDataRequestListAPIView(TestProjectAPIPermissionBase): + """Tests for TestIrodsDataRequestListAPIView permissions""" + + def setUp(self): + super().setUp() + self.url = reverse( + 'samplesheets:api_irods_request_list', + kwargs={'project': self.project.sodar_uuid}, + ) + + def test_get(self): + """Test IrodsDataRequestListAPIView GET""" + good_users = [ + self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + ] + bad_users = [ + self.user_guest_cat, + self.user_finder_cat, + self.user_guest, + self.user_no_roles, + ] + self.assert_response_api(self.url, good_users, 200) + self.assert_response_api(self.url, bad_users, 403) + self.assert_response_api(self.url, self.anonymous, 401) + # Test public project + self.project.set_public() + self.assert_response_api( + self.url, [self.user_finder_cat, self.user_no_roles], 403 + ) + self.assert_response_api(self.url, self.anonymous, 401) + + @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) + def test_get_anon(self): + """Test GET with anonymous guest access""" + self.project.set_public() + self.assert_response_api(self.url, self.anonymous, 401) + + def test_get_archive(self): + """Test GET with archived project""" + self.project.set_archive() + good_users = [self.superuser] + bad_users = [ + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_guest_cat, + self.user_finder_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + self.user_guest, + self.user_no_roles, + ] + self.assert_response_api(self.url, good_users, 200) + self.assert_response_api(self.url, bad_users, 403) + self.assert_response_api(self.url, self.anonymous, 401) + + +class TestIrodsDataRequestRejectAPIView( + IrodsDataRequestMixin, TestProjectAPIPermissionBase +): + """Test permissions for TestIrodsDataRequestRejectAPIView""" + + def _cleanup(self): + self.request.status = IRODS_REQUEST_STATUS_ACTIVE + self.request.save() + + def setUp(self): + super().setUp() + self.request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=IRODS_FILE_PATH, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user_contributor, + ) + self.url = reverse( + 'samplesheets:api_irods_request_reject', + kwargs={'irodsdatarequest': self.request.sodar_uuid}, + ) + + def test_reject(self): + """Test IrodsDataRequestRejectAPIView POST""" + good_users = [ + self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_owner, + self.user_delegate, + ] + bad_users = [ + self.user_contributor_cat, + self.user_guest_cat, + self.user_finder_cat, + self.user_contributor, + self.user_guest, + self.user_no_roles, + ] + self.assert_response_api( + self.url, + good_users, + 200, + method='POST', + cleanup_method=self._cleanup, + ) + self.assert_response_api(self.url, bad_users, 403, method='POST') + self.assert_response_api(self.url, self.anonymous, 401, method='POST') + + @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) + def test_accept_anon(self): + """Test POST in IrodsDataRequestRejectAPIView with anonymous access""" + self.assert_response_api(self.url, self.anonymous, 401, method='POST') + + def test_reject_archive(self): + """Test POST in IrodsDataRequestUpdateAPIView with archived project""" + self.project.set_archive() + good_users = [self.superuser] + bad_users = [ + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_guest_cat, + self.user_finder_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + self.user_guest, + self.user_no_roles, + ] + self.assert_response_api( + self.url, + good_users, + 200, + method='POST', + cleanup_method=self._cleanup, + ) + self.assert_response_api(self.url, bad_users, 403, method='POST') + self.assert_response_api(self.url, self.anonymous, 401, method='POST') + + +class TestIrodsDataRequestDestroyAPIView( + SampleSheetIOMixin, IrodsDataRequestMixin, TestProjectAPIPermissionBase +): + """Test permissions for IrodsDataRequestDestroyAPIView""" + + def _make_request(self): + self.request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=IRODS_FILE_PATH, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user_contributor, + ) + self.request.sodar_uuid = self.request_uuid + self.request.save() + self.url = reverse( + 'samplesheets:api_irods_request_delete', + kwargs={'irodsdatarequest': self.request.sodar_uuid}, + ) + + def setUp(self): + super().setUp() + self.request_uuid = uuid.uuid4() + self._make_request() + + def test_delete(self): + """Test IrodsDataRequestDestroyAPIView DELETE""" + good_users = [ + self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, # Request creator + ] + bad_users = [ + self.user_contributor_cat, + self.user_guest_cat, + self.user_finder_cat, + self.user_guest, + self.user_no_roles, + ] + self.assert_response_api( + self.url, + good_users, + 204, + method='DELETE', + cleanup_method=self._make_request, + ) + self.assert_response_api(self.url, bad_users, 403, method='DELETE') + self.assert_response_api(self.url, self.anonymous, 401, method='DELETE') + + @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) + def test_delete_anon(self): + """Test DELETE with anonymous access""" + self.project.set_public() + self.assert_response_api(self.url, self.anonymous, 401, method='DELETE') + + def test_delete_archive(self): + """Test DELETE with archived project""" + self.project.set_archive() + good_users = [self.superuser] + bad_users = [ + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_guest_cat, + self.user_finder_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + self.user_guest, + self.user_no_roles, + ] + self.assert_response_api( + self.url, + good_users, + 204, + method='DELETE', + cleanup_method=self._make_request, + ) + self.assert_response_api(self.url, bad_users, 403, method='DELETE') + self.assert_response_api(self.url, self.anonymous, 401, method='DELETE') + + class TestRemoteSheetGetAPIView( SampleSheetIOMixin, RemoteSiteMixin, @@ -379,7 +685,7 @@ def setUp(self): ) def test_get(self): - """Test RemoteSheetGetAPIView with correct access""" + """Test RemoteSheetGetAPIView GET""" # Create remote project self.make_remote_project( project_uuid=self.project.sodar_uuid, @@ -396,7 +702,7 @@ def test_get(self): self.assert_response(url, self.anonymous, 200) def test_get_invalid_access(self): - """Test RemoteSheetGetAPIView with invalid access level""" + """Test GET with invalid access level""" self.make_remote_project( project_uuid=self.project.sodar_uuid, site=self.target_site, @@ -412,7 +718,7 @@ def test_get_invalid_access(self): self.assert_response(url, self.anonymous, 401) def test_get_no_access(self): - """Test RemoteSheetGetAPIView with no remote access rights""" + """Test GET with no remote access rights""" url = reverse( 'samplesheets:api_remote_get', kwargs={ @@ -423,7 +729,7 @@ def test_get_no_access(self): self.assert_response(url, self.anonymous, 401) def test_get_invalid_secret(self): - """Test RemoteSheetGetAPIView with invalid remote site secret""" + """Test GET with invalid remote site secret""" self.make_remote_project( project_uuid=self.project.sodar_uuid, site=self.target_site, @@ -439,7 +745,7 @@ def test_get_invalid_secret(self): self.assert_response(url, self.anonymous, 401) def test_get_archive(self): - """Test RemoteSheetGetAPIView with archived project""" + """Test GET with archived project""" self.project.set_archive() self.make_remote_project( project_uuid=self.project.sodar_uuid, diff --git a/samplesheets/tests/test_permissions_api_taskflow.py b/samplesheets/tests/test_permissions_api_taskflow.py index ece22341..b36e65cb 100644 --- a/samplesheets/tests/test_permissions_api_taskflow.py +++ b/samplesheets/tests/test_permissions_api_taskflow.py @@ -10,8 +10,12 @@ # Taskflowbackend dependency from taskflowbackend.tests.base import TaskflowAPIPermissionTestBase -from samplesheets.models import IrodsDataRequest +from samplesheets.models import ( + IRODS_REQUEST_ACTION_DELETE, + IRODS_REQUEST_STATUS_ACTIVE, +) from samplesheets.tests.test_io import SampleSheetIOMixin +from samplesheets.tests.test_models import IrodsDataRequestMixin from samplesheets.tests.test_permissions import SHEET_PATH from samplesheets.tests.test_views_api_taskflow import ( IRODS_FILE_PATH, @@ -19,10 +23,37 @@ ) from samplesheets.tests.test_views_taskflow import ( SampleSheetTaskflowMixin, - TEST_FILE_NAME, + IRODS_FILE_NAME, ) +# Base Classes and Mixins ------------------------------------------------------ + + +class IrodsDataRequestAPIViewTestBase( + SampleSheetIOMixin, SampleSheetTaskflowMixin, TaskflowAPIPermissionTestBase +): + """Base class for iRODS data request API view permission tests""" + + def setUp(self): + super().setUp() + # Import investigation + self.investigation = self.import_isa_from_file(SHEET_PATH, self.project) + self.study = self.investigation.studies.first() + self.assay = self.study.assays.first() + # Set up iRODS data + self.make_irods_colls(self.investigation) + self.assay_path = self.irods_backend.get_path(self.assay) + self.obj_path = os.path.join(self.assay_path, IRODS_FILE_NAME) + self.md5_path = os.path.join(self.assay_path, IRODS_FILE_NAME + '.md5') + # Create objects + self.file_obj = self.irods.data_objects.create(self.obj_path) + self.md5_obj = self.irods.data_objects.create(self.md5_path) + + +# Test Classes ----------------------------------------------------------------- + + class TestSampleDataFileExistsAPIView( SampleSheetIOMixin, SampleSheetTaskflowMixin, TaskflowAPIPermissionTestBase ): @@ -42,10 +73,10 @@ def setUp(self): IRODS_FILE_PATH, coll_path, **{REG_CHKSUM_KW: ''} ) self.post_data = {'checksum': IRODS_FILE_MD5} + self.url = reverse('samplesheets:api_file_exists') def test_get(self): - """Test get() in SampleDataFileExistsAPIView""" - url = reverse('samplesheets:api_file_exists') + """Test SampleDataFileExistsAPIView GET""" good_users = [ self.superuser, self.user_owner_cat, @@ -59,13 +90,19 @@ def test_get(self): self.user_guest, self.user_no_roles, ] - self.assert_response_api(url, good_users, 200, data=self.post_data) - self.assert_response_api(url, self.anonymous, 401, data=self.post_data) + self.assert_response_api(self.url, good_users, 200, data=self.post_data) + self.assert_response_api( + self.url, self.anonymous, 401, data=self.post_data + ) + + @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) + def test_get_anon(self): + """Test GET with anonymous access""" + self.assert_response_api(self.url, self.anonymous, 401) def test_get_archive(self): - """Test get() with archived project""" + """Test GET with archived project""" self.project.set_archive() - url = reverse('samplesheets:api_file_exists') good_users = [ self.superuser, self.user_owner_cat, @@ -79,60 +116,88 @@ def test_get_archive(self): self.user_guest, self.user_no_roles, ] - self.assert_response_api(url, good_users, 200, data=self.post_data) - self.assert_response_api(url, self.anonymous, 401, data=self.post_data) + self.assert_response_api(self.url, good_users, 200, data=self.post_data) + self.assert_response_api( + self.url, self.anonymous, 401, data=self.post_data + ) -class TestIrodsRequestAPIViewBase( +class TestIrodsDataRequestListAPIView( SampleSheetIOMixin, SampleSheetTaskflowMixin, TaskflowAPIPermissionTestBase ): - """Base test class for IrodsRequestAPIView permission tests""" - - def create_request(self): - """Helper function to create a request""" - url = reverse( - 'samplesheets:api_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ) - # Set up post data - post_data = {'path': self.path + '/', 'description': 'bla'} - with self.login(self.superuser): - self.client.post(url, post_data) - obj = IrodsDataRequest.objects.first() - return obj + """Tests for IrodsDataRequestListAPIView permissions""" def setUp(self): super().setUp() - # Import investigation self.investigation = self.import_isa_from_file(SHEET_PATH, self.project) self.study = self.investigation.studies.first() self.assay = self.study.assays.first() - - # Set up iRODS data self.make_irods_colls(self.investigation) - self.assay_path = self.irods_backend.get_path(self.assay) - self.path = os.path.join(self.assay_path, TEST_FILE_NAME) - self.path_md5 = os.path.join(self.assay_path, f'{TEST_FILE_NAME}.md5') - # Create objects - self.file_obj = self.irods.data_objects.create(self.path) - self.md5_obj = self.irods.data_objects.create(self.path_md5) + self.url = reverse( + 'samplesheets:api_irods_request_list', + kwargs={'project': self.project.sodar_uuid}, + ) + + def test_get(self): + """Test IrodsDataRequestListAPIView GET""" + good_users = [ + self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + ] + bad_users = [ + self.user_guest_cat, + self.user_finder_cat, + self.user_guest, + self.user_no_roles, + ] + self.assert_response_api(self.url, good_users, 200) + self.assert_response_api(self.url, bad_users, 403) + self.assert_response_api(self.url, self.anonymous, 401) + + @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) + def test_get_anon(self): + """Test GET with anonymous access""" + self.assert_response_api(self.url, self.anonymous, 401) + + def test_get_archive(self): + """Test GET with archived project""" + self.project.set_archive() + good_users = [self.superuser] + bad_users = [ + self.user_owner_cat, + self.user_delegate_cat, + self.user_contributor_cat, + self.user_owner, + self.user_delegate, + self.user_contributor, + self.user_guest_cat, + self.user_finder_cat, + self.user_guest, + self.user_no_roles, + ] + self.assert_response_api(self.url, good_users, 200) + self.assert_response_api(self.url, bad_users, 403) + self.assert_response_api(self.url, self.anonymous, 401) -class TestIrodsRequestCreateAPIView(TestIrodsRequestAPIViewBase): - """Test permissions for IrodsRequestCreateAPIView""" +class TestIrodsDataRequestCreateAPIView(IrodsDataRequestAPIViewTestBase): + """Test permissions for IrodsDataRequestCreateAPIView""" def setUp(self): super().setUp() - # Set up URLs self.url = reverse( 'samplesheets:api_irods_request_create', kwargs={'project': self.project.sodar_uuid}, ) - # Set up post data - self.post_data = {'path': self.path + '/', 'description': 'bla'} + self.post_data = {'path': self.obj_path + '/', 'description': ''} def test_create(self): - """Test post() in IrodsRequestCreateAPIView""" + """Test IrodsDataRequestCreateAPIView POST""" good_users = [ self.superuser, self.user_owner_cat, @@ -148,9 +213,8 @@ def test_create(self): self.user_guest, self.user_no_roles, ] - self.assert_response_api( - self.url, good_users, 200, method='POST', data=self.post_data + self.url, good_users, 201, method='POST', data=self.post_data ) self.assert_response_api( self.url, bad_users, 403, method='POST', data=self.post_data @@ -161,7 +225,7 @@ def test_create(self): @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) def test_create_anon(self): - """Test post() in IrodsRequestCreateAPIView with anonymous access""" + """Test POST with anonymous access""" self.project.set_public() self.assert_response_api( self.url, @@ -171,8 +235,8 @@ def test_create_anon(self): data=self.post_data, ) - def test_create_archived(self): - """Test post() in IrodsRequestCreateAPIView with archived project""" + def test_create_archive(self): + """Test POST with archived project""" self.project.set_archive() good_users = [self.superuser] bad_users = [ @@ -187,9 +251,8 @@ def test_create_archived(self): self.user_guest, self.user_no_roles, ] - self.assert_response_api( - self.url, good_users, 200, method='POST', data=self.post_data + self.url, good_users, 201, method='POST', data=self.post_data ) self.assert_response_api( self.url, bad_users, 403, method='POST', data=self.post_data @@ -199,32 +262,40 @@ def test_create_archived(self): ) -class TestIrodsRequestUpdateAPIView(TestIrodsRequestAPIViewBase): - """Test permissions for IrodsRequestUpdateAPIView""" +class TestIrodsDataRequestUpdateAPIView( + IrodsDataRequestMixin, IrodsDataRequestAPIViewTestBase +): + """Test permissions for IrodsDataRequestUpdateAPIView""" def setUp(self): super().setUp() - self.irods_request = self.create_request() + self.request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=IRODS_FILE_PATH, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user_contributor, + ) self.url = reverse( 'samplesheets:api_irods_request_update', - kwargs={'irodsdatarequest': self.irods_request.sodar_uuid}, + kwargs={'irodsdatarequest': self.request.sodar_uuid}, ) - self.update_data = {'path': self.path, 'description': 'Updated'} + self.update_data = {'path': self.obj_path, 'description': 'Updated'} def test_update(self): - """Test post() in IrodsRequestUpdateAPIView""" + """Test IrodsDataRequestUpdateAPIView POST""" good_users = [ self.superuser, self.user_owner_cat, self.user_delegate_cat, self.user_owner, self.user_delegate, + self.user_contributor, # Request creator ] bad_users = [ self.user_contributor_cat, self.user_guest_cat, self.user_finder_cat, - self.user_contributor, self.user_guest, self.user_no_roles, ] @@ -240,14 +311,14 @@ def test_update(self): @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) def test_update_anon(self): - """Test post() in IrodsRequestUpdateAPIView with anonymous access""" + """Test POST with anonymous access""" self.project.set_public() self.assert_response_api( self.url, self.anonymous, 401, method='PUT', data=self.update_data ) - def test_update_archived(self): - """Test post() in IrodsRequestUpdateAPIView with archived project""" + def test_update_archive(self): + """Test POST with archived project""" self.project.set_archive() good_users = [self.superuser] bad_users = [ @@ -273,63 +344,34 @@ def test_update_archived(self): ) -class TestIrodsRequestDeleteAPIView(TestIrodsRequestAPIViewBase): - """Test permissions for IrodsRequestDeleteAPIView""" - - def test_delete(self): - """Test delete() in IrodsRequestDeleteAPIView""" - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_owner, - self.user_delegate, - ] - bad_users = [ - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] +# NOTE: For IrodsDataRequestDestroyAPIView, see test_permissions_api - for user in good_users: - obj = self.create_request() - self.url_delete = reverse( - 'samplesheets:api_irods_request_delete', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ) - self.assert_response_api( - self.url_delete, user, 200, method='DELETE' - ) - - obj = self.create_request() - self.url_delete = reverse( - 'samplesheets:api_irods_request_delete', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ) - self.assert_response_api( - self.url_delete, bad_users, 403, method='DELETE' - ) - # Test with anonymous access - with self.login(self.superuser): - obj = self.create_request() - self.url_delete = reverse( - 'samplesheets:api_irods_request_delete', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ) - self.assert_response_api( - self.url_delete, self.anonymous, 401, method='DELETE' - ) +class TestIrodsDataRequestAcceptAPIView( + IrodsDataRequestMixin, IrodsDataRequestAPIViewTestBase +): + """Test permissions for TestIrodsDataRequestAcceptAPIView""" + def _cleanup(self): + self.request.status = IRODS_REQUEST_STATUS_ACTIVE + self.request.save() -class TestIrodsRequestAcceptAPIView(TestIrodsRequestAPIViewBase): - """Test permissions for TestIrodsRequestAcceptAPIView""" + def setUp(self): + super().setUp() + self.request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=IRODS_FILE_PATH, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user_contributor, + ) + self.url = reverse( + 'samplesheets:api_irods_request_accept', + kwargs={'irodsdatarequest': self.request.sodar_uuid}, + ) def test_accept(self): - """Test post() in IrodsRequestAcceptAPIView""" + """Test IrodsDataRequestAcceptAPIView POST""" good_users = [ self.superuser, self.user_owner_cat, @@ -345,60 +387,23 @@ def test_accept(self): self.user_guest, self.user_no_roles, ] - for user in good_users: - obj = self.create_request() - self.url_accept = reverse( - 'samplesheets:api_irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ) - self.assert_response_api( - self.url_accept, - user, - 200, - method='POST', - ) - - obj = self.create_request() - self.url_accept = reverse( - 'samplesheets:api_irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ) self.assert_response_api( - self.url_accept, - bad_users, - 403, - method='POST', - ) - - obj = self.create_request() - self.url_accept = reverse( - 'samplesheets:api_irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ) - self.assert_response_api( - self.url_accept, - self.anonymous, - 401, + self.url, + good_users, + 200, method='POST', + cleanup_method=self._cleanup, ) + self.assert_response_api(self.url, bad_users, 403, method='POST') + self.assert_response_api(self.url, self.anonymous, 401, method='POST') @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) def test_accept_anon(self): - """Test post() in IrodsRequestAcceptAPIView with anonymous access""" - obj = self.create_request() - self.url_accept = reverse( - 'samplesheets:api_irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ) - self.assert_response_api( - self.url_accept, - self.anonymous, - 401, - method='POST', - ) + """Test POST with anonymous access""" + self.assert_response_api(self.url, self.anonymous, 401, method='POST') - def test_accept_archived(self): - """Test post() in IrodsRequestUpdateAPIView with archived project""" + def test_accept_archive(self): + """Test POST with archived project""" self.project.set_archive() good_users = [self.superuser] bad_users = [ @@ -413,139 +418,15 @@ def test_accept_archived(self): self.user_guest, self.user_no_roles, ] - for user in good_users: - obj = self.create_request() - self.url_accept = reverse( - 'samplesheets:api_irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ) - self.assert_response_api( - self.url_accept, - user, - 200, - method='POST', - ) - - obj = self.create_request() - self.url_accept = reverse( - 'samplesheets:api_irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ) self.assert_response_api( - self.url_accept, - bad_users, - 403, - method='POST', - ) - - obj = self.create_request() - self.url_accept = reverse( - 'samplesheets:api_irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ) - self.assert_response_api( - self.url_accept, - self.anonymous, - 401, + self.url, + good_users, + 200, method='POST', + cleanup_method=self._cleanup, ) + self.assert_response_api(self.url, bad_users, 403, method='POST') + self.assert_response_api(self.url, self.anonymous, 401, method='POST') -class TestIrodsRequestRejectAPIView(TestIrodsRequestAPIViewBase): - """Test permissions for TestIrodsRequestRejectAPIView""" - - def test_reject(self): - """Test get() in IrodsRequestRejectAPIView""" - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_owner, - self.user_delegate, - ] - bad_users = [ - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] - for user in good_users: - obj = self.create_request() - self.url_reject = reverse( - 'samplesheets:api_irods_request_reject', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ) - self.assert_response_api( - self.url_reject, - user, - 200, - method='GET', - ) - - obj = self.create_request() - self.url_reject = reverse( - 'samplesheets:api_irods_request_reject', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ) - self.assert_response_api( - self.url_reject, - bad_users, - 403, - method='GET', - ) - - obj = self.create_request() - self.url_reject = reverse( - 'samplesheets:api_irods_request_reject', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ) - self.assert_response_api( - self.url_reject, - self.anonymous, - 401, - method='GET', - ) - - @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) - def test_accept_anon(self): - """Test get() in IrodsRequestRejectAPIView with anonymous access""" - obj = self.create_request() - self.url_reject = reverse( - 'samplesheets:api_irods_request_reject', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ) - self.assert_response_api( - self.url_reject, - self.anonymous, - 401, - method='GET', - ) - - def test_reject_archived(self): - """Test post() in IrodsRequestUpdateAPIView with archived project""" - self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] - obj = self.create_request() - self.url_reject = reverse( - 'samplesheets:api_irods_request_reject', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ) - self.assert_response_api(self.url_reject, good_users, 200, method='GET') - self.assert_response_api(self.url_reject, bad_users, 403, method='GET') - self.assert_response_api( - self.url_reject, self.anonymous, 401, method='GET' - ) +# NOTE: For IrodsDataRequestRejectAPIView, see test_permissions_api diff --git a/samplesheets/tests/test_ui.py b/samplesheets/tests/test_ui.py index 12f35de8..ddd713b0 100644 --- a/samplesheets/tests/test_ui.py +++ b/samplesheets/tests/test_ui.py @@ -1,6 +1,7 @@ """UI tests for the samplesheets app""" import json +import os from cubi_isa_templates import _TEMPLATES as ISA_TEMPLATES from datetime import timedelta @@ -22,13 +23,16 @@ from samplesheets.constants import HIDDEN_SHEET_TEMPLATE_FIELDS from samplesheets.models import ( ISATab, - IRODS_DATA_REQUEST_STATUS_CHOICES, Investigation, + IRODS_REQUEST_ACTION_DELETE, + IRODS_REQUEST_STATUS_ACTIVE, ) + from samplesheets.tests.test_io import SampleSheetIOMixin, SHEET_DIR from samplesheets.tests.test_models import ( IrodsAccessTicketMixin, IrodsDataRequestMixin, + IRODS_REQUEST_DESC, ) from samplesheets.tests.test_sheet_config import ( SheetConfigMixin, @@ -47,6 +51,8 @@ # Local constants SHEET_PATH = SHEET_DIR + 'i_small.zip' DEFAULT_WAIT_ID = 'sodar-ss-vue-content' +IRODS_REQUEST_PATH = '/some/path/to/a/file' + with open(CONFIG_PATH_DEFAULT) as fp: CONFIG_DATA_DEFAULT = json.load(fp) with open(CONFIG_PATH_UPDATED) as fp: @@ -149,11 +155,13 @@ def test_render_alert(self): irods_backend = get_backend_api('omics_irods') self.investigation.irods_status = True self.investigation.save() - self.make_irods_data_request( + self.make_irods_request( project=self.project, - action='delete', - path=irods_backend.get_path(self.assay) + '/test/xxx.bam', - status='ACTIVE', + action=IRODS_REQUEST_ACTION_DELETE, + path=os.path.join( + irods_backend.get_path(self.assay), 'test', 'xxx.bam' + ), + status=IRODS_REQUEST_STATUS_ACTIVE, user=self.user_contributor, ) users = [ @@ -394,7 +402,7 @@ def test_render(self): self.selenium.find_element(By.ID, 'sodar-ss-alert-ticket-create') -class TestIrodsRequestCreateView(TestProjectSheetsUIBase): +class TestIrodsDataRequestCreateView(TestProjectSheetsUIBase): """Tests for iRODS request create view UI""" def setUp(self): @@ -414,28 +422,24 @@ def test_render_form(self): ) -class TestIrodsRequestUpdateView( +class TestIrodsDataRequestUpdateView( IrodsDataRequestMixin, TestProjectSheetsUIBase ): """Tests for irods request update view UI""" def setUp(self): super().setUp() - self.action = 'delete' - 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.request = self.make_irods_request( project=self.project, - action=self.action, - status=self.status, - path=self.path, - description=self.description, + action=IRODS_REQUEST_ACTION_DELETE, + status=IRODS_REQUEST_STATUS_ACTIVE, + path=IRODS_REQUEST_PATH, + description=IRODS_REQUEST_DESC, user=self.user_contributor, ) self.url = reverse( 'samplesheets:irods_request_update', - kwargs={'irodsdatarequest': self.irods_data_request.sodar_uuid}, + kwargs={'irodsdatarequest': self.request.sodar_uuid}, ) def test_render_form(self): @@ -448,28 +452,24 @@ def test_render_form(self): ) -class TestIrodsRequestDeleteView( +class TestIrodsDataRequestDeleteView( IrodsDataRequestMixin, TestProjectSheetsUIBase ): """Tests for iRODS request delete view UI""" def setUp(self): super().setUp() - self.action = 'delete' - 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.request = self.make_irods_request( project=self.project, - action=self.action, - status=self.status, - path=self.path, - description=self.description, + action=IRODS_REQUEST_ACTION_DELETE, + status=IRODS_REQUEST_STATUS_ACTIVE, + path=IRODS_REQUEST_PATH, + description=IRODS_REQUEST_DESC, user=self.user_contributor, ) self.url = reverse( 'samplesheets:irods_request_delete', - kwargs={'irodsdatarequest': self.irods_data_request.sodar_uuid}, + kwargs={'irodsdatarequest': self.request.sodar_uuid}, ) def test_render(self): @@ -482,28 +482,24 @@ def test_render(self): ) -class TestIrodsRequestAcceptView( +class TestIrodsDataRequestAcceptView( IrodsDataRequestMixin, TestProjectSheetsUIBase ): """Tests for iRODS request accept view UI""" def setUp(self): super().setUp() - self.action = 'delete' - 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.request = self.make_irods_request( project=self.project, - action=self.action, - status=self.status, - path=self.path, - description=self.description, + action=IRODS_REQUEST_ACTION_DELETE, + status=IRODS_REQUEST_STATUS_ACTIVE, + path=IRODS_REQUEST_PATH, + description=IRODS_REQUEST_DESC, user=self.user_contributor, ) self.url = reverse( 'samplesheets:irods_request_accept', - kwargs={'irodsdatarequest': self.irods_data_request.sodar_uuid}, + kwargs={'irodsdatarequest': self.request.sodar_uuid}, ) def test_render(self): @@ -513,21 +509,19 @@ def test_render(self): ) -class TestIrodsRequestListView(IrodsDataRequestMixin, TestProjectSheetsUIBase): +class TestIrodsDataRequestListView( + IrodsDataRequestMixin, TestProjectSheetsUIBase +): """Tests for iRODS request reject view UI""" def setUp(self): super().setUp() - self.action = 'delete' - 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.request = self.make_irods_request( project=self.project, - action=self.action, - status=self.status, - path=self.path, - description=self.description, + action=IRODS_REQUEST_ACTION_DELETE, + status=IRODS_REQUEST_STATUS_ACTIVE, + path=IRODS_REQUEST_PATH, + description=IRODS_REQUEST_DESC, user=self.user_contributor, ) self.url = reverse( diff --git a/samplesheets/tests/test_views.py b/samplesheets/tests/test_views.py index 83eff006..c57ffddf 100644 --- a/samplesheets/tests/test_views.py +++ b/samplesheets/tests/test_views.py @@ -36,7 +36,14 @@ from samplesheets.forms import TPL_DIR_FIELD from samplesheets.io import SampleSheetIO -from samplesheets.models import Investigation, Assay, ISATab +from samplesheets.models import ( + Investigation, + Assay, + ISATab, + IrodsDataRequest, + IRODS_REQUEST_ACTION_DELETE, + IRODS_REQUEST_STATUS_ACTIVE, +) from samplesheets.rendering import ( SampleSheetTableBuilder, STUDY_TABLE_CACHE_ITEM, @@ -47,7 +54,10 @@ SHEET_DIR, SHEET_DIR_SPECIAL, ) -from samplesheets.tests.test_models import SampleSheetModelMixin +from samplesheets.tests.test_models import ( + SampleSheetModelMixin, + IrodsDataRequestMixin, +) from samplesheets.tests.test_sheet_config import CONFIG_PATH_DEFAULT # TODO: This should not be required (see issue #1578) @@ -109,18 +119,19 @@ REMOTE_SITE_SECRET = build_secret() EDIT_NEW_VALUE_STR = 'edited value' DUMMY_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' +IRODS_BACKEND_SKIP_MSG = 'iRODS backend not enabled in settings' +IRODS_FILE_PATH = '/sodarZone/path/test1.txt' with open(CONFIG_PATH_DEFAULT) as fp: CONFIG_DATA_DEFAULT = json.load(fp) IRODS_BACKEND_ENABLED = ( True if 'omics_irods' in settings.ENABLED_BACKEND_PLUGINS else False ) -IRODS_BACKEND_SKIP_MSG = 'iRODS backend not enabled in settings' # TODO: Add testing for study table cache updates -class TestViewsBase( +class ViewTestBase( ProjectMixin, RoleMixin, RoleAssignmentMixin, SampleSheetIOMixin, TestCase ): """Base view for samplesheets views tests""" @@ -159,7 +170,7 @@ def setUp(self): ) -class TestProjectSheetsView(TestViewsBase): +class TestProjectSheetsView(ViewTestBase): """Tests for the project sheets view""" def setUp(self): @@ -187,7 +198,7 @@ def test_render_no_sheets(self): self.assertNotIn('tables', response.context) -class TestSheetImportView(SheetImportMixin, LandingZoneMixin, TestViewsBase): +class TestSheetImportView(SheetImportMixin, LandingZoneMixin, ViewTestBase): """Tests for the investigation import view""" def setUp(self): @@ -654,7 +665,7 @@ def test_import_no_plugin_assay(self): ) -class TestSheetTemplateSelectView(TestViewsBase): +class TestSheetTemplateSelectView(ViewTestBase): """Tests for SheetTemplateSelectView""" def test_render(self): @@ -691,7 +702,7 @@ def test_render_with_sheets(self): ) -class TestSheetTemplateCreateFormView(TestViewsBase): +class TestSheetTemplateCreateFormView(ViewTestBase): """Tests for SheetTemplateCreateFormView""" def _get_post_data(self, tpl_name): @@ -808,7 +819,7 @@ def test_post_multiple(self): self.assertEqual(self.project.investigations.count(), 1) -class TestSheetExcelExportView(TestViewsBase): +class TestSheetExcelExportView(ViewTestBase): """Tests for the sample sheet Excel export view""" def setUp(self): @@ -852,7 +863,7 @@ def test_render_assay(self): self.assertEqual(tl_event.event_name, 'sheet_export_excel') -class TestSheetISAExportView(TestViewsBase): +class TestSheetISAExportView(ViewTestBase): """Tests for the investigation ISA-Tab export view""" def setUp(self): @@ -919,7 +930,7 @@ def test_get_version(self): ) -class TestSheetDeleteView(TestViewsBase): +class TestSheetDeleteView(ViewTestBase): """Tests for the investigation delete view""" def setUp(self): @@ -1045,7 +1056,7 @@ def test_delete_study_cache(self): self.assertEqual(JSONCacheItem.objects.count(), 0) -class TestSheetVersionListView(TestViewsBase): +class TestSheetVersionListView(ViewTestBase): """Tests for the sample sheet version list view""" def test_render(self): @@ -1087,7 +1098,7 @@ def test_render_no_sheets(self): self.assertIsNone(response.context['current_version']) -class TestSheetVersionRestoreView(TestViewsBase): +class TestSheetVersionRestoreView(ViewTestBase): """Tests for the sample sheet version restore view""" def setUp(self): @@ -1173,7 +1184,7 @@ def test_restore_study_cache(self): self.assertEqual(JSONCacheItem.objects.count(), 1) -class TestSheetVersionUpdateView(TestViewsBase): +class TestSheetVersionUpdateView(ViewTestBase): """Tests for the sample sheet version update view""" def setUp(self): @@ -1218,7 +1229,7 @@ def test_update(self): self.assertEqual(self.isatab.date_created, date_created) -class TestSheetVersionDeleteView(TestViewsBase): +class TestSheetVersionDeleteView(ViewTestBase): """Tests for the sample sheet version delete view""" def setUp(self): @@ -1256,7 +1267,7 @@ def test_post(self): self.assertEqual(ISATab.objects.count(), 0) -class TestSheetVersionDeleteBatchView(SampleSheetModelMixin, TestViewsBase): +class TestSheetVersionDeleteBatchView(SampleSheetModelMixin, ViewTestBase): """Tests for the sample sheet version batch delete view""" def setUp(self): @@ -1323,7 +1334,7 @@ def test_delete(self): self.assertEqual(ISATab.objects.count(), 0) -class TestProjectSearchResultsView(TestViewsBase): +class TestProjectSearchResultsView(ViewTestBase): """Tests for ProjectSearchResultsView view with sample sheet input""" def _get_items(self, response): @@ -1431,7 +1442,7 @@ def test_search_multi(self): self.assertEqual(len(items), 2) -class TestSheetVersionCompareView(TestViewsBase): +class TestSheetVersionCompareView(ViewTestBase): """Tests for the SheetVersionCompareView""" def setUp(self): @@ -1490,7 +1501,7 @@ def test_render_no_permission(self): self.assertRedirects(response, reverse('home')) -class TestSheetVersionCompareFileView(TestViewsBase): +class TestSheetVersionCompareFileView(ViewTestBase): """Tests for the SheetVersionCompareFileView""" def setUp(self): @@ -1555,6 +1566,86 @@ def test_render_no_permission(self): self.assertRedirects(response, reverse('home')) +class TestIrodsDataRequestListView(IrodsDataRequestMixin, ViewTestBase): + """Tests for IrodsDataRequestListView""" + + def test_list(self): + """Test GET request for listing delete requests""" + self.assertEqual(IrodsDataRequest.objects.count(), 0) + request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=IRODS_FILE_PATH, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user, + ) + with self.login(self.user): + response = self.client.get( + reverse( + 'samplesheets:irods_requests', + kwargs={'project': self.project.sodar_uuid}, + ), + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context['object_list']), 1) + self.assertEqual(response.context['object_list'][0], request) + + def test_list_as_contributor_by_superuser(self): + """Test GET as contibutor with request created by superuser""" + self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=IRODS_FILE_PATH, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user, + ) + with self.login(self.user_contributor): + response = self.client.get( + reverse( + 'samplesheets:irods_requests', + kwargs={'project': self.project.sodar_uuid}, + ), + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context['object_list']), 0) + + def test_list_as_contributor2_by_contributor(self): + """Test GET as contributor2 with request created by contributor""" + user_contributor2 = self.make_user('user_contributor2') + self.make_assignment( + self.project, user_contributor2, self.role_contributor + ) + self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=IRODS_FILE_PATH, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user_contributor, + ) + with self.login(user_contributor2): + response = self.client.get( + reverse( + 'samplesheets:irods_requests', + kwargs={'project': self.project.sodar_uuid}, + ), + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context['object_list']), 0) + + def test_list_empty(self): + """Test GET request for empty list of delete requests""" + self.assertEqual(IrodsDataRequest.objects.count(), 0) + with self.login(self.user): + response = self.client.get( + reverse( + 'samplesheets:irods_requests', + kwargs={'project': self.project.sodar_uuid}, + ), + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context['object_list']), 0) + + class TestSheetRemoteSyncBase( ProjectMixin, RoleMixin, diff --git a/samplesheets/tests/test_views_ajax.py b/samplesheets/tests/test_views_ajax.py index 5f132238..8f55e32e 100644 --- a/samplesheets/tests/test_views_ajax.py +++ b/samplesheets/tests/test_views_ajax.py @@ -44,7 +44,7 @@ CONFIG_STUDY_UUID, ) from samplesheets.tests.test_views import ( - TestViewsBase, + ViewTestBase, SHEET_DIR_SPECIAL, SHEET_PATH, SHEET_PATH_SMALL2, @@ -169,7 +169,7 @@ def update_assay_row_uuids(self, update_sample=True): n_uuid = self.row_uuids[i] -class TestSheetContextAjaxView(TestViewsBase): +class TestSheetContextAjaxView(ViewTestBase): """Tests for SheetContextAjaxView""" # TODO: Test with realistic ISA-Tab examples using BIH configs (see #434) @@ -452,7 +452,7 @@ def test_inherited_owner(self): self.assertEqual(response_data['perms']['edit_config'], True) -class TestStudyTablesAjaxView(IrodsAccessTicketMixin, TestViewsBase): +class TestStudyTablesAjaxView(IrodsAccessTicketMixin, ViewTestBase): """Tests for StudyTablesAjaxView""" # TODO: Test with realistic ISA-Tab examples using BIH configs (see #434) @@ -586,7 +586,7 @@ def test_get_study_cache_existing(self): self.assertEqual(JSONCacheItem.objects.count(), 1) -class TestStudyLinksAjaxView(TestViewsBase): +class TestStudyLinksAjaxView(ViewTestBase): """Tests for StudyLinksAjaxView""" # TODO: Test with realistic ISA-Tab examples using BIH configs (see #434) @@ -610,7 +610,7 @@ def test_get(self): self.assertEqual(response.status_code, 404) # No plugin for ISA-Tab -class TestSheetWarningsAjaxView(TestViewsBase): +class TestSheetWarningsAjaxView(ViewTestBase): """Tests for SheetWarningsAjaxView""" # TODO: Test with realistic ISA-Tab examples using BIH configs (see #434) @@ -635,7 +635,7 @@ def test_get(self): ) -class TestSheetCellEditAjaxView(TestViewsBase): +class TestSheetCellEditAjaxView(ViewTestBase): """Tests for SheetCellEditAjaxView""" def setUp(self): @@ -1246,7 +1246,7 @@ def test_edit_study_cache(self): self.assertEqual(JSONCacheItem.objects.count(), 1) -class TestSheetCellEditAjaxViewSpecial(TestViewsBase): +class TestSheetCellEditAjaxViewSpecial(ViewTestBase): """Tests for SheetCellEditAjaxView with special columns""" def setUp(self): @@ -1292,7 +1292,7 @@ def test_edit_extract_label_string(self): self.assertEqual(obj.extract_label, label) -class TestSheetRowInsertAjaxView(RowEditMixin, SheetConfigMixin, TestViewsBase): +class TestSheetRowInsertAjaxView(RowEditMixin, SheetConfigMixin, ViewTestBase): """Tests for SheetRowInsertAjaxView""" def setUp(self): @@ -1505,7 +1505,7 @@ def test_insert_study_cache(self): self.assertEqual(JSONCacheItem.objects.count(), 1) -class TestSheetRowDeleteAjaxView(RowEditMixin, SheetConfigMixin, TestViewsBase): +class TestSheetRowDeleteAjaxView(RowEditMixin, SheetConfigMixin, ViewTestBase): """Tests for SheetRowDeleteAjaxView""" def setUp(self): @@ -1631,7 +1631,7 @@ def test_delete_assay_row_study_cache(self): self.assertEqual(JSONCacheItem.objects.count(), 1) -class TestSheetVersionSaveAjaxView(TestViewsBase): +class TestSheetVersionSaveAjaxView(ViewTestBase): """Tests for SheetVersionSaveAjaxView""" def setUp(self): @@ -1657,7 +1657,7 @@ def test_post(self): self.assertEqual(new_version.description, VERSION_DESC) -class TestSheetEditFinishAjaxView(TestViewsBase): +class TestSheetEditFinishAjaxView(ViewTestBase): """Tests for SheetEditFinishAjaxView""" def setUp(self): @@ -1711,7 +1711,7 @@ def test_post_no_updates(self): self.assertEqual(ISATab.objects.count(), 1) -class TestSheetEditConfigAjaxView(SheetConfigMixin, TestViewsBase): +class TestSheetEditConfigAjaxView(SheetConfigMixin, ViewTestBase): """Tests for SheetEditConfigAjaxView""" # TODO: Test with assay updates (needs a better test ISA-Tab) @@ -1948,7 +1948,7 @@ def test_update_study_cache(self): self.assertEqual(JSONCacheItem.objects.count(), 1) -class TestStudyDisplayConfigAjaxView(SheetConfigMixin, TestViewsBase): +class TestStudyDisplayConfigAjaxView(SheetConfigMixin, ViewTestBase): """Tests for StudyDisplayConfigAjaxView""" def setUp(self): @@ -2070,7 +2070,7 @@ def test_post_no_change(self): ) -class TestSheetVersionCompareAjaxView(SheetImportMixin, TestViewsBase): +class TestSheetVersionCompareAjaxView(SheetImportMixin, ViewTestBase): """Tests for SheetVersionCompareAjaxView""" def setUp(self): diff --git a/samplesheets/tests/test_views_ajax_taskflow.py b/samplesheets/tests/test_views_ajax_taskflow.py index 793fac16..940b741d 100644 --- a/samplesheets/tests/test_views_ajax_taskflow.py +++ b/samplesheets/tests/test_views_ajax_taskflow.py @@ -12,15 +12,23 @@ # Taskflowbackend dependency from taskflowbackend.tests.base import TaskflowViewTestBase -from samplesheets.models import GenericMaterial, IrodsDataRequest +from samplesheets.models import ( + GenericMaterial, + IrodsDataRequest, + IRODS_REQUEST_ACTION_DELETE, + IRODS_REQUEST_STATUS_ACTIVE, +) from samplesheets.rendering import SampleSheetTableBuilder from samplesheets.tests.test_io import SampleSheetIOMixin, SHEET_DIR +from samplesheets.tests.test_models import IrodsDataRequestMixin from samplesheets.tests.test_views_taskflow import ( - TestIrodsRequestViewsBase, + IrodsDataRequestViewTestBase, SampleSheetTaskflowMixin, - TEST_FILE_NAME2, + IRODS_FILE_NAME, + IRODS_FILE_NAME2, + SHEET_PATH, ) -from samplesheets.views import IRODS_REQ_CREATE_ALERT as CREATE_ALERT +from samplesheets.views import IRODS_REQUEST_EVENT_CREATE as CREATE_ALERT from samplesheets.views_ajax import ALERT_LIB_FILES_EXIST @@ -218,8 +226,8 @@ def test_update_library_field(self): ) -class TestIrodsRequestCreateAjaxView(TestIrodsRequestViewsBase): - """Tests for IrodsRequestCreateAjaxView""" +class TestIrodsDataRequestCreateAjaxView(IrodsDataRequestViewTestBase): + """Tests for IrodsDataRequestCreateAjaxView""" def test_create_request(self): """Test creating a delete request on a data object""" @@ -303,6 +311,10 @@ def test_create_exists_as_admin_by_contributor(self): def test_create_exists_as_contributor_by_contributor2(self): """Test creating as contributor if request by contributor2 exists""" + user_contributor2 = self.make_user('user_contributor2') + self.make_assignment_taskflow( + self.project, user_contributor2, self.role_contributor + ) with self.login(self.user_contrib): self.client.post( reverse( @@ -314,7 +326,7 @@ def test_create_exists_as_contributor_by_contributor2(self): self.assertEqual(IrodsDataRequest.objects.count(), 1) - with self.login(self.user_contrib2): + with self.login(user_contributor2): response = self.client.post( reverse( 'samplesheets:ajax_irods_request_create', @@ -330,8 +342,8 @@ def test_create_exists_as_contributor_by_contributor2(self): def test_create_multiple(self): """Test creating multiple delete requests""" - path2 = os.path.join(self.assay_path, TEST_FILE_NAME2) - path2_md5 = os.path.join(self.assay_path, TEST_FILE_NAME2 + '.md5') + path2 = os.path.join(self.assay_path, IRODS_FILE_NAME2) + path2_md5 = os.path.join(self.assay_path, IRODS_FILE_NAME2 + '.md5') self.irods.data_objects.create(path2) self.irods.data_objects.create(path2_md5) @@ -362,8 +374,8 @@ def test_create_multiple(self): self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) -class TestIrodsRequestDeleteAjaxView(TestIrodsRequestViewsBase): - """Tests for IrodsRequestDeleteAjaxView""" +class TestIrodsDataRequestDeleteAjaxView(IrodsDataRequestViewTestBase): + """Tests for IrodsDataRequestDeleteAjaxView""" def test_delete_request(self): """Test GET request for deleting an existing delete request""" @@ -428,6 +440,10 @@ def test_delete_request_as_admin_by_contributor(self): def test_delete_request_as_contributor_by_contributor2(self): """Test GET request for deleting an existing delete request""" + user_contributor2 = self.make_user('user_contributor2') + self.make_assignment_taskflow( + self.project, user_contributor2, self.role_contributor + ) with self.login(self.user_contrib): self.client.post( reverse( @@ -440,7 +456,7 @@ def test_delete_request_as_contributor_by_contributor2(self): self.assertEqual(IrodsDataRequest.objects.count(), 1) # Delete request - with self.login(self.user_contrib2): + with self.login(user_contributor2): response = self.client.post( reverse( 'samplesheets:ajax_irods_request_delete', @@ -473,8 +489,8 @@ def test_delete_no_request(self): def test_delete_one_of_multiple(self): """Test deleting one of multiple requests""" - path2 = os.path.join(self.assay_path, TEST_FILE_NAME2) - path2_md5 = os.path.join(self.assay_path, TEST_FILE_NAME2 + '.md5') + path2 = os.path.join(self.assay_path, IRODS_FILE_NAME2) + path2_md5 = os.path.join(self.assay_path, IRODS_FILE_NAME2 + '.md5') self.irods.data_objects.create(path2) self.irods.data_objects.create(path2_md5) @@ -515,23 +531,33 @@ def test_delete_one_of_multiple(self): self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) -class TestIrodsObjectListAjaxView(TestIrodsRequestViewsBase): +class TestIrodsObjectListAjaxView( + SampleSheetIOMixin, + SampleSheetTaskflowMixin, + IrodsDataRequestMixin, + TaskflowViewTestBase, +): """Tests for IrodsObjectListAjaxView""" + def setUp(self): + super().setUp() + # Make project with owner in Taskflow and Django + self.project, self.owner_as = self.make_project_taskflow( + title='TestProject', + type=PROJECT_TYPE_PROJECT, + parent=self.category, + owner=self.user, + description='description', + ) + # Import investigation + self.investigation = self.import_isa_from_file(SHEET_PATH, self.project) + self.study = self.investigation.studies.first() + self.assay = self.study.assays.first() + self.assay_path = self.irods_backend.get_path(self.assay) + self.make_irods_colls(self.investigation) + def test_get_empty_coll(self): """Test GET request for listing an empty collection in iRODS""" - self.irods.data_objects.unlink(self.obj_path, force=True) - self.irods.data_objects.unlink(self.obj_path_md5, force=True) - self.irods.data_objects.unlink(self.obj_path2, force=True) - self.irods.data_objects.unlink(self.obj_path2_md5, force=True) - self.assertEqual(self.irods.data_objects.exists(self.obj_path), False) - self.assertEqual( - self.irods.data_objects.exists(self.obj_path_md5), False - ) - self.assertEqual(self.irods.data_objects.exists(self.obj_path2), False) - self.assertEqual( - self.irods.data_objects.exists(self.obj_path2_md5), False - ) with self.login(self.user): response = self.client.get( reverse( @@ -545,6 +571,8 @@ def test_get_empty_coll(self): def test_get_coll_obj(self): """Test GET request for listing a collection with a data object""" + obj_path = os.path.join(self.assay_path, IRODS_FILE_NAME) + file_obj = self.irods.data_objects.create(obj_path) with self.login(self.user): response = self.client.get( reverse( @@ -554,12 +582,12 @@ def test_get_coll_obj(self): data={'path': self.assay_path}, ) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.data['irods_data']), 2) + self.assertEqual(len(response.data['irods_data']), 1) list_obj = response.data['irods_data'][0] self.assertNotIn('md5_file', list_obj) - self.assertEqual(self.file_obj.name, list_obj['name']) - self.assertEqual(self.file_obj.path, list_obj['path']) - self.assertEqual(self.file_obj.size, 0) + self.assertEqual(file_obj.name, list_obj['name']) + self.assertEqual(file_obj.path, list_obj['path']) + self.assertEqual(file_obj.size, 0) def test_get_invalid_path(self): """Test GET request with invalid path""" @@ -604,11 +632,11 @@ def test_get_coll_not_in_project(self): def test_get_no_access(self): """Test GET request for listing with no acces for the iRODS folder""" - new_user = self.make_user('new_user') + user_new = self.make_user('user_new') self.make_assignment( - self.project, new_user, self.role_contributor + self.project, user_new, self.role_contributor ) # No taskflow - with self.login(new_user): + with self.login(user_new): response = self.client.get( reverse( 'samplesheets:ajax_irods_objects', @@ -620,18 +648,22 @@ def test_get_no_access(self): def test_get_coll_obj_delete_request(self): """Test listing collection with data object and delete request""" - # Create request - with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:ajax_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - data={'path': self.obj_path}, - ) - self.assertEqual(IrodsDataRequest.objects.count(), 1) + user_contributor = self.make_user('user_contributor') + self.make_assignment_taskflow( + self.project, user_contributor, self.role_contributor + ) + obj_path = os.path.join(self.assay_path, IRODS_FILE_NAME) + self.irods.data_objects.create(obj_path) + self.request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=obj_path, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=user_contributor, + description='', + ) - with self.login(self.user_contrib): + with self.login(user_contributor): response = self.client.get( reverse( 'samplesheets:ajax_irods_objects', @@ -641,11 +673,10 @@ def test_get_coll_obj_delete_request(self): ) self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()['irods_data'][0]['name'], 'test1') - self.assertEqual( - response.json()['irods_data'][0]['path'], self.obj_path - ) + response_data = response.json()['irods_data'] + self.assertEqual(response_data[0]['name'], IRODS_FILE_NAME) + self.assertEqual(response_data[0]['path'], obj_path) self.assertEqual( - response.json()['irods_data'][0]['irods_request_status'], - 'ACTIVE', + response_data[0]['irods_request_status'], + IRODS_REQUEST_STATUS_ACTIVE, ) diff --git a/samplesheets/tests/test_views_api.py b/samplesheets/tests/test_views_api.py index a80be40c..f1bca3a3 100644 --- a/samplesheets/tests/test_views_api.py +++ b/samplesheets/tests/test_views_api.py @@ -1,6 +1,7 @@ """Tests for REST API views in the samplesheets app""" import json +import os from django.test import override_settings from django.urls import reverse @@ -15,12 +16,25 @@ # Sodarcache dependency from sodarcache.models import JSONCacheItem +# Timeline dependency +from timeline.models import ProjectEvent + # Landingzones dependency from landingzones.models import LandingZone from landingzones.tests.test_models import LandingZoneMixin from samplesheets.io import SampleSheetIO -from samplesheets.models import Investigation, Assay, GenericMaterial, ISATab +from samplesheets.models import ( + Investigation, + Assay, + GenericMaterial, + ISATab, + IrodsDataRequest, + IRODS_REQUEST_ACTION_DELETE, + IRODS_REQUEST_STATUS_ACCEPTED, + IRODS_REQUEST_STATUS_ACTIVE, + IRODS_REQUEST_STATUS_FAILED, +) from samplesheets.rendering import ( SampleSheetTableBuilder, STUDY_TABLE_CACHE_ITEM, @@ -31,9 +45,10 @@ SHEET_DIR, SHEET_DIR_SPECIAL, ) +from samplesheets.tests.test_models import IrodsDataRequestMixin from samplesheets.tests.test_sheet_config import SheetConfigMixin from samplesheets.tests.test_views import ( - TestViewsBase, + ViewTestBase, REMOTE_SITE_NAME, REMOTE_SITE_URL, REMOTE_SITE_DESC, @@ -58,6 +73,7 @@ SHEET_NAME_ALT = 'i_small.zip' SHEET_PATH_ALT = SHEET_DIR + 'i_small2_alt.zip' SHEET_PATH_NO_PLUGIN_ASSAY = SHEET_DIR_SPECIAL + 'i_small_assay_no_plugin.zip' +IRODS_FILE_NAME = 'test1.txt' IRODS_FILE_MD5 = '0b26e313ed4a7ca6904b0e9369e5b957' @@ -609,6 +625,195 @@ def test_get_json(self): self.assertEqual(response.data, expected) +class TestIrodsDataRequestRetrieveAPIView( + IrodsDataRequestMixin, TestSampleSheetAPIBase +): + """Tests for IrodsDataRequestRetrieveAPIView""" + + def setUp(self): + super().setUp() + # Add contributor user + self.user_contrib = self.make_user('user_contributor') + self.make_assignment( + self.project, self.user_contrib, self.role_contributor + ) + # Import investigation + self.investigation = self.import_isa_from_file(SHEET_PATH, self.project) + self.study = self.investigation.studies.first() + self.assay = self.study.assays.first() + # Set up iRODS backend and paths + self.irods_backend = get_backend_api('omics_irods') + self.assay_path = self.irods_backend.get_path(self.assay) + # Make request + self.request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=os.path.join(self.assay_path, IRODS_FILE_NAME), + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user_contrib, + ) + self.url = reverse( + 'samplesheets:api_irods_request_retrieve', + kwargs={'irodsdatarequest': self.request.sodar_uuid}, + ) + + def test_get(self): + """Test retrieving iRODS data request object""" + response = self.request_knox(self.url) + self.assertEqual(response.status_code, 200) + response_data = json.loads(response.content) + expected = { + 'project': str(self.project.sodar_uuid), + 'action': IRODS_REQUEST_ACTION_DELETE, + 'path': self.request.path, + 'target_path': '', + 'user': self.get_serialized_user(self.user_contrib), + 'status': IRODS_REQUEST_STATUS_ACTIVE, + 'status_info': '', + 'description': self.request.description, + 'date_created': self.get_drf_datetime(self.request.date_created), + 'sodar_uuid': str(self.request.sodar_uuid), + } + self.assertEqual(response_data, expected) + + +class TestIrodsDataRequestListAPIView( + IrodsDataRequestMixin, TestSampleSheetAPIBase +): + """Tests for IrodsDataRequestListAPIView""" + + def setUp(self): + super().setUp() + # Add contributor user + self.user_contrib = self.make_user('user_contributor') + self.make_assignment( + self.project, self.user_contrib, self.role_contributor + ) + # Import investigation + self.investigation = self.import_isa_from_file(SHEET_PATH, self.project) + self.study = self.investigation.studies.first() + self.assay = self.study.assays.first() + # Set up iRODS backend and paths + self.irods_backend = get_backend_api('omics_irods') + self.assay_path = self.irods_backend.get_path(self.assay) + # Make request + self.request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=os.path.join(self.assay_path, IRODS_FILE_NAME), + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user_contrib, + ) + self.url = reverse( + 'samplesheets:api_irods_request_list', + kwargs={'project': self.project.sodar_uuid}, + ) + + def test_get(self): + """Test retrieving iRODS data request list""" + response = self.request_knox(self.url) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 1) + response_data = json.loads(response.content) + expected = { + 'project': str(self.project.sodar_uuid), + 'action': IRODS_REQUEST_ACTION_DELETE, + 'path': self.request.path, + 'target_path': '', + 'user': self.get_serialized_user(self.user_contrib), + 'status': IRODS_REQUEST_STATUS_ACTIVE, + 'status_info': '', + 'description': self.request.description, + 'date_created': self.get_drf_datetime(self.request.date_created), + 'sodar_uuid': str(self.request.sodar_uuid), + } + self.assertEqual(response_data[0], expected) + + def test_get_failed_as_superuser(self): + """Test retrieving list as superuser with failed request""" + self.request.status = IRODS_REQUEST_STATUS_FAILED + self.request.save() + response = self.request_knox(self.url) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 1) + + def test_get_accepted_as_superuser(self): + """Test retrieving list as superuser with accepted request""" + self.request.status = IRODS_REQUEST_STATUS_ACCEPTED + self.request.save() + response = self.request_knox(self.url) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 0) + + def test_get_accepted_as_owner(self): + """Test retrieving list as owner with accepted request""" + self.request.status = IRODS_REQUEST_STATUS_ACCEPTED + self.request.save() + response = self.request_knox( + self.url, token=self.get_token(self.user_owner) + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 0) + + def test_get_accepted_as_request_creator(self): + """Test retrieving list as request creator with accepted request""" + self.request.status = IRODS_REQUEST_STATUS_ACCEPTED + self.request.save() + response = self.request_knox( + self.url, token=self.get_token(self.user_contrib) + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 1) + + +class TestIrodsDataRequestDestroyAPIView( + IrodsDataRequestMixin, TestSampleSheetAPIBase +): + """Tests for IrodsDataRequestDestroyAPIView""" + + def _assert_tl_count(self, count): + """Assert timeline ProjectEvent count""" + self.assertEqual( + ProjectEvent.objects.filter( + event_name='irods_request_delete' + ).count(), + count, + ) + + def setUp(self): + super().setUp() + # Import investigation + self.investigation = self.import_isa_from_file(SHEET_PATH, self.project) + self.study = self.investigation.studies.first() + self.assay = self.study.assays.first() + # Set up iRODS backend and paths + self.irods_backend = get_backend_api('omics_irods') + self.assay_path = self.irods_backend.get_path(self.assay) + self.obj_path = os.path.join(self.assay_path, IRODS_FILE_NAME) + + def test_delete(self): + """Test deleting a request""" + self._assert_tl_count(0) + obj = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=self.obj_path, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user, + ) + self.assertEqual(IrodsDataRequest.objects.count(), 1) + with self.login(self.user): + response = self.client.delete( + reverse( + 'samplesheets:api_irods_request_delete', + kwargs={'irodsdatarequest': obj.sodar_uuid}, + ) + ) + self.assertEqual(response.status_code, 204) + self.assertEqual(IrodsDataRequest.objects.count(), 0) + self._assert_tl_count(1) + + class TestSampleDataFileExistsAPIView(TestSampleSheetAPIBase): """Tests for SampleDataFileExistsAPIView""" @@ -622,7 +827,7 @@ def test_get_no_irods(self): # NOTE: Not yet standardized api, use old base class to test class TestRemoteSheetGetAPIView( - RemoteSiteMixin, RemoteProjectMixin, TestViewsBase + RemoteSiteMixin, RemoteProjectMixin, ViewTestBase ): """Tests for RemoteSheetGetAPIView""" diff --git a/samplesheets/tests/test_views_api_taskflow.py b/samplesheets/tests/test_views_api_taskflow.py index 996e7fe9..3666d802 100644 --- a/samplesheets/tests/test_views_api_taskflow.py +++ b/samplesheets/tests/test_views_api_taskflow.py @@ -7,6 +7,7 @@ from irods.keywords import REG_CHKSUM_KW +from django.forms.models import model_to_dict from django.test import override_settings from django.urls import reverse @@ -14,23 +15,38 @@ from projectroles.models import SODAR_CONSTANTS from projectroles.plugins import get_backend_api +# Timeline dependency +from timeline.models import ProjectEvent + # Taskflowbackend dependency from taskflowbackend.tests.base import ( TaskflowAPIViewTestBase, ) -from samplesheets.models import IrodsDataRequest +from samplesheets.models import ( + IrodsDataRequest, + IRODS_REQUEST_STATUS_ACCEPTED, + IRODS_REQUEST_STATUS_ACTIVE, + IRODS_REQUEST_STATUS_FAILED, + IRODS_REQUEST_STATUS_REJECTED, + IRODS_REQUEST_ACTION_DELETE, +) from samplesheets.views import ( - IRODS_REQ_CREATE_ALERT as CREATE_ALERT, - IRODS_REQ_ACCEPT_ALERT as ACCEPT_ALERT, - IRODS_REQ_REJECT_ALERT as REJECT_ALERT, + IRODS_REQUEST_EVENT_CREATE as CREATE_ALERT, + IRODS_REQUEST_EVENT_ACCEPT as ACCEPT_ALERT, + IRODS_REQUEST_EVENT_REJECT as REJECT_ALERT, ) from samplesheets.views_api import IRODS_QUERY_ERROR_MSG from samplesheets.tests.test_io import SampleSheetIOMixin, SHEET_DIR +from samplesheets.tests.test_models import ( + IrodsDataRequestMixin, + IRODS_REQUEST_DESC, +) from samplesheets.tests.test_views_taskflow import ( SampleSheetTaskflowMixin, - TEST_FILE_NAME, + IRODS_FILE_NAME, + IRODS_FILE_NAME2, INVALID_REDIS_URL, ) @@ -43,8 +59,11 @@ SHEET_PATH_EDITED = SHEET_DIR + 'i_small2_edited.zip' SHEET_PATH_ALT = SHEET_DIR + 'i_small2_alt.zip' IRODS_FILE_PATH = os.path.dirname(__file__) + '/irods/test1.txt' -IRODS_FILE_NAME = 'test1.txt' IRODS_FILE_MD5 = '0b26e313ed4a7ca6904b0e9369e5b957' +IRODS_REQUEST_DESC_UPDATED = 'updated' + + +# Base Classes and Mixins ------------------------------------------------------ class TestSampleSheetAPITaskflowBase( @@ -68,11 +87,12 @@ def setUp(self): self.assay = self.study.assays.first() -class TestIrodsRequestAPIViewBase( +class TestIrodsDataRequestAPIViewBase( SampleSheetIOMixin, SampleSheetTaskflowMixin, TaskflowAPIViewTestBase ): """Base samplesheets API view test class for iRODS delete requests""" + # TODO: Retrieve this from a common base/helper class instead of redef def assert_alert_count(self, alert_name, user, count, project=None): """ Assert expected app alert count. If project is not specified, default to @@ -104,62 +124,52 @@ def setUp(self): owner=self.user, description='description', ) - - # Import investigation - self.investigation = self.import_isa_from_file(SHEET_PATH, self.project) - self.study = self.investigation.studies.first() - self.assay = self.study.assays.first() - - # Set up iRODS data - self.make_irods_colls(self.investigation) - self.assay_path = self.irods_backend.get_path(self.assay) - self.path = os.path.join(self.assay_path, TEST_FILE_NAME) - self.path_md5 = os.path.join(self.assay_path, f'{TEST_FILE_NAME}.md5') - # Create objects - self.file_obj = self.irods.data_objects.create(self.path) - self.md5_obj = self.irods.data_objects.create(self.path_md5) - # Init users (owner = user_cat, superuser = user) self.user_delegate = self.make_user('user_delegate') self.user_contrib = self.make_user('user_contrib') - self.user_contrib2 = self.make_user('user_contrib2') self.user_guest = self.make_user('user_guest') - self.make_assignment_taskflow( self.project, self.user_delegate, self.role_delegate ) self.make_assignment_taskflow( self.project, self.user_contrib, self.role_contributor ) - self.make_assignment_taskflow( - self.project, self.user_contrib2, self.role_contributor - ) self.make_assignment_taskflow( self.project, self.user_guest, self.role_guest ) - # Get appalerts API and model + # Import investigation + self.investigation = self.import_isa_from_file(SHEET_PATH, self.project) + self.study = self.investigation.studies.first() + self.assay = self.study.assays.first() + # Set up iRODS data + self.make_irods_colls(self.investigation) + self.assay_path = self.irods_backend.get_path(self.assay) + self.obj_path = os.path.join(self.assay_path, IRODS_FILE_NAME) + self.file_obj = self.irods.data_objects.create(self.obj_path) + + # Setup for tests self.app_alerts = get_backend_api('appalerts_backend') self.app_alert_model = self.app_alerts.get_model() - - # Set create URL self.url = reverse( 'samplesheets:api_irods_request_create', kwargs={'project': self.project.sodar_uuid}, ) - # Set default POST data - self.post_data = {'path': self.path, 'description': 'bla'} + self.post_data = { + 'path': self.obj_path, + 'description': IRODS_REQUEST_DESC, + } + self.token_contrib = self.get_token(self.user_contrib) - def tearDown(self): - self.irods.collections.get('/sodarZone/projects').remove(force=True) - super().tearDown() + +# Test Cases ------------------------------------------------------------------- class TestInvestigationRetrieveAPIView(TestSampleSheetAPITaskflowBase): """Tests for InvestigationRetrieveAPIView""" def test_get(self): - """Test get() in InvestigationRetrieveAPIView""" + """Test InvestigationRetrieveAPIView GET""" self.investigation.irods_status = True self.investigation.save() @@ -213,7 +223,7 @@ class TestIrodsCollsCreateAPIView(TestSampleSheetAPITaskflowBase): """Tests for IrodsCollsCreateAPIView""" def test_post(self): - """Test post() in IrodsCollsCreateAPIView""" + """Test IrodsCollsCreateAPIView POST""" self.assertEqual(self.investigation.irods_status, False) url = reverse( 'samplesheets:api_irods_colls_create', @@ -228,7 +238,7 @@ def test_post(self): self.assert_irods_coll(self.assay) def test_post_created(self): - """Test post() with already created collections (should fail)""" + """Test POST with already created collections (should fail)""" # Set up iRODS collections self.make_irods_colls(self.investigation) self.assertEqual(self.investigation.irods_status, True) @@ -240,631 +250,391 @@ def test_post_created(self): self.assertEqual(response.status_code, 400) -class TestIrodsRequestCreateAPIView(TestIrodsRequestAPIViewBase): - """Tests for IrodsRequestCreateAPIView""" +# NOTE: For TestIrodsDataRequestRetrieveAPIView, see test_views_api +# NOTE: For TestIrodsDataRequestListAPIView, see test_views_api + + +class TestIrodsDataRequestCreateAPIView(TestIrodsDataRequestAPIViewBase): + """Tests for IrodsDataRequestCreateAPIView""" def test_create(self): - """Test post() in IrodsRequestCreateAPIView""" + """Test IrodsDataRequestCreateAPIView POST""" self.assertEqual(IrodsDataRequest.objects.count(), 0) self.assert_alert_count(CREATE_ALERT, self.user, 0) self.assert_alert_count(CREATE_ALERT, self.user_delegate, 0) - - with self.login(self.user_contrib): - response = self.client.post(self.url, self.post_data) - - self.assertEqual(response.status_code, 200) + response = self.request_knox( + self.url, 'POST', data=self.post_data, token=self.token_contrib + ) + self.assertEqual(response.status_code, 201) obj = IrodsDataRequest.objects.first() self.assertEqual(IrodsDataRequest.objects.count(), 1) - self.assertEqual(obj.path, self.path) - self.assertEqual(obj.description, 'bla') + expected = { + 'id': obj.pk, + 'project': self.project.pk, + 'action': IRODS_REQUEST_ACTION_DELETE, + 'target_path': None, + 'path': self.obj_path, + 'user': self.user_contrib.pk, + 'status': IRODS_REQUEST_STATUS_ACTIVE, + 'status_info': '', + 'description': IRODS_REQUEST_DESC, + 'sodar_uuid': obj.sodar_uuid, + } + self.assertEqual(model_to_dict(obj), expected) self.assert_alert_count(CREATE_ALERT, self.user, 1) self.assert_alert_count(CREATE_ALERT, self.user_delegate, 1) self.assert_alert_count(CREATE_ALERT, self.user_contrib, 0) self.assert_alert_count(CREATE_ALERT, self.user_guest, 0) - def test_create_trailing_slash(self): - """Test creating a request with a trailing slash in path""" + def test_create_no_description(self): + """Test POST without description""" self.assertEqual(IrodsDataRequest.objects.count(), 0) - post_data = {'path': self.path + '/', 'description': 'bla'} - - with self.login(self.user_contrib): - response = self.client.post(self.url, post_data) + self.post_data['description'] = '' + response = self.request_knox( + self.url, 'POST', data=self.post_data, token=self.token_contrib + ) + self.assertEqual(response.status_code, 201) + obj = IrodsDataRequest.objects.first() + self.assertEqual(IrodsDataRequest.objects.count(), 1) + self.assertEqual(obj.description, '') - self.assertEqual(response.status_code, 200) + def test_create_trailing_slash(self): + """Test POST with trailing slash in path""" + self.assertEqual(IrodsDataRequest.objects.count(), 0) + self.post_data['path'] += '/' + response = self.request_knox( + self.url, 'POST', data=self.post_data, token=self.token_contrib + ) + self.assertEqual(response.status_code, 201) obj = IrodsDataRequest.objects.first() self.assertEqual(IrodsDataRequest.objects.count(), 1) - self.assertEqual(obj.path, self.path + '/') - self.assertEqual(obj.description, 'bla') + self.assertEqual(obj.path, self.obj_path + '/') + self.assertEqual(obj.description, IRODS_REQUEST_DESC) def test_create_invalid_data(self): - """Test creating a request with invalid data""" + """Test POST with invalid data""" self.assertEqual(IrodsDataRequest.objects.count(), 0) self.assert_alert_count(CREATE_ALERT, self.user, 0) self.assert_alert_count(CREATE_ALERT, self.user_delegate, 0) - post_data = {'path': '/doesnt/exist', 'description': 'bla'} - - with self.login(self.user_contrib): - response = self.client.post(self.url, post_data) - + self.post_data['path'] = '/sodarZone/does/not/exist' + response = self.request_knox( + self.url, 'POST', data=self.post_data, token=self.token_contrib + ) self.assertEqual(response.status_code, 400) self.assertEqual(IrodsDataRequest.objects.count(), 0) def test_create_invalid_path_assay_collection(self): - """Test creating a request with assay path (should fail)""" + """Test POST with assay path (should fail)""" self.assertEqual(IrodsDataRequest.objects.count(), 0) - post_data = {'path': self.assay_path, 'description': 'bla'} - - with self.login(self.user_contrib): - response = self.client.post(self.url, post_data) - + self.post_data['path'] = self.assay_path + response = self.request_knox( + self.url, 'POST', data=self.post_data, token=self.token_contrib + ) self.assertEqual(response.status_code, 400) self.assertEqual(IrodsDataRequest.objects.count(), 0) def test_create_multiple(self): - """Test creating multiple requests""" - path2 = os.path.join(self.assay_path, TEST_FILE_NAME + '_2') - path2_md5 = os.path.join(self.assay_path, TEST_FILE_NAME + '_2.md5') + """Test creating multiple requests for same path""" + path2 = os.path.join(self.assay_path, IRODS_FILE_NAME2) self.irods.data_objects.create(path2) - self.irods.data_objects.create(path2_md5) self.assertEqual(IrodsDataRequest.objects.count(), 0) self.assert_alert_count(CREATE_ALERT, self.user, 0) self.assert_alert_count(CREATE_ALERT, self.user_delegate, 0) - - with self.login(self.user_contrib): - response = self.client.post(self.url, self.post_data) - self.assertEqual(response.status_code, 200) - self.post_data['path'] = path2 - response = self.client.post(self.url, self.post_data) - self.assertEqual(response.status_code, 200) - + response = self.request_knox( + self.url, 'POST', data=self.post_data, token=self.token_contrib + ) + self.assertEqual(response.status_code, 201) + self.post_data['path'] = path2 + response = self.request_knox( + self.url, 'POST', data=self.post_data, token=self.token_contrib + ) + self.assertEqual(response.status_code, 201) self.assertEqual(IrodsDataRequest.objects.count(), 2) self.assert_alert_count(CREATE_ALERT, self.user, 1) self.assert_alert_count(CREATE_ALERT, self.user_delegate, 1) -class TestIrodsRequestUpdateAPIView(TestIrodsRequestAPIViewBase): - """Tests for IrodsRequestUpdateAPIView""" - - def test_update(self): - """Test updating a request""" - update_data = {'path': self.path, 'description': 'Updated'} - - with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:api_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() - response = self.client.put( - reverse( - 'samplesheets:api_irods_request_update', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ), - update_data, - ) - - self.assertEqual(response.status_code, 200) - obj.refresh_from_db() - self.assertEqual(obj.description, 'Updated') - self.assertEqual(obj.path, self.path) - - def test_update_invalid_data(self): - """Test updating a request with invalid data""" - update_data = {'path': '/doesnt/exist', 'description': 'Updated'} - - with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:api_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() - response = self.client.put( - reverse( - 'samplesheets:api_irods_request_update', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ), - update_data, - ) - - self.assertEqual(response.status_code, 400) - obj.refresh_from_db() - self.assertEqual(obj.description, 'bla') - self.assertEqual(obj.path, self.path) - +class TestIrodsDataRequestUpdateAPIView( + IrodsDataRequestMixin, TestIrodsDataRequestAPIViewBase +): + """Tests for IrodsDataRequestUpdateAPIView""" -class TestIrodsRequestDeleteAPIView(TestIrodsRequestAPIViewBase): - """Tests for IrodsRequestDeleteAPIView""" + def _assert_tl_count(self, count): + """Assert timeline ProjectEvent count""" + self.assertEqual( + ProjectEvent.objects.filter( + event_name='irods_request_update' + ).count(), + count, + ) - def test_delete(self): - """Test deleting a request""" - with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:api_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) + def setUp(self): + super().setUp() + self.request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=self.obj_path, + status=IRODS_REQUEST_STATUS_ACTIVE, + description='', + user=self.user_contrib, + ) + self.url = reverse( + 'samplesheets:api_irods_request_update', + kwargs={'irodsdatarequest': self.request.sodar_uuid}, + ) + self.update_data = { + 'path': self.obj_path, + 'description': IRODS_REQUEST_DESC_UPDATED, + } + def test_put(self): + """Test IrodsDataRequestUpdateAPIView PUT""" self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() - - with self.login(self.user_contrib): - response = self.client.delete( - reverse( - 'samplesheets:api_irods_request_delete', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ) - ) - + self._assert_tl_count(0) + response = self.request_knox( + self.url, 'PUT', data=self.update_data, token=self.token_contrib + ) self.assertEqual(response.status_code, 200) - self.assertEqual(IrodsDataRequest.objects.count(), 0) + self.assertEqual(IrodsDataRequest.objects.count(), 1) + self.request.refresh_from_db() + expected = { + 'id': self.request.pk, + 'project': self.project.pk, + 'action': IRODS_REQUEST_ACTION_DELETE, + 'target_path': '', + 'path': self.obj_path, + 'user': self.user_contrib.pk, + 'status': IRODS_REQUEST_STATUS_ACTIVE, + 'status_info': '', + 'description': IRODS_REQUEST_DESC_UPDATED, + 'sodar_uuid': self.request.sodar_uuid, + } + self.assertEqual(model_to_dict(self.request), expected) + self._assert_tl_count(1) + + def test_put_empty_description(self): + """Test PUT with empty description""" + self.update_data['description'] = '' + response = self.request_knox( + self.url, 'PUT', data=self.update_data, token=self.token_contrib + ) + self.assertEqual(response.status_code, 200) + self.request.refresh_from_db() + self.assertEqual(self.request.description, '') + + def test_put_invalid_path(self): + """Test PUT to update request with invalid path""" + self._assert_tl_count(0) + self.update_data['path'] = '/sodarZone/does/not/exist' + response = self.request_knox( + self.url, 'PUT', data=self.update_data, token=self.token_contrib + ) + self.assertEqual(response.status_code, 400) + self.request.refresh_from_db() + self.assertEqual(self.request.description, '') + self.assertEqual(self.request.path, self.obj_path) + self._assert_tl_count(0) + + def test_put_assay_path(self): + """Test PUT to update request with assay path (should fail)""" + self.update_data['path'] = self.irods_backend.get_path(self.assay) + response = self.request_knox( + self.url, 'PUT', data=self.update_data, token=self.token_contrib + ) + self.assertEqual(response.status_code, 400) + self.request.refresh_from_db() + self.assertEqual(self.request.path, self.obj_path) + + def test_patch(self): + """Test PATCH to update request""" + self._assert_tl_count(0) + update_data = {'description': IRODS_REQUEST_DESC_UPDATED} + response = self.request_knox( + self.url, 'PATCH', data=update_data, token=self.token_contrib + ) + self.assertEqual(response.status_code, 200) + self.request.refresh_from_db() + self.assertEqual(self.request.description, IRODS_REQUEST_DESC_UPDATED) + self.assertEqual(self.request.path, self.obj_path) + self._assert_tl_count(1) - def test_delete_one_of_multiple(self): - """Test deleting one of multiple requests""" - path2 = os.path.join(self.assay_path, TEST_FILE_NAME + '_2') - path2_md5 = os.path.join(self.assay_path, TEST_FILE_NAME + '_2.md5') - self.irods.data_objects.create(path2) - self.irods.data_objects.create(path2_md5) - self.assertEqual(IrodsDataRequest.objects.count(), 0) - with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:api_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - self.post_data['path'] = path2 - self.client.post( - reverse( - 'samplesheets:api_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - self.assertEqual(IrodsDataRequest.objects.count(), 2) - obj = IrodsDataRequest.objects.first() - response = self.client.delete( - reverse( - 'samplesheets:api_irods_request_delete', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ) - ) +# NOTE: For TestIrodsDataRequestDestroyAPIView, see test_views_api - self.assertEqual(response.status_code, 200) - self.assertEqual(IrodsDataRequest.objects.count(), 1) +class TestIrodsDataRequestAcceptAPIView( + IrodsDataRequestMixin, TestIrodsDataRequestAPIViewBase +): + """Tests for IrodsDataRequestAcceptAPIView""" -class TestIrodsRequestAcceptAPIView(TestIrodsRequestAPIViewBase): - """Tests for IrodsRequestAcceptAPIView""" + def setUp(self): + super().setUp() + self.request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=self.obj_path, + status=IRODS_REQUEST_STATUS_ACTIVE, + description=IRODS_REQUEST_DESC, + user=self.user_contrib, + ) + self.url = reverse( + 'samplesheets:api_irods_request_accept', + kwargs={'irodsdatarequest': self.request.sodar_uuid}, + ) def test_accept(self): - """Test accepting a request""" - with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:api_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - + """Test IrodsDataRequestAcceptAPIView POST""" self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() self.assert_alert_count(ACCEPT_ALERT, self.user, 0) self.assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) self.assert_alert_count(ACCEPT_ALERT, self.user_contrib, 0) - - with self.login(self.user): - response = self.client.post( - reverse( - 'samplesheets:api_irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ), - ) - + response = self.request_knox(self.url, 'POST') self.assertEqual(response.status_code, 200) - obj.refresh_from_db() - self.assertEqual(obj.status, 'ACCEPTED') + self.request.refresh_from_db() + self.assertEqual(self.request.status, IRODS_REQUEST_STATUS_ACCEPTED) self.assert_alert_count(ACCEPT_ALERT, self.user, 0) self.assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) self.assert_alert_count(ACCEPT_ALERT, self.user_contrib, 1) - self.assert_irods_obj(self.path, False) + self.assert_irods_obj(self.obj_path, False) def test_accept_no_request(self): - """Test accepting a non-existing request""" - self.assertEqual(IrodsDataRequest.objects.count(), 0) - with self.login(self.user): - response = self.client.post( - reverse( - 'samplesheets:api_irods_request_accept', - kwargs={'irodsdatarequest': self.category.sodar_uuid}, - ), - ) + """Test POST to accept non-existing request""" + url = reverse( + 'samplesheets:api_irods_request_accept', + kwargs={'irodsdatarequest': self.category.sodar_uuid}, + ) + response = self.request_knox(url, 'POST') self.assertEqual(response.status_code, 404) - def test_accept_invalid_data(self): - """Test accepting a request with invalid data""" - with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:api_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() - self.assert_alert_count(ACCEPT_ALERT, self.user, 0) - self.assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) - self.assert_alert_count(ACCEPT_ALERT, self.user_contrib, 0) - - with self.login(self.user): - response = self.client.post( - reverse( - 'samplesheets:api_irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ), - ) - - self.assertEqual(response.status_code, 200) - obj.refresh_from_db() - self.assert_alert_count(ACCEPT_ALERT, self.user, 0) - self.assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) - self.assert_alert_count(ACCEPT_ALERT, self.user_contrib, 1) - self.assert_irods_obj(self.path, False) - def test_accept_delegate(self): - """Test accepting a request as delegate""" - self.assert_irods_obj(self.path) - - with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:api_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() + """Test POST to accept request as delegate""" + self.assert_irods_obj(self.obj_path) self.assert_alert_count(ACCEPT_ALERT, self.user, 0) self.assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) self.assert_alert_count(ACCEPT_ALERT, self.user_contrib, 0) - - with self.login(self.user_delegate): - response = self.client.post( - reverse( - 'samplesheets:api_irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ), - ) - + response = self.request_knox( + self.url, 'POST', token=self.get_token(self.user_delegate) + ) self.assertEqual(response.status_code, 200) - obj.refresh_from_db() - self.assertEqual(obj.status, 'ACCEPTED') - self.assert_irods_obj(self.path, False) + self.request.refresh_from_db() + self.assertEqual(self.request.status, IRODS_REQUEST_STATUS_ACCEPTED) + self.assert_irods_obj(self.obj_path, False) self.assert_alert_count(ACCEPT_ALERT, self.user, 0) self.assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) self.assert_alert_count(ACCEPT_ALERT, self.user_contrib, 1) def test_accept_contributor(self): - """Test accepting a request as contributor""" - self.assert_irods_obj(self.path) - - with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:api_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() + """Test POST to accept request as contributor (should fail)""" + self.assert_irods_obj(self.obj_path) self.assert_alert_count(ACCEPT_ALERT, self.user, 0) self.assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) self.assert_alert_count(ACCEPT_ALERT, self.user_contrib, 0) - - with self.login(self.user_contrib): - response = self.client.post( - reverse( - 'samplesheets:api_irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ), - ) - + response = self.request_knox( + self.url, 'POST', token=self.get_token(self.user_contrib) + ) self.assertEqual(response.status_code, 403) - obj.refresh_from_db() - self.assertEqual(obj.status, 'ACTIVE') - self.assert_irods_obj(self.path, True) + self.request.refresh_from_db() + self.assertEqual(self.request.status, IRODS_REQUEST_STATUS_ACTIVE) + self.assert_irods_obj(self.obj_path) self.assert_alert_count(ACCEPT_ALERT, self.user, 0) self.assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) self.assert_alert_count(ACCEPT_ALERT, self.user_contrib, 0) - def test_accept_one_of_multiple(self): - """Test accepting one of multiple requests""" - path2 = os.path.join(self.assay_path, TEST_FILE_NAME + '_2') - path2_md5 = os.path.join(self.assay_path, TEST_FILE_NAME + '_2.md5') - self.irods.data_objects.create(path2) - self.irods.data_objects.create(path2_md5) - self.assertEqual(IrodsDataRequest.objects.count(), 0) - - with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:api_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - self.post_data['path'] = path2 - self.client.post( - reverse( - 'samplesheets:api_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - - self.assertEqual(IrodsDataRequest.objects.count(), 2) - obj = IrodsDataRequest.objects.first() - - with self.login(self.user): - response = self.client.post( - reverse( - 'samplesheets:api_irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ), - ) - self.assertEqual(response.status_code, 200) - obj.refresh_from_db() - self.assertEqual(obj.status, 'ACCEPTED') - self.assertEqual( - IrodsDataRequest.objects.filter(status='ACTIVE').count(), 1 - ) - @override_settings(REDIS_URL=INVALID_REDIS_URL) def test_accept_lock_failure(self): - """Test accepting a request with project lock failure""" - self.assert_irods_obj(self.path) - - with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:api_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() - - with self.login(self.user): - response = self.client.post( - reverse( - 'samplesheets:api_irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ), - ) - + """Test POST toa ccept request with project lock failure""" + self.assert_irods_obj(self.obj_path) + response = self.request_knox(self.url, 'POST') self.assertEqual(response.status_code, 400) - obj.refresh_from_db() - self.assertEqual(obj.status, 'FAILED') - self.assert_irods_obj(self.path, True) + self.request.refresh_from_db() + self.assertEqual(self.request.status, IRODS_REQUEST_STATUS_FAILED) + self.assert_irods_obj(self.obj_path) self.assert_alert_count(ACCEPT_ALERT, self.user, 0) self.assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) self.assert_alert_count(ACCEPT_ALERT, self.user_contrib, 0) - self.assert_irods_obj(self.path, True) def test_accept_already_accepted(self): - """Test accepting an already accepted request (should fail)""" - with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:api_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() - - with self.login(self.user): - response = self.client.post( - reverse( - 'samplesheets:api_irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ), - ) - + """Test accepting already accepted request (should fail)""" + self.assertEqual(self.request.status, IRODS_REQUEST_STATUS_ACTIVE) + response = self.request_knox(self.url, 'POST') self.assertEqual(response.status_code, 200) - obj.refresh_from_db() - self.assertEqual(obj.status, 'ACCEPTED') - - with self.login(self.user): - response = self.client.post( - reverse( - 'samplesheets:api_irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ), - ) + self.request.refresh_from_db() + self.assertEqual(self.request.status, IRODS_REQUEST_STATUS_ACCEPTED) + response = self.request_knox(self.url, 'POST') self.assertEqual(response.status_code, 400) + self.assertEqual(self.request.status, IRODS_REQUEST_STATUS_ACCEPTED) -class TestIrodsRequestRejectAPIView(TestIrodsRequestAPIViewBase): - """Tests for IrodsRequestRejectAPIView""" +class TestIrodsDataRequestRejectAPIView( + IrodsDataRequestMixin, TestIrodsDataRequestAPIViewBase +): + """Tests for IrodsDataRequestRejectAPIView""" - def test_reject_admin(self): - """Test rejecting a request as admin""" - self.assertEqual(IrodsDataRequest.objects.count(), 0) + def setUp(self): + super().setUp() + self.request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=self.obj_path, + status=IRODS_REQUEST_STATUS_ACTIVE, + description=IRODS_REQUEST_DESC, + user=self.user_contrib, + ) + self.url = reverse( + 'samplesheets:api_irods_request_reject', + kwargs={'irodsdatarequest': self.request.sodar_uuid}, + ) + + def test_reject(self): + """Test IrodsDataRequestRejectAPIView POST""" self.assert_alert_count(REJECT_ALERT, self.user, 0) self.assert_alert_count(REJECT_ALERT, self.user_delegate, 0) self.assert_alert_count(REJECT_ALERT, self.user_contrib, 0) - - with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:api_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() - - with self.login(self.user): - response = self.client.get( - reverse( - 'samplesheets:api_irods_request_reject', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ), - ) - + response = self.request_knox(self.url, 'POST') self.assertEqual(response.status_code, 200) - obj.refresh_from_db() - self.assertEqual(obj.status, 'REJECTED') + self.request.refresh_from_db() + self.assertEqual(self.request.status, IRODS_REQUEST_STATUS_REJECTED) + self.assert_irods_obj(self.obj_path) self.assert_alert_count(REJECT_ALERT, self.user, 0) self.assert_alert_count(REJECT_ALERT, self.user_delegate, 0) self.assert_alert_count(REJECT_ALERT, self.user_contrib, 1) def test_reject_delegate(self): - """Test rejecting a request as delegate""" - self.assertEqual(IrodsDataRequest.objects.count(), 0) + """Test POST to reject request as delegate""" self.assert_alert_count(REJECT_ALERT, self.user, 0) self.assert_alert_count(REJECT_ALERT, self.user_delegate, 0) self.assert_alert_count(REJECT_ALERT, self.user_contrib, 0) - - with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:api_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() - - with self.login(self.user_delegate): - response = self.client.get( - reverse( - 'samplesheets:api_irods_request_reject', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ), - ) - + response = self.request_knox( + self.url, 'POST', token=self.get_token(self.user_delegate) + ) self.assertEqual(response.status_code, 200) - obj.refresh_from_db() - self.assertEqual(obj.status, 'REJECTED') + self.request.refresh_from_db() + self.assertEqual(self.request.status, IRODS_REQUEST_STATUS_REJECTED) self.assert_alert_count(REJECT_ALERT, self.user, 0) self.assert_alert_count(REJECT_ALERT, self.user_delegate, 0) self.assert_alert_count(REJECT_ALERT, self.user_contrib, 1) def test_reject_contributor(self): - """Test rejecting a request as contributor""" - self.assertEqual(IrodsDataRequest.objects.count(), 0) + """Test POST to reject request as contributor""" self.assert_alert_count(REJECT_ALERT, self.user, 0) self.assert_alert_count(REJECT_ALERT, self.user_delegate, 0) self.assert_alert_count(REJECT_ALERT, self.user_contrib, 0) - - with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:api_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() - response = self.client.get( - reverse( - 'samplesheets:api_irods_request_reject', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ), - ) - + response = self.request_knox(self.url, 'POST', token=self.token_contrib) self.assertEqual(response.status_code, 403) - obj.refresh_from_db() - self.assertEqual(obj.status, 'ACTIVE') + self.request.refresh_from_db() + self.assertEqual(self.request.status, IRODS_REQUEST_STATUS_ACTIVE) + self.assert_irods_obj(self.obj_path) self.assert_alert_count(REJECT_ALERT, self.user, 0) self.assert_alert_count(REJECT_ALERT, self.user_delegate, 0) self.assert_alert_count(REJECT_ALERT, self.user_contrib, 0) - def test_reject_one_of_multiple(self): - """Test rejecting one of multipe requests""" - path2 = os.path.join(self.assay_path, TEST_FILE_NAME + '_2') - path2_md5 = os.path.join(self.assay_path, TEST_FILE_NAME + '_2.md5') - self.irods.data_objects.create(path2) - self.irods.data_objects.create(path2_md5) - self.assertEqual(IrodsDataRequest.objects.count(), 0) - - with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:api_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - self.post_data['path'] = path2 - self.client.post( - reverse( - 'samplesheets:api_irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - - self.assertEqual(IrodsDataRequest.objects.count(), 2) - obj = IrodsDataRequest.objects.first() - - with self.login(self.user): - response = self.client.get( - reverse( - 'samplesheets:api_irods_request_reject', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ), - ) - - self.assertEqual(response.status_code, 200) - self.assertEqual( - IrodsDataRequest.objects.filter(status='ACTIVE').count(), 1 - ) - obj.refresh_from_db() - self.assertEqual(obj.status, 'REJECTED') - self.assert_alert_count(REJECT_ALERT, self.user, 0) - self.assert_alert_count(REJECT_ALERT, self.user_delegate, 0) - self.assert_alert_count(REJECT_ALERT, self.user_contrib, 1) - def test_reject_no_request(self): - """Test rejecting request, that doesn't exist""" - self.assertEqual(IrodsDataRequest.objects.count(), 0) - with self.login(self.user): - response = self.client.get( - reverse( - 'samplesheets:api_irods_request_reject', - kwargs={'irodsdatarequest': self.category.sodar_uuid}, - ), - ) + """Test POST to reject non-existing request""" + url = reverse( + 'samplesheets:api_irods_request_reject', + kwargs={'irodsdatarequest': self.category.sodar_uuid}, + ) + response = self.request_knox(url, 'POST') self.assertEqual(response.status_code, 404) @@ -875,15 +645,15 @@ def setUp(self): super().setUp() self.make_irods_colls(self.investigation) - def test_get(self): - """Test getting file existence info with no file uploaded""" + def test_get_no_file(self): + """Test GET with no file uploaded""" url = reverse('samplesheets:api_file_exists') response = self.request_knox(url, data={'checksum': IRODS_FILE_MD5}) self.assertEqual(response.status_code, 200) self.assertEqual(json.loads(response.content)['status'], False) def test_get_file(self): - """Test getting file existence info with an uploaded file""" + """Test GET with uploaded file""" coll_path = self.irods_backend.get_sample_path(self.project) + '/' self.irods.data_objects.put( IRODS_FILE_PATH, coll_path, **{REG_CHKSUM_KW: ''} @@ -894,7 +664,7 @@ def test_get_file(self): self.assertEqual(json.loads(response.content)['status'], True) def test_get_file_sub_coll(self): - """Test getting file existence info in a sub collection""" + """Test GET with file in a sub collection""" coll_path = self.irods_backend.get_sample_path(self.project) + '/sub' self.irods.collections.create(coll_path) self.irods.data_objects.put( @@ -906,15 +676,15 @@ def test_get_file_sub_coll(self): self.assertEqual(json.loads(response.content)['status'], True) def test_get_no_checksum(self): - """Test getting file existence info with no checksum (should fail)""" + """Test GET with no checksum (should fail)""" url = reverse('samplesheets:api_file_exists') response = self.request_knox(url, data={'checksum': ''}) self.assertEqual(response.status_code, 400) def test_get_invalid_checksum(self): - """Test getting file existence info with an invalid checksum (should fail)""" + """Test GET with invalid checksum (should fail)""" url = reverse('samplesheets:api_file_exists') - response = self.request_knox(url, data={'checksum': 'Notvalid MD5!'}) + response = self.request_knox(url, data={'checksum': 'Invalid MD5!'}) self.assertEqual(response.status_code, 400) @@ -942,7 +712,7 @@ def setUp(self): self.assay = self.study.assays.first() def test_get_no_collection(self): - """Test GET request in ProjectIrodsFileListAPIView without collection""" + """Test ProjectIrodsFileListAPIView GET without collection""" url = reverse( 'samplesheets:api_file_list', kwargs={'project': self.project.sodar_uuid}, @@ -958,7 +728,7 @@ def test_get_no_collection(self): ) def test_get_empty_collection(self): - """Test GET request in ProjectIrodsFileListAPIView""" + """Test GET with empty collection""" # Set up iRODS collections self.make_irods_colls(self.investigation) url = reverse( @@ -971,7 +741,7 @@ def test_get_empty_collection(self): self.assertEqual(response.data['irods_data'], []) def test_get_collection_with_files(self): - """Test GET request in ProjectIrodsFileListAPIView""" + """Test GET with files""" # Set up iRODS collections self.make_irods_colls(self.investigation) coll_path = self.irods_backend.get_sample_path(self.project) + '/' diff --git a/samplesheets/tests/test_views_taskflow.py b/samplesheets/tests/test_views_taskflow.py index e9fb594d..3dfc5986 100644 --- a/samplesheets/tests/test_views_taskflow.py +++ b/samplesheets/tests/test_views_taskflow.py @@ -1,9 +1,9 @@ """View tests in the samplesheets Django app with taskflow""" -import irods import os from datetime import timedelta +from irods.exception import CollectionDoesNotExist, NoResultFound from irods.models import TicketQuery from urllib.parse import urlencode @@ -34,18 +34,26 @@ Investigation, IrodsAccessTicket, IrodsDataRequest, + IRODS_REQUEST_ACTION_DELETE, + IRODS_REQUEST_STATUS_ACTIVE, + IRODS_REQUEST_STATUS_FAILED, + IRODS_REQUEST_STATUS_ACCEPTED, + IRODS_REQUEST_STATUS_REJECTED, ) from samplesheets.tests.test_io import SampleSheetIOMixin, SHEET_DIR from samplesheets.tests.test_models import ( IrodsAccessTicketMixin, IrodsDataRequestMixin, + IRODS_REQUEST_DESC, ) from samplesheets.utils import get_sample_colls from samplesheets.views import ( - IRODS_REQ_ACCEPT_ALERT as ACCEPT_ALERT, - IRODS_REQ_CREATE_ALERT as CREATE_ALERT, - IRODS_REQ_REJECT_ALERT as REJECT_ALERT, - IRODS_NO_REQ_MSG, + IRODS_REQUEST_EVENT_ACCEPT as EVENT_ACCEPT, + IRODS_REQUEST_EVENT_CREATE as EVENT_CREATE, + IRODS_REQUEST_EVENT_DELETE as EVENT_DELETE, + IRODS_REQUEST_EVENT_REJECT as EVENT_REJECT, + IRODS_REQUEST_EVENT_UPDATE as EVENT_UPDATE, + NO_REQUEST_MSG, ) @@ -64,8 +72,8 @@ # Local constants APP_NAME = 'samplesheets' SHEET_PATH = SHEET_DIR + 'i_small.zip' -TEST_FILE_NAME = 'test1' -TEST_FILE_NAME2 = 'test2' +IRODS_FILE_NAME = 'test1.txt' +IRODS_FILE_NAME2 = 'test2.txt' DUMMY_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' PUBLIC_USER_NAME = 'user_no_roles' PUBLIC_USER_PASS = 'password' @@ -75,6 +83,7 @@ TICKET_LABEL = 'TestTicket' TICKET_LABEL_UPDATED = 'TestTicketUpdated' TICKET_STR = 'q657xxx3i2x2b8vj' +IRODS_REQUEST_DESC_UPDATE = 'Updated' class SampleSheetTaskflowMixin: @@ -115,7 +124,7 @@ def make_track_hub_coll(self, session, assay_path, name): track_hubs_path = assay_path + '/TrackHubs' try: session.collections.get(track_hubs_path) - except irods.exception.CollectionDoesNotExist: + except CollectionDoesNotExist: session.collections.create(track_hubs_path) track_hub = session.collections.create(track_hubs_path + '/' + name) return track_hub.path @@ -161,7 +170,7 @@ def get_irods_ticket(self, sodar_ticket): .filter(TicketQuery.Ticket.string == sodar_ticket.ticket) .one() ) - except irods.exception.NoResultFound: + except NoResultFound: return None @classmethod @@ -296,7 +305,7 @@ def _setup_files(self): self.assert_irods_coll(self.study) self.assert_irods_coll(self.assay) self.assay_path = self.irods_backend.get_path(self.assay) - self.file_path = self.assay_path + '/' + TEST_FILE_NAME + self.file_path = self.assay_path + '/' + IRODS_FILE_NAME self.irods.data_objects.create(self.file_path) self.assertEqual(self.irods.data_objects.exists(self.file_path), True) @@ -1045,13 +1054,14 @@ def test_delete(self): self.assertEqual(self.get_app_alert_count('delete'), 1) -class TestIrodsRequestViewsBase( +class IrodsDataRequestViewTestBase( SampleSheetIOMixin, SampleSheetTaskflowMixin, TaskflowViewTestBase, ): """Base test class for iRODS delete requests""" + # TODO: Retrieve this from a common base/helper class instead of redef def _assert_alert_count(self, alert_name, user, count, project=None): """ Assert expected app alert count. If project is not specified, default to @@ -1074,6 +1084,25 @@ def _assert_alert_count(self, alert_name, user, count, project=None): count, ) + # TODO: Move this into SODAR Core (see bihealth/sodar-core#1243) + def _assert_tl_count(self, event_name, count, **kwargs): + """ + Assert expected timeline event count. + + :param event_name: Event name (string) + :param user: SODARUser object + :param count: Integer + :param kwargs: Extra kwargs for query (dict, optional) + """ + timeline = get_backend_api('timeline_backend') + ProjectEvent, _ = timeline.get_models() + self.assertEqual( + ProjectEvent.objects.filter( + event_name=event_name, **kwargs + ).count(), + count, + ) + def setUp(self): super().setUp() self.project, self.owner_as = self.make_project_taskflow( @@ -1092,25 +1121,14 @@ def setUp(self): # Set up iRODS data self.make_irods_colls(self.investigation) self.assay_path = self.irods_backend.get_path(self.assay) - self.obj_path = os.path.join(self.assay_path, TEST_FILE_NAME) - self.obj_path_md5 = os.path.join( - self.assay_path, f'{TEST_FILE_NAME}.md5' - ) - # Second file - self.obj_path2 = os.path.join(self.assay_path, TEST_FILE_NAME2) - self.obj_path2_md5 = os.path.join( - self.assay_path, f'{TEST_FILE_NAME2}.md5' - ) - # Create objects + self.obj_path = os.path.join(self.assay_path, IRODS_FILE_NAME) + self.obj_path2 = os.path.join(self.assay_path, IRODS_FILE_NAME2) self.file_obj = self.irods.data_objects.create(self.obj_path) - self.md5_obj = self.irods.data_objects.create(self.obj_path_md5) self.file_obj2 = self.irods.data_objects.create(self.obj_path2) - self.md5_obj2 = self.irods.data_objects.create(self.obj_path2_md5) # Init users (owner = user_cat, superuser = user) self.user_delegate = self.make_user('user_delegate') self.user_contrib = self.make_user('user_contrib') - self.user_contrib2 = self.make_user('user_contrib2') self.user_guest = self.make_user('user_guest') self.make_assignment_taskflow( @@ -1119,9 +1137,6 @@ def setUp(self): self.make_assignment_taskflow( self.project, self.user_contrib, self.role_contributor ) - self.make_assignment_taskflow( - self.project, self.user_contrib2, self.role_contributor - ) self.make_assignment_taskflow( self.project, self.user_guest, self.role_guest ) @@ -1131,22 +1146,29 @@ def setUp(self): self.app_alert_model = self.app_alerts.get_model() # Set default POST data - self.post_data = {'path': self.obj_path, 'description': 'bla'} - self.post_data2 = {'path': self.obj_path2, 'description': 'bla2'} + self.post_data = { + 'path': self.obj_path, + 'description': IRODS_REQUEST_DESC, + } + self.post_data2 = { + 'path': self.obj_path2, + 'description': IRODS_REQUEST_DESC, + } def tearDown(self): self.irods.collections.get('/sodarZone/projects').remove(force=True) super().tearDown() -class TestIrodsRequestCreateView(TestIrodsRequestViewsBase): - """Test IrodsRequestCreateView""" +class TestIrodsDataRequestCreateView(IrodsDataRequestViewTestBase): + """Test IrodsDataRequestCreateView""" def test_create(self): """Test creating a delete request""" self.assertEqual(IrodsDataRequest.objects.count(), 0) - self._assert_alert_count(CREATE_ALERT, self.user, 0) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 0) + self._assert_tl_count(EVENT_CREATE, 0) + self._assert_alert_count(EVENT_CREATE, self.user, 0) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 0) with self.login(self.user_contrib): response = self.client.post( @@ -1171,14 +1193,18 @@ def test_create(self): ) self.assertEqual(IrodsDataRequest.objects.count(), 1) self.assertEqual(obj.path, self.obj_path) - self.assertEqual(obj.description, 'bla') - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) + self.assertEqual(obj.description, IRODS_REQUEST_DESC) + self._assert_tl_count(EVENT_CREATE, 1) + self._assert_alert_count(EVENT_CREATE, self.user, 1) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 1) def test_create_trailing_slash(self): """Test creating a delete request with trailing slash in path""" self.assertEqual(IrodsDataRequest.objects.count(), 0) - post_data = {'path': self.obj_path + '/', 'description': 'bla'} + post_data = { + 'path': self.obj_path + '/', + 'description': IRODS_REQUEST_DESC, + } with self.login(self.user_contrib): response = self.client.post( @@ -1203,14 +1229,15 @@ def test_create_trailing_slash(self): ) self.assertEqual(IrodsDataRequest.objects.count(), 1) self.assertEqual(obj.path, self.obj_path) - self.assertEqual(obj.description, 'bla') + self.assertEqual(obj.description, IRODS_REQUEST_DESC) def test_create_invalid_form_data(self): """Test creating a delete request with invalid form data""" self.assertEqual(IrodsDataRequest.objects.count(), 0) - self._assert_alert_count(CREATE_ALERT, self.user, 0) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 0) - post_data = {'path': '/doesnt/exist', 'description': 'bla'} + self._assert_tl_count(EVENT_CREATE, 0) + self._assert_alert_count(EVENT_CREATE, self.user, 0) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 0) + post_data = {'path': '/doesnt/exist', 'description': IRODS_REQUEST_DESC} with self.login(self.user_contrib): response = self.client.post( @@ -1226,13 +1253,14 @@ def test_create_invalid_form_data(self): ) self.assertEqual(IrodsDataRequest.objects.count(), 0) - self._assert_alert_count(CREATE_ALERT, self.user, 0) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 0) + self._assert_tl_count(EVENT_CREATE, 0) + self._assert_alert_count(EVENT_CREATE, self.user, 0) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 0) def test_create_invalid_path_assay_collection(self): """Test creating a delete request with assay path (should fail)""" self.assertEqual(IrodsDataRequest.objects.count(), 0) - post_data = {'path': self.assay_path, 'description': 'bla'} + post_data = {'path': self.assay_path, 'description': IRODS_REQUEST_DESC} with self.login(self.user_contrib): response = self.client.post( @@ -1251,14 +1279,12 @@ def test_create_invalid_path_assay_collection(self): def test_create_multiple(self): """Test creating multiple_requests""" - path2 = os.path.join(self.assay_path, TEST_FILE_NAME2) - path2_md5 = os.path.join(self.assay_path, TEST_FILE_NAME2 + '.md5') + path2 = os.path.join(self.assay_path, IRODS_FILE_NAME2) self.irods.data_objects.create(path2) - self.irods.data_objects.create(path2_md5) - self.assertEqual(IrodsDataRequest.objects.count(), 0) - self._assert_alert_count(CREATE_ALERT, self.user, 0) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 0) + self._assert_tl_count(EVENT_CREATE, 0) + self._assert_alert_count(EVENT_CREATE, self.user, 0) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 0) with self.login(self.user_contrib): self.client.post( @@ -1278,32 +1304,41 @@ def test_create_multiple(self): ) self.assertEqual(IrodsDataRequest.objects.count(), 2) - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) + self._assert_tl_count(EVENT_CREATE, 2) + self._assert_alert_count(EVENT_CREATE, self.user, 1) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 1) -class TestIrodsRequestUpdateView(TestIrodsRequestViewsBase): - """Tests for IrodsRequestUpdateView""" +class TestIrodsDataRequestUpdateView( + IrodsDataRequestMixin, IrodsDataRequestViewTestBase +): + """Tests for IrodsDataRequestUpdateView""" + + def setUp(self): + super().setUp() + self.request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=self.obj_path, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user_contrib, + description=IRODS_REQUEST_DESC, + ) def test_update(self): """Test POST request for updating a delete request""" - post_data = {'path': self.obj_path, 'description': 'Description'} - update_data = {'path': self.obj_path, 'description': 'Updated'} + self.assertEqual(IrodsDataRequest.objects.count(), 1) + self._assert_tl_count(EVENT_UPDATE, 0) + update_data = { + 'path': self.obj_path, + 'description': IRODS_REQUEST_DESC_UPDATE, + } with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - post_data, - ) - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() response = self.client.post( reverse( 'samplesheets:irods_request_update', - kwargs={'irodsdatarequest': obj.sodar_uuid}, + kwargs={'irodsdatarequest': self.request.sodar_uuid}, ), update_data, ) @@ -1315,47 +1350,51 @@ def test_update(self): ), ) - obj = IrodsDataRequest.objects.first() self.assertEqual( list(get_messages(response.wsgi_request))[-1].message, - 'iRODS data request "{}" updated.'.format(obj.get_display_name()), + 'iRODS data request "{}" updated.'.format( + self.request.get_display_name() + ), ) self.assertEqual(IrodsDataRequest.objects.count(), 1) - self.assertEqual(obj.path, self.obj_path) - self.assertEqual(obj.description, 'Updated') + self.request.refresh_from_db() + self.assertEqual(self.request.path, self.obj_path) + self.assertEqual(self.request.description, IRODS_REQUEST_DESC_UPDATE) + self._assert_tl_count(EVENT_UPDATE, 1) def test_post_update_invalid_form_data(self): """Test updating a delete request with invalid form data""" - post_data = {'path': self.obj_path, 'description': 'Description'} - update_data = {'path': '/doesnt/exist', 'description': 'Updated'} + self.assertEqual(IrodsDataRequest.objects.count(), 1) + self._assert_tl_count(EVENT_UPDATE, 0) + update_data = { + 'path': '/sodarZone/path/does/not/exist', + 'description': IRODS_REQUEST_DESC_UPDATE, + } with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - post_data, - ) - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() response = self.client.post( reverse( 'samplesheets:irods_request_update', - kwargs={'irodsdatarequest': obj.sodar_uuid}, + kwargs={'irodsdatarequest': self.request.sodar_uuid}, ), update_data, ) - self.assertEqual( - response.context['form'].errors['path'][0], - ERROR_MSG_INVALID_PATH, - ) + + self.assertEqual( + response.context['form'].errors['path'][0], + ERROR_MSG_INVALID_PATH, + ) + self.assertEqual(IrodsDataRequest.objects.count(), 1) + self.request.refresh_from_db() + self.assertEqual(self.request.path, self.obj_path) + self.assertEqual(self.request.description, IRODS_REQUEST_DESC) + self._assert_tl_count(EVENT_UPDATE, 0) -class TestIrodsRequestDeleteView( - IrodsDataRequestMixin, TestIrodsRequestViewsBase +class TestIrodsDataRequestDeleteView( + IrodsDataRequestMixin, IrodsDataRequestViewTestBase ): - """Tests for IrodsRequestUpdateView""" + """Tests for IrodsDataRequestUpdateView""" def setUp(self): super().setUp() @@ -1364,42 +1403,19 @@ def setUp(self): kwargs={'project': self.project.sodar_uuid}, ) - def test_get_contributor(self): - """Test GET request for deleting a request""" - self.assertEqual(IrodsDataRequest.objects.count(), 0) - - self.make_irods_data_request( - project=self.project, - action='delete', - path=self.obj_path, - status='ACTIVE', - user=self.user_contrib, - ) - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() - - with self.login(self.user_contrib): - response = self.client.get( - reverse( - 'samplesheets:irods_request_delete', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ), - ) - self.assertEqual(response.status_code, 200) - self.assertEqual(IrodsDataRequest.objects.count(), 1) - def test_delete_contributor(self): """Test POST request for deleting a request""" - self.assertEqual(IrodsDataRequest.objects.count(), 0) - + # NOTE: We use post() to ensure alerts are created with self.login(self.user_contrib): self.client.post(self.create_url, self.post_data) + self.assertEqual(IrodsDataRequest.objects.count(), 1) + self.assert_irods_obj(self.obj_path) + self._assert_tl_count(EVENT_DELETE, 0) + self._assert_alert_count(EVENT_CREATE, self.user, 1) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 1) + obj = IrodsDataRequest.objects.first() - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) - + with self.login(self.user_contrib): response = self.client.post( reverse( 'samplesheets:irods_request_delete', @@ -1419,28 +1435,31 @@ def test_delete_contributor(self): 'iRODS data request deleted.', ) self.assertEqual(IrodsDataRequest.objects.count(), 0) - self._assert_alert_count(CREATE_ALERT, self.user, 0) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 0) + self.assert_irods_obj(self.obj_path) + # Create requests should be deleted + self._assert_tl_count(EVENT_DELETE, 1) + self._assert_alert_count(EVENT_CREATE, self.user, 0) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 0) def test_delete_one_of_multiple(self): """Test deleting one of multiple requests""" - path2 = os.path.join(self.assay_path, TEST_FILE_NAME2) - path2_md5 = os.path.join(self.assay_path, TEST_FILE_NAME2 + '.md5') - self.irods.data_objects.create(path2) - self.irods.data_objects.create(path2_md5) + self._assert_tl_count(EVENT_DELETE, 0) + obj_path2 = os.path.join(self.assay_path, IRODS_FILE_NAME2) + self.irods.data_objects.create(obj_path2) self.assertEqual(IrodsDataRequest.objects.count(), 0) with self.login(self.user_contrib): self.client.post(self.create_url, self.post_data) - self.post_data['path'] = path2 + self.post_data['path'] = obj_path2 self.client.post(self.create_url, self.post_data) self.assertEqual(IrodsDataRequest.objects.count(), 2) - obj = IrodsDataRequest.objects.first() + self.assert_irods_obj(self.obj_path) + self.assert_irods_obj(obj_path2) # NOTE: Still should only have one request for both - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) - + self._assert_alert_count(EVENT_CREATE, self.user, 1) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 1) + obj = IrodsDataRequest.objects.filter(path=obj_path2).first() self.client.post( reverse( 'samplesheets:irods_request_delete', @@ -1449,15 +1468,18 @@ def test_delete_one_of_multiple(self): ) self.assertEqual(IrodsDataRequest.objects.count(), 1) + self.assert_irods_obj(self.obj_path) + self.assert_irods_obj(obj_path2) + self._assert_tl_count(EVENT_DELETE, 1) # NOTE: After deleting just one the requests, alerts remain - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) + self._assert_alert_count(EVENT_CREATE, self.user, 1) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 1) -class TestIrodsRequestAcceptView( - IrodsDataRequestMixin, TestIrodsRequestViewsBase +class TestIrodsDataRequestAcceptView( + IrodsDataRequestMixin, IrodsDataRequestViewTestBase ): - """Tests for IrodsRequestAcceptView""" + """Tests for IrodsDataRequestAcceptView""" def setUp(self): super().setUp() @@ -1467,13 +1489,13 @@ def setUp(self): ) def test_render(self): - """Test rendering IrodsRequestAcceptView""" + """Test rendering IrodsDataRequestAcceptView""" self.assert_irods_obj(self.obj_path) - self.make_irods_data_request( + self.make_irods_request( project=self.project, - action='delete', + action=IRODS_REQUEST_ACTION_DELETE, path=self.obj_path, - status='ACTIVE', + status=IRODS_REQUEST_STATUS_ACTIVE, user=self.user_contrib, ) self.assertEqual(IrodsDataRequest.objects.count(), 1) @@ -1490,29 +1512,28 @@ def test_render(self): self.assertEqual(response.context['request_objects'][0], obj) def test_render_coll(self): - """Test rendering IrodsRequestAcceptView with a collection request""" + """Test rendering IrodsDataRequestAcceptView with collection request""" coll_path = os.path.join(self.assay_path, 'request_coll') self.irods.collections.create(coll_path) self.assertEqual(self.irods.collections.exists(coll_path), True) - self.make_irods_data_request( + request = self.make_irods_request( project=self.project, - action='delete', + action=IRODS_REQUEST_ACTION_DELETE, path=coll_path, - status='ACTIVE', + status=IRODS_REQUEST_STATUS_ACTIVE, user=self.user_contrib, ) self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() with self.login(self.user): response = self.client.get( reverse( 'samplesheets:irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, + kwargs={'irodsdatarequest': request.sodar_uuid}, ), {'confirm': True}, ) - self.assertEqual(response.context['request_objects'][0], obj) + self.assertEqual(response.context['request_objects'][0], request) def test_accept(self): """Test accepting a delete request""" @@ -1520,12 +1541,13 @@ def test_accept(self): with self.login(self.user_contrib): self.client.post(self.create_url, self.post_data) self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) - self._assert_alert_count(ACCEPT_ALERT, self.user, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) + self._assert_tl_count(EVENT_ACCEPT, 0) + self._assert_alert_count(EVENT_CREATE, self.user, 1) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 1) + self._assert_alert_count(EVENT_ACCEPT, self.user, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_delegate, 0) + obj = IrodsDataRequest.objects.first() with self.login(self.user): response = self.client.post( reverse( @@ -1541,23 +1563,22 @@ def test_accept(self): kwargs={'project': self.project.sodar_uuid}, ), ) - self.assertEqual( - list(get_messages(response.wsgi_request))[-1].message, - 'iRODS data request "{}" accepted.'.format( - obj.get_display_name() - ), - ) + self.assertEqual( + list(get_messages(response.wsgi_request))[-1].message, + 'iRODS data request "{}" accepted.'.format(obj.get_display_name()), + ) obj.refresh_from_db() - self.assertEqual(obj.status, 'ACCEPTED') - self._assert_alert_count(CREATE_ALERT, self.user, 0) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) + self.assertEqual(obj.status, IRODS_REQUEST_STATUS_ACCEPTED) + self._assert_tl_count(EVENT_ACCEPT, 1) + self._assert_alert_count(EVENT_CREATE, self.user, 0) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_delegate, 0) self.assert_irods_obj(self.obj_path, False) def test_accept_no_request(self): - """Test accepting a delete request that doesn't exist""" + """Test accepting non-existing delete request""" self.assertEqual(IrodsDataRequest.objects.count(), 0) with self.login(self.user_owner_cat): response = self.client.post( @@ -1570,17 +1591,18 @@ def test_accept_no_request(self): self.assertEqual(response.status_code, 404) def test_accept_invalid_form_data(self): - """Test accepting a delete request with invalid form data""" + """Test accepting delete request with invalid form data""" self.assert_irods_obj(self.obj_path) with self.login(self.user_contrib): self.client.post(self.create_url, self.post_data) self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) - self._assert_alert_count(ACCEPT_ALERT, self.user, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) + self._assert_tl_count(EVENT_ACCEPT, 0) + self._assert_alert_count(EVENT_CREATE, self.user, 1) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 1) + self._assert_alert_count(EVENT_ACCEPT, self.user, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_delegate, 0) + obj = IrodsDataRequest.objects.first() with self.login(self.user): response = self.client.post( reverse( @@ -1589,172 +1611,153 @@ def test_accept_invalid_form_data(self): ), {'confirm': False}, ) - self.assertEqual(response.status_code, 200) - self.assertEqual( - response.context['form'].errors['confirm'][0], - 'This field is required.', - ) - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) - self._assert_alert_count(ACCEPT_ALERT, self.user, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) + self.assertEqual(response.status_code, 200) + self.assertEqual( + response.context['form'].errors['confirm'][0], + 'This field is required.', + ) + self._assert_tl_count(EVENT_ACCEPT, 0) + self._assert_alert_count(EVENT_CREATE, self.user, 1) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 1) + self._assert_alert_count(EVENT_ACCEPT, self.user, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_delegate, 0) self.assert_irods_obj(self.obj_path) def test_accept_owner(self): - """Test accepting a delete request as owner""" + """Test accepting delete request as owner""" self.assert_irods_obj(self.obj_path) - self.make_irods_data_request( + request = self.make_irods_request( project=self.project, - action='delete', + action=IRODS_REQUEST_ACTION_DELETE, path=self.obj_path, - status='ACTIVE', + status=IRODS_REQUEST_STATUS_ACTIVE, user=self.user_contrib, ) - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() - self._assert_alert_count(ACCEPT_ALERT, self.user, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_contrib, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_delegate, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_contrib, 0) with self.login(self.user_owner_cat): - response = self.client.post( + self.client.post( reverse( 'samplesheets:irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, + kwargs={'irodsdatarequest': request.sodar_uuid}, ), {'confirm': True}, ) - self.assertRedirects( - response, - reverse( - 'samplesheets:irods_requests', - kwargs={'project': self.project.sodar_uuid}, - ), - ) - self.assertEqual( - list(get_messages(response.wsgi_request))[-1].message, - 'iRODS data request "{}" accepted.'.format( - obj.get_display_name() - ), - ) - obj.refresh_from_db() - self.assertEqual(obj.status, 'ACCEPTED') + request.refresh_from_db() + self.assertEqual(request.status, IRODS_REQUEST_STATUS_ACCEPTED) self.assert_irods_obj(self.obj_path, False) - self._assert_alert_count(ACCEPT_ALERT, self.user, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_contrib, 1) + self._assert_alert_count(EVENT_ACCEPT, self.user, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_delegate, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_contrib, 1) def test_accept_delegate(self): - """Test accepting a delete request as delegate""" + """Test accepting delete request as delegate""" self.assert_irods_obj(self.obj_path) - self.make_irods_data_request( + request = self.make_irods_request( project=self.project, - action='delete', + action=IRODS_REQUEST_ACTION_DELETE, path=self.obj_path, - status='ACTIVE', + status=IRODS_REQUEST_STATUS_ACTIVE, user=self.user_contrib, ) - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() - self._assert_alert_count(ACCEPT_ALERT, self.user, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_contrib, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_delegate, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_contrib, 0) with self.login(self.user_delegate): - response = self.client.post( + self.client.post( reverse( 'samplesheets:irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, + kwargs={'irodsdatarequest': request.sodar_uuid}, ), {'confirm': True}, ) - self.assertRedirects( - response, - reverse( - 'samplesheets:irods_requests', - kwargs={'project': self.project.sodar_uuid}, - ), - ) - self.assertEqual( - list(get_messages(response.wsgi_request))[-1].message, - 'iRODS data request "{}" accepted.'.format( - obj.get_display_name() - ), - ) - obj.refresh_from_db() - self.assertEqual(obj.status, 'ACCEPTED') + request.refresh_from_db() + self.assertEqual(request.status, IRODS_REQUEST_STATUS_ACCEPTED) self.assert_irods_obj(self.obj_path, False) - self._assert_alert_count(ACCEPT_ALERT, self.user, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_contrib, 1) + self._assert_alert_count(EVENT_ACCEPT, self.user, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_delegate, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_contrib, 1) def test_accept_contributor(self): - """Test accepting a delete request as contributor""" + """Test accepting delete request as contributor""" self.assert_irods_obj(self.obj_path) - self.make_irods_data_request( + request = self.make_irods_request( project=self.project, - action='delete', + action=IRODS_REQUEST_ACTION_DELETE, path=self.obj_path, - status='ACTIVE', + status=IRODS_REQUEST_STATUS_ACTIVE, user=self.user_contrib, ) - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() - self._assert_alert_count(ACCEPT_ALERT, self.user, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_contrib, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_delegate, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_contrib, 0) with self.login(self.user_contrib): response = self.client.post( reverse( 'samplesheets:irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, + kwargs={'irodsdatarequest': request.sodar_uuid}, ), {'confirm': True}, ) self.assertRedirects(response, reverse('home')) - self.assertEqual(IrodsDataRequest.objects.count(), 1) + request.refresh_from_db() + self.assertEqual(request.status, IRODS_REQUEST_STATUS_ACTIVE) self.assert_irods_obj(self.obj_path) - self._assert_alert_count(ACCEPT_ALERT, self.user, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_contrib, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_delegate, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_contrib, 0) def test_accept_one_of_multiple(self): """Test accepting one of multiple requests""" - path2 = os.path.join(self.assay_path, TEST_FILE_NAME2) - path2_md5 = os.path.join(self.assay_path, TEST_FILE_NAME2 + '.md5') - self.irods.data_objects.create(path2) - self.irods.data_objects.create(path2_md5) - self.assertEqual(IrodsDataRequest.objects.count(), 0) - with self.login(self.user_contrib): - self.client.post(self.create_url, self.post_data) - self.post_data['path'] = path2 - self.client.post(self.create_url, self.post_data) + obj_path2 = os.path.join(self.assay_path, IRODS_FILE_NAME2) + self.irods.data_objects.create(obj_path2) + self.assert_irods_obj(self.obj_path) + self.assert_irods_obj(obj_path2) + self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=self.obj_path, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user_contrib, + ) + request2 = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=obj_path2, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user_contrib, + ) self.assertEqual( - IrodsDataRequest.objects.filter(status='ACTIVE').count(), 2 + IrodsDataRequest.objects.filter( + status=IRODS_REQUEST_STATUS_ACTIVE + ).count(), + 2, ) - obj = IrodsDataRequest.objects.first() - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) - with self.login(self.user): self.client.post( reverse( 'samplesheets:irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, + kwargs={'irodsdatarequest': request2.sodar_uuid}, ), {'confirm': True}, ) self.assertEqual( - IrodsDataRequest.objects.filter(status='ACTIVE').count(), 1 + IrodsDataRequest.objects.filter( + status=IRODS_REQUEST_STATUS_ACTIVE + ).count(), + 1, ) - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) + self.assert_irods_obj(self.obj_path) + self.assert_irods_obj(obj_path2, False) @override_settings(REDIS_URL=INVALID_REDIS_URL) def test_accept_lock_failure(self): @@ -1763,10 +1766,10 @@ def test_accept_lock_failure(self): with self.login(self.user_contrib): self.client.post(self.create_url, self.post_data) self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) + self._assert_alert_count(EVENT_CREATE, self.user, 1) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 1) + obj = IrodsDataRequest.objects.first() with self.login(self.user): response = self.client.post( reverse( @@ -1784,64 +1787,53 @@ def test_accept_lock_failure(self): ) obj.refresh_from_db() - self.assertEqual(obj.status, 'FAILED') - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) + self.assertEqual(obj.status, IRODS_REQUEST_STATUS_FAILED) + self._assert_alert_count(EVENT_CREATE, self.user, 1) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 1) self.assert_irods_obj(self.obj_path, True) def test_accept_collection(self): """Test accepting a collection request with multiple objects inside""" coll_path = os.path.join(self.assay_path, 'request_coll') - obj_path = os.path.join(coll_path, TEST_FILE_NAME) + obj_path2 = os.path.join(coll_path, IRODS_FILE_NAME) self.irods.collections.create(coll_path) - self.irods.data_objects.create(obj_path) + self.irods.data_objects.create(obj_path2) self.assertEqual(self.irods.collections.exists(coll_path), True) - self.post_data['path'] = coll_path - with self.login(self.user_contrib): - self.client.post(self.create_url, self.post_data) - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) - self._assert_alert_count(ACCEPT_ALERT, self.user, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) + self.assert_irods_obj(obj_path2) + + request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=coll_path, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user_contrib, + ) + self._assert_alert_count(EVENT_ACCEPT, self.user, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_delegate, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_contrib, 0) with self.login(self.user): - response = self.client.post( + self.client.post( reverse( 'samplesheets:irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, + kwargs={'irodsdatarequest': request.sodar_uuid}, ), {'confirm': True}, ) - self.assertRedirects( - response, - reverse( - 'samplesheets:irods_requests', - kwargs={'project': self.project.sodar_uuid}, - ), - ) - self.assertEqual( - list(get_messages(response.wsgi_request))[-1].message, - 'iRODS data request "{}" accepted.'.format( - obj.get_display_name() - ), - ) - obj.refresh_from_db() - self.assertEqual(obj.status, 'ACCEPTED') - self._assert_alert_count(CREATE_ALERT, self.user, 0) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) + request.refresh_from_db() + self.assertEqual(request.status, IRODS_REQUEST_STATUS_ACCEPTED) + self._assert_alert_count(EVENT_ACCEPT, self.user, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_delegate, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_contrib, 1) self.assertEqual(self.irods.collections.exists(coll_path), False) - self.assert_irods_obj(obj_path, False) + self.assert_irods_obj(obj_path2, False) -class TestIrodsRequestAcceptBatchView( - IrodsDataRequestMixin, TestIrodsRequestViewsBase +class TestIrodsDataRequestAcceptBatchView( + IrodsDataRequestMixin, IrodsDataRequestViewTestBase ): - """Tests for IrodsRequestAcceptBatchView""" + """Tests for IrodsDataRequestAcceptBatchView""" def setUp(self): super().setUp() @@ -1855,21 +1847,20 @@ def setUp(self): ) def test_render(self): - """Test rendering IrodsRequestAcceptBatchView""" + """Test rendering IrodsDataRequestAcceptBatchView""" self.assert_irods_obj(self.obj_path) - self.assert_irods_obj(self.obj_path_md5) - self.make_irods_data_request( + self.make_irods_request( project=self.project, - action='delete', + action=IRODS_REQUEST_ACTION_DELETE, path=self.obj_path, - status='ACTIVE', + status=IRODS_REQUEST_STATUS_ACTIVE, user=self.user_contrib, ) - self.make_irods_data_request( + self.make_irods_request( project=self.project, - action='delete', + action=IRODS_REQUEST_ACTION_DELETE, path=self.obj_path2, - status='ACTIVE', + status=IRODS_REQUEST_STATUS_ACTIVE, user=self.user_contrib, ) self.assertEqual(IrodsDataRequest.objects.count(), 2) @@ -1909,15 +1900,15 @@ def test_render(self): ) def test_render_coll(self): - """Test rendering IrodsRequestAcceptBatchView for a collection""" + """Test rendering IrodsDataRequestAcceptBatchView for a collection""" coll_path = os.path.join(self.assay_path, 'request_coll') self.irods.collections.create(coll_path) self.assertEqual(self.irods.collections.exists(coll_path), True) - self.make_irods_data_request( + self.make_irods_request( project=self.project, - action='delete', + action=IRODS_REQUEST_ACTION_DELETE, path=coll_path, - status='ACTIVE', + status=IRODS_REQUEST_STATUS_ACTIVE, user=self.user_contrib, ) self.assertEqual(IrodsDataRequest.objects.count(), 1) @@ -1950,7 +1941,6 @@ def test_accept(self): with self.login(self.user_contrib): self.client.post(self.create_url, self.post_data) - self.assertEqual(IrodsDataRequest.objects.count(), 1) self.client.post( reverse( 'samplesheets:irods_request_create', @@ -1958,12 +1948,12 @@ def test_accept(self): ), self.post_data2, ) - self.assertEqual(IrodsDataRequest.objects.count(), 2) - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) - self._assert_alert_count(ACCEPT_ALERT, self.user, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) + self.assertEqual(IrodsDataRequest.objects.count(), 2) + self._assert_alert_count(EVENT_CREATE, self.user, 1) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 1) + self._assert_alert_count(EVENT_ACCEPT, self.user, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_delegate, 0) with self.login(self.user): response = self.client.post( @@ -2001,14 +1991,14 @@ def test_accept(self): ) obj = IrodsDataRequest.objects.first() obj.refresh_from_db() - self.assertEqual(obj.status, 'ACCEPTED') + self.assertEqual(obj.status, IRODS_REQUEST_STATUS_ACCEPTED) obj2 = IrodsDataRequest.objects.last() obj2.refresh_from_db() - self.assertEqual(obj2.status, 'ACCEPTED') - self._assert_alert_count(CREATE_ALERT, self.user, 0) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) + self.assertEqual(obj2.status, IRODS_REQUEST_STATUS_ACCEPTED) + self._assert_alert_count(EVENT_CREATE, self.user, 0) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_delegate, 0) self.assert_irods_obj(self.obj_path, False) self.assert_irods_obj(self.obj_path2, False) @@ -2026,7 +2016,7 @@ def test_accept_no_request(self): self.assertEqual(response.status_code, 302) self.assertEqual( list(get_messages(response.wsgi_request))[-1].message, - IRODS_NO_REQ_MSG, + NO_REQUEST_MSG, ) def test_accept_invalid_form_data(self): @@ -2040,10 +2030,10 @@ def test_accept_invalid_form_data(self): self.client.post(self.create_url, self.post_data2) self.assertEqual(IrodsDataRequest.objects.count(), 2) - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) - self._assert_alert_count(ACCEPT_ALERT, self.user, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) + self._assert_alert_count(EVENT_CREATE, self.user, 1) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 1) + self._assert_alert_count(EVENT_ACCEPT, self.user, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_delegate, 0) with self.login(self.user): response = self.client.post( @@ -2064,10 +2054,10 @@ def test_accept_invalid_form_data(self): response.context['form'].errors['confirm'][0], 'This field is required.', ) - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) - self._assert_alert_count(ACCEPT_ALERT, self.user, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) + self._assert_alert_count(EVENT_CREATE, self.user, 1) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 1) + self._assert_alert_count(EVENT_ACCEPT, self.user, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_delegate, 0) self.assert_irods_obj(self.obj_path) self.assert_irods_obj(self.obj_path2) @@ -2083,10 +2073,10 @@ def test_accept_lock_failure(self): self.client.post(self.create_url, self.post_data2) self.assertEqual(IrodsDataRequest.objects.count(), 2) - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) - self._assert_alert_count(ACCEPT_ALERT, self.user, 0) - self._assert_alert_count(ACCEPT_ALERT, self.user_delegate, 0) + self._assert_alert_count(EVENT_CREATE, self.user, 1) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 1) + self._assert_alert_count(EVENT_ACCEPT, self.user, 0) + self._assert_alert_count(EVENT_ACCEPT, self.user_delegate, 0) with self.login(self.user): response = self.client.post( @@ -2111,20 +2101,20 @@ def test_accept_lock_failure(self): ) obj = IrodsDataRequest.objects.first() obj.refresh_from_db() - self.assertEqual(obj.status, 'FAILED') + self.assertEqual(obj.status, IRODS_REQUEST_STATUS_FAILED) obj2 = IrodsDataRequest.objects.last() obj2.refresh_from_db() - self.assertEqual(obj2.status, 'FAILED') - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) + self.assertEqual(obj2.status, IRODS_REQUEST_STATUS_FAILED) + self._assert_alert_count(EVENT_CREATE, self.user, 1) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 1) self.assert_irods_obj(self.obj_path) self.assert_irods_obj(self.obj_path2) -class TestIrodsRequestRejectView( - IrodsDataRequestMixin, TestIrodsRequestViewsBase +class TestIrodsDataRequestRejectView( + IrodsDataRequestMixin, IrodsDataRequestViewTestBase ): - """Tests for IrodsRequestRejectView""" + """Tests for IrodsDataRequestRejectView""" def setUp(self): super().setUp() @@ -2136,24 +2126,23 @@ def setUp(self): def test_reject_admin(self): """Test rejecting delete request as admin""" self.assertEqual(IrodsDataRequest.objects.count(), 0) - self._assert_alert_count(REJECT_ALERT, self.user, 0) - self._assert_alert_count(REJECT_ALERT, self.user_delegate, 0) - self._assert_alert_count(REJECT_ALERT, self.user_contrib, 0) - self.make_irods_data_request( + self.assert_irods_obj(self.obj_path) + self._assert_alert_count(EVENT_REJECT, self.user, 0) + self._assert_alert_count(EVENT_REJECT, self.user_delegate, 0) + self._assert_alert_count(EVENT_REJECT, self.user_contrib, 0) + request = self.make_irods_request( project=self.project, - action='delete', + action=IRODS_REQUEST_ACTION_DELETE, path=self.obj_path, - status='ACTIVE', + status=IRODS_REQUEST_STATUS_ACTIVE, user=self.user_contrib, ) - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() with self.login(self.user): response = self.client.get( reverse( 'samplesheets:irods_request_reject', - kwargs={'irodsdatarequest': obj.sodar_uuid}, + kwargs={'irodsdatarequest': request.sodar_uuid}, ), ) self.assertRedirects( @@ -2166,110 +2155,83 @@ def test_reject_admin(self): self.assertEqual( list(get_messages(response.wsgi_request))[-1].message, - 'iRODS data request "{}" rejected.'.format(obj.get_display_name()), + 'iRODS data request "{}" rejected.'.format( + request.get_display_name() + ), ) - obj.refresh_from_db() - self.assertEqual(obj.status, 'REJECTED') - self._assert_alert_count(REJECT_ALERT, self.user, 0) - self._assert_alert_count(REJECT_ALERT, self.user_delegate, 0) - self._assert_alert_count(REJECT_ALERT, self.user_contrib, 1) + request.refresh_from_db() + self.assertEqual(request.status, IRODS_REQUEST_STATUS_REJECTED) + self.assert_irods_obj(self.obj_path) + self._assert_alert_count(EVENT_REJECT, self.user, 0) + self._assert_alert_count(EVENT_REJECT, self.user_delegate, 0) + self._assert_alert_count(EVENT_REJECT, self.user_contrib, 1) def test_reject_owner(self): """Test rejecting delete request as owner""" self.assertEqual(IrodsDataRequest.objects.count(), 0) - self.make_irods_data_request( + request = self.make_irods_request( project=self.project, - action='delete', + action=IRODS_REQUEST_ACTION_DELETE, path=self.obj_path, - status='ACTIVE', + status=IRODS_REQUEST_STATUS_ACTIVE, user=self.user_contrib, ) - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() with self.login(self.user_owner_cat): - response = self.client.get( + self.client.get( reverse( 'samplesheets:irods_request_reject', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ), - ) - self.assertRedirects( - response, - reverse( - 'samplesheets:irods_requests', - kwargs={'project': self.project.sodar_uuid}, + kwargs={'irodsdatarequest': request.sodar_uuid}, ), ) - self.assertEqual( - list(get_messages(response.wsgi_request))[-1].message, - 'iRODS data request "{}" rejected.'.format(obj.get_display_name()), - ) - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj.refresh_from_db() - self.assertEqual(obj.status, 'REJECTED') - self._assert_alert_count(REJECT_ALERT, self.user, 0) - self._assert_alert_count(REJECT_ALERT, self.user_delegate, 0) - self._assert_alert_count(REJECT_ALERT, self.user_contrib, 1) + request.refresh_from_db() + self.assertEqual(request.status, IRODS_REQUEST_STATUS_REJECTED) + self._assert_alert_count(EVENT_REJECT, self.user, 0) + self._assert_alert_count(EVENT_REJECT, self.user_delegate, 0) + self._assert_alert_count(EVENT_REJECT, self.user_contrib, 1) def test_reject_delegate(self): """Test rejecting delete request as delegate""" self.assertEqual(IrodsDataRequest.objects.count(), 0) - self.make_irods_data_request( + request = self.make_irods_request( project=self.project, - action='delete', + action=IRODS_REQUEST_ACTION_DELETE, path=self.obj_path, - status='ACTIVE', + status=IRODS_REQUEST_STATUS_ACTIVE, user=self.user_contrib, ) - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() with self.login(self.user_delegate): - response = self.client.get( + self.client.get( reverse( 'samplesheets:irods_request_reject', - kwargs={'irodsdatarequest': obj.sodar_uuid}, - ), - ) - self.assertRedirects( - response, - reverse( - 'samplesheets:irods_requests', - kwargs={'project': self.project.sodar_uuid}, + kwargs={'irodsdatarequest': request.sodar_uuid}, ), ) - self.assertEqual( - list(get_messages(response.wsgi_request))[-1].message, - 'iRODS data request "{}" rejected.'.format(obj.get_display_name()), - ) - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj.refresh_from_db() - self.assertEqual(obj.status, 'REJECTED') - self._assert_alert_count(REJECT_ALERT, self.user, 0) - self._assert_alert_count(REJECT_ALERT, self.user_delegate, 0) - self._assert_alert_count(REJECT_ALERT, self.user_contrib, 1) + request.refresh_from_db() + self.assertEqual(request.status, IRODS_REQUEST_STATUS_REJECTED) + self._assert_alert_count(EVENT_REJECT, self.user, 0) + self._assert_alert_count(EVENT_REJECT, self.user_delegate, 0) + self._assert_alert_count(EVENT_REJECT, self.user_contrib, 1) def test_reject_contributor(self): """Test rejecting delete request as contributor""" self.assertEqual(IrodsDataRequest.objects.count(), 0) - self.make_irods_data_request( + request = self.make_irods_request( project=self.project, - action='delete', + action=IRODS_REQUEST_ACTION_DELETE, path=self.obj_path, - status='ACTIVE', + status=IRODS_REQUEST_STATUS_ACTIVE, user=self.user_contrib, ) - self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() with self.login(self.user_contrib): response = self.client.get( reverse( 'samplesheets:irods_request_reject', - kwargs={'irodsdatarequest': obj.sodar_uuid}, + kwargs={'irodsdatarequest': request.sodar_uuid}, ), ) self.assertRedirects(response, reverse('home')) @@ -2278,28 +2240,31 @@ def test_reject_contributor(self): list(get_messages(response.wsgi_request))[-1].message, 'User not authorized for requested action.', ) - self.assertEqual(IrodsDataRequest.objects.count(), 1) - self._assert_alert_count(REJECT_ALERT, self.user, 0) - self._assert_alert_count(REJECT_ALERT, self.user_delegate, 0) - self._assert_alert_count(REJECT_ALERT, self.user_contrib, 0) + request.refresh_from_db() + self.assertEqual(request.status, IRODS_REQUEST_STATUS_ACTIVE) + self._assert_alert_count(EVENT_REJECT, self.user, 0) + self._assert_alert_count(EVENT_REJECT, self.user_delegate, 0) + self._assert_alert_count(EVENT_REJECT, self.user_contrib, 0) def test_reject_one_of_multiple(self): """Test rejecting one of multiple requests""" - path2 = os.path.join(self.assay_path, TEST_FILE_NAME2) - path2_md5 = os.path.join(self.assay_path, TEST_FILE_NAME2 + '.md5') - self.irods.data_objects.create(path2) - self.irods.data_objects.create(path2_md5) - self.assertEqual(IrodsDataRequest.objects.count(), 0) + obj_path2 = os.path.join(self.assay_path, IRODS_FILE_NAME2) + self.irods.data_objects.create(obj_path2) + self.assert_irods_obj(self.obj_path) + self.assert_irods_obj(obj_path2) with self.login(self.user_contrib): self.client.post(self.create_url, self.post_data) - self.post_data['path'] = path2 + self.post_data['path'] = obj_path2 self.client.post(self.create_url, self.post_data) self.assertEqual( - IrodsDataRequest.objects.filter(status='ACTIVE').count(), 2 + IrodsDataRequest.objects.filter( + status=IRODS_REQUEST_STATUS_ACTIVE + ).count(), + 2, ) - obj = IrodsDataRequest.objects.first() - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) + obj = IrodsDataRequest.objects.filter(path=obj_path2).first() + self._assert_alert_count(EVENT_CREATE, self.user, 1) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 1) with self.login(self.user): self.client.get( @@ -2310,18 +2275,21 @@ def test_reject_one_of_multiple(self): ) self.assertEqual( - IrodsDataRequest.objects.filter(status='ACTIVE').count(), 1 + IrodsDataRequest.objects.filter( + status=IRODS_REQUEST_STATUS_ACTIVE + ).count(), + 1, ) - self._assert_alert_count(CREATE_ALERT, self.user, 1) - self._assert_alert_count(CREATE_ALERT, self.user_delegate, 1) - self._assert_alert_count(REJECT_ALERT, self.user, 0) - self._assert_alert_count(REJECT_ALERT, self.user_delegate, 0) - self._assert_alert_count(REJECT_ALERT, self.user_contrib, 1) + self.assert_irods_obj(self.obj_path) + self.assert_irods_obj(obj_path2) + self._assert_alert_count(EVENT_CREATE, self.user, 1) + self._assert_alert_count(EVENT_CREATE, self.user_delegate, 1) + self._assert_alert_count(EVENT_REJECT, self.user, 0) + self._assert_alert_count(EVENT_REJECT, self.user_delegate, 0) + self._assert_alert_count(EVENT_REJECT, self.user_contrib, 1) def test_reject_no_request(self): """Test rejecting delete request that doesn't exist""" - self.assertEqual(IrodsDataRequest.objects.count(), 0) - with self.login(self.user_owner_cat): response = self.client.get( reverse( @@ -2332,10 +2300,10 @@ def test_reject_no_request(self): self.assertEqual(response.status_code, 404) -class TestIrodsRequestRejectBatchView( - IrodsDataRequestMixin, TestIrodsRequestViewsBase +class TestIrodsDataRequestRejectBatchView( + IrodsDataRequestMixin, IrodsDataRequestViewTestBase ): - """Tests for IrodsRequestRejectBatchView""" + """Tests for IrodsDataRequestRejectBatchView""" def setUp(self): super().setUp() @@ -2351,15 +2319,12 @@ def setUp(self): def test_reject(self): """Test rejecting delete request""" self.assertEqual(IrodsDataRequest.objects.count(), 0) - self._assert_alert_count(REJECT_ALERT, self.user, 0) - self._assert_alert_count(REJECT_ALERT, self.user_delegate, 0) - self._assert_alert_count(REJECT_ALERT, self.user_contrib, 0) + self._assert_alert_count(EVENT_REJECT, self.user, 0) + self._assert_alert_count(EVENT_REJECT, self.user_delegate, 0) + self._assert_alert_count(EVENT_REJECT, self.user_contrib, 0) with self.login(self.user): self.client.post(self.create_url, self.post_data) - self.assertEqual(IrodsDataRequest.objects.count(), 1) self.client.post(self.create_url, self.post_data2) - self.assertEqual(IrodsDataRequest.objects.count(), 2) - response = self.client.post( self.reject_url, { @@ -2394,17 +2359,16 @@ def test_reject(self): ) obj = IrodsDataRequest.objects.first() obj.refresh_from_db() - self.assertEqual(obj.status, 'REJECTED') + self.assertEqual(obj.status, IRODS_REQUEST_STATUS_REJECTED) obj2 = IrodsDataRequest.objects.last() obj2.refresh_from_db() - self.assertEqual(obj2.status, 'REJECTED') - self._assert_alert_count(REJECT_ALERT, self.user, 0) - self._assert_alert_count(REJECT_ALERT, self.user_delegate, 0) - self._assert_alert_count(REJECT_ALERT, self.user_contrib, 0) + self.assertEqual(obj2.status, IRODS_REQUEST_STATUS_REJECTED) + self._assert_alert_count(EVENT_REJECT, self.user, 0) + self._assert_alert_count(EVENT_REJECT, self.user_delegate, 0) + self._assert_alert_count(EVENT_REJECT, self.user_contrib, 0) def test_reject_no_request(self): """Test rejecting delete request that doesn't exist""" - self.assertEqual(IrodsDataRequest.objects.count(), 0) with self.login(self.user_owner_cat): response = self.client.post( self.reject_url, @@ -2413,132 +2377,10 @@ def test_reject_no_request(self): self.assertEqual(response.status_code, 302) self.assertEqual( list(get_messages(response.wsgi_request))[-1].message, - IRODS_NO_REQ_MSG, + NO_REQUEST_MSG, ) -class TestIrodsRequestListView(TestIrodsRequestViewsBase): - """Tests for IrodsRequestListView""" - - def test_list(self): - """Test GET request for listing delete requests""" - self.assertEqual(IrodsDataRequest.objects.count(), 0) - - with self.login(self.user): - self.client.post( - reverse( - 'samplesheets:irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - self.assertEqual(IrodsDataRequest.objects.count(), 1) - - response = self.client.get( - reverse( - 'samplesheets:irods_requests', - kwargs={'project': self.project.sodar_uuid}, - ), - ) - self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['object_list']), 1) - self.assertEqual(response.context['object_list'][0].path, self.obj_path) - - def test_list_as_admin_by_contributor(self): - """Test GET request for listing delete requests""" - self.assertEqual(IrodsDataRequest.objects.count(), 0) - - with self.login(self.user): - self.client.post( - reverse( - 'samplesheets:irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - - self.assertEqual(IrodsDataRequest.objects.count(), 1) - - with self.login(self.user_contrib): - response = self.client.get( - reverse( - 'samplesheets:irods_requests', - kwargs={'project': self.project.sodar_uuid}, - ), - ) - - self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['object_list']), 0) - - def test_list_as_owner_by_contributor(self): - """Test GET request for listing delete requests""" - self.assertEqual(IrodsDataRequest.objects.count(), 0) - - with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - - self.assertEqual(IrodsDataRequest.objects.count(), 1) - - with self.login(self.user): - response = self.client.get( - reverse( - 'samplesheets:irods_requests', - kwargs={'project': self.project.sodar_uuid}, - ), - ) - - self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['object_list']), 1) - self.assertEqual(response.context['object_list'][0].path, self.obj_path) - - def test_list_as_contributor2_by_contributor(self): - """Test GET request for listing delete requests""" - self.assertEqual(IrodsDataRequest.objects.count(), 0) - - with self.login(self.user_contrib): - self.client.post( - reverse( - 'samplesheets:irods_request_create', - kwargs={'project': self.project.sodar_uuid}, - ), - self.post_data, - ) - - self.assertEqual(IrodsDataRequest.objects.count(), 1) - - with self.login(self.user_contrib2): - response = self.client.get( - reverse( - 'samplesheets:irods_requests', - kwargs={'project': self.project.sodar_uuid}, - ), - ) - - self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['object_list']), 0) - - def test_list_empty(self): - """Test GET request for empty list of delete requests""" - self.assertEqual(IrodsDataRequest.objects.count(), 0) - - with self.login(self.user): - response = self.client.get( - reverse( - 'samplesheets:irods_requests', - kwargs={'project': self.project.sodar_uuid}, - ), - ) - - self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context['object_list']), 0) - - class TestSampleDataPublicAccess( SampleSheetIOMixin, SampleSheetTaskflowMixin, @@ -2551,14 +2393,11 @@ def setUp(self): super().setUp() # Create user in iRODS self.user_no_roles = self.make_user(PUBLIC_USER_NAME) - try: - self.irods.users.create( - user_name=PUBLIC_USER_NAME, - user_type='rodsuser', - user_zone=self.irods.zone, - ) - except irods.exception.CATALOG_ALREADY_HAS_ITEM_BY_THAT_NAME: - pass # In case a previous test failed before cleanup + self.irods.users.create( + user_name=PUBLIC_USER_NAME, + user_type='rodsuser', + user_zone=self.irods.zone, + ) self.irods.users.modify(PUBLIC_USER_NAME, 'password', PUBLIC_USER_PASS) self.user_home_path = '/{}/home/{}'.format( settings.IRODS_ZONE, PUBLIC_USER_NAME @@ -2587,7 +2426,7 @@ def setUp(self): self.sample_path = self.irods_backend.get_sample_path(self.project) # Create test file - self.file_path = self.sample_path + '/' + TEST_FILE_NAME + self.file_path = self.sample_path + '/' + IRODS_FILE_NAME self.irods.data_objects.create(self.file_path) def tearDown(self): @@ -2601,7 +2440,7 @@ def test_public_access(self): obj = self.user_session.data_objects.get(self.file_path) self.assertIsNotNone(obj) # Ensure no access to project root - with self.assertRaises(irods.exception.CollectionDoesNotExist): + with self.assertRaises(CollectionDoesNotExist): self.user_session.data_objects.get(self.project_path) def test_public_access_disable(self): @@ -2609,7 +2448,7 @@ def test_public_access_disable(self): self.set_public_access(False) obj = self.irods.data_objects.get(self.file_path) # Test with owner self.assertIsNotNone(obj) - with self.assertRaises(irods.exception.CollectionDoesNotExist): + with self.assertRaises(CollectionDoesNotExist): self.user_session.data_objects.get(self.file_path) def test_public_access_reenable(self): @@ -2621,7 +2460,7 @@ def test_public_access_reenable(self): obj = self.user_session.data_objects.get(self.file_path) self.assertIsNotNone(obj) # Ensure no access to project root - with self.assertRaises(irods.exception.CollectionDoesNotExist): + with self.assertRaises(CollectionDoesNotExist): self.user_session.data_objects.get(self.project_path) def test_public_access_nested(self): @@ -2638,7 +2477,7 @@ def test_public_access_nested_disable(self): new_coll_path = self.sample_path + '/new_coll' coll = self.irods.collections.create(new_coll_path) # Test with owner self.assertIsNotNone(coll) - with self.assertRaises(irods.exception.CollectionDoesNotExist): + with self.assertRaises(CollectionDoesNotExist): self.user_session.collections.get(new_coll_path) diff --git a/samplesheets/urls.py b/samplesheets/urls.py index afe8a7e0..8b97af4d 100644 --- a/samplesheets/urls.py +++ b/samplesheets/urls.py @@ -131,37 +131,37 @@ ), path( route='irods/request/create/', - view=views.IrodsRequestCreateView.as_view(), + view=views.IrodsDataRequestCreateView.as_view(), name='irods_request_create', ), path( route='irods/request/update/', - view=views.IrodsRequestUpdateView.as_view(), + view=views.IrodsDataRequestUpdateView.as_view(), name='irods_request_update', ), path( route='irods/request/delete/', - view=views.IrodsRequestDeleteView.as_view(), + view=views.IrodsDataRequestDeleteView.as_view(), name='irods_request_delete', ), path( route='irods/request/accept/', - view=views.IrodsRequestAcceptView.as_view(), + view=views.IrodsDataRequestAcceptView.as_view(), name='irods_request_accept', ), path( route='irods/request/accept/batch/', - view=views.IrodsRequestAcceptBatchView.as_view(), + view=views.IrodsDataRequestAcceptBatchView.as_view(), name='irods_request_accept_batch', ), path( route='irods/request/reject/', - view=views.IrodsRequestRejectView.as_view(), + view=views.IrodsDataRequestRejectView.as_view(), name='irods_request_reject', ), path( route='irods/request/reject/batch/', - view=views.IrodsRequestRejectBatchView.as_view(), + view=views.IrodsDataRequestRejectBatchView.as_view(), name='irods_request_reject_batch', ), ] @@ -178,6 +178,11 @@ view=samplesheets.views_api.IrodsCollsCreateAPIView.as_view(), name='api_irods_colls_create', ), + path( + route='api/irods/request/retrieve/', + view=samplesheets.views_api.IrodsDataRequestRetrieveAPIView.as_view(), + name='api_irods_request_retrieve', + ), path( route='api/irods/requests/', view=samplesheets.views_api.IrodsDataRequestListAPIView.as_view(), @@ -185,27 +190,27 @@ ), path( route='api/irods/request/create/', - view=samplesheets.views_api.IrodsRequestCreateAPIView.as_view(), + view=samplesheets.views_api.IrodsDataRequestCreateAPIView.as_view(), name='api_irods_request_create', ), path( route='api/irods/request/update/', - view=samplesheets.views_api.IrodsRequestUpdateAPIView.as_view(), + view=samplesheets.views_api.IrodsDataRequestUpdateAPIView.as_view(), name='api_irods_request_update', ), path( route='api/irods/request/delete/', - view=samplesheets.views_api.IrodsRequestDeleteAPIView.as_view(), + view=samplesheets.views_api.IrodsDataRequestDestroyAPIView.as_view(), name='api_irods_request_delete', ), path( route='api/irods/request/accept/', - view=samplesheets.views_api.IrodsRequestAcceptAPIView.as_view(), + view=samplesheets.views_api.IrodsDataRequestAcceptAPIView.as_view(), name='api_irods_request_accept', ), path( route='api/irods/request/reject/', - view=samplesheets.views_api.IrodsRequestRejectAPIView.as_view(), + view=samplesheets.views_api.IrodsDataRequestRejectAPIView.as_view(), name='api_irods_request_reject', ), path( @@ -299,12 +304,12 @@ ), path( route='ajax/irods/request/create/', - view=samplesheets.views_ajax.IrodsRequestCreateAjaxView.as_view(), + view=samplesheets.views_ajax.IrodsDataRequestCreateAjaxView.as_view(), name='ajax_irods_request_create', ), path( route='ajax/irods/request/delete/', - view=samplesheets.views_ajax.IrodsRequestDeleteAjaxView.as_view(), + view=samplesheets.views_ajax.IrodsDataRequestDeleteAjaxView.as_view(), name='ajax_irods_request_delete', ), path( diff --git a/samplesheets/views.py b/samplesheets/views.py index 0cc645f9..782ae3ac 100644 --- a/samplesheets/views.py +++ b/samplesheets/views.py @@ -43,6 +43,7 @@ ROLE_RANKING, ) from projectroles.plugins import get_backend_api +from projectroles.rules import can_modify_project_data from projectroles.utils import build_secret from projectroles.views import ( LoginRequiredMixin, @@ -59,8 +60,8 @@ SheetImportForm, SheetTemplateCreateForm, IrodsAccessTicketForm, - IrodsRequestForm, - IrodsRequestAcceptForm, + IrodsDataRequestForm, + IrodsDataRequestAcceptForm, SheetVersionEditForm, ) from samplesheets.io import ( @@ -75,6 +76,10 @@ ISATab, IrodsAccessTicket, IrodsDataRequest, + IRODS_REQUEST_STATUS_ACCEPTED, + IRODS_REQUEST_STATUS_ACTIVE, + IRODS_REQUEST_STATUS_FAILED, + IRODS_REQUEST_STATUS_REJECTED, ) from samplesheets.rendering import SampleSheetTableBuilder, EMPTY_VALUE from samplesheets.sheet_config import SheetConfigAPI @@ -108,10 +113,12 @@ TRACK_HUBS_COLL = 'TrackHubs' RESULTS_COLL_ID = 'results_reports' RESULTS_COLL = 'ResultsReports' -IRODS_REQ_CREATE_ALERT = 'irods_request_create' -IRODS_REQ_ACCEPT_ALERT = 'irods_request_accept' -IRODS_NO_REQ_MSG = 'No iRODS data requests found for the given UUIDs' -IRODS_REQ_REJECT_ALERT = 'irods_request_reject' +IRODS_REQUEST_EVENT_ACCEPT = 'irods_request_accept' +IRODS_REQUEST_EVENT_CREATE = 'irods_request_create' +IRODS_REQUEST_EVENT_DELETE = 'irods_request_delete' +IRODS_REQUEST_EVENT_REJECT = 'irods_request_reject' +IRODS_REQUEST_EVENT_UPDATE = 'irods_request_update' +NO_REQUEST_MSG = 'No iRODS data requests found for the given UUIDs' SYNC_SUCCESS_MSG = 'Sample sheet sync successful' SYNC_FAIL_DISABLED = 'Sample sheet sync disabled' SYNC_FAIL_PREFIX = 'Sample sheet sync failed' @@ -821,26 +828,27 @@ def create_app_alerts(cls, ticket, action, user): ) -class IrodsRequestModifyMixin: - """iRODS data request helpers""" +class IrodsDataRequestModifyMixin: + """iRODS data request modification helpers""" + + def __init__(self): + self.timeline = get_backend_api('timeline_backend') # Timeline helpers --------------------------------------------------------- - @classmethod - def add_tl_create(cls, irods_request): + def add_tl_create(self, irods_request): """ Create timeline event for iRODS data request creation. :param irods_request: IrodsDataRequest object """ - timeline = get_backend_api('timeline_backend') - if not timeline: + if not self.timeline: return - tl_event = timeline.add_event( + tl_event = self.timeline.add_event( project=irods_request.project, app_name=APP_NAME, user=irods_request.user, - event_name='irods_request_create', + event_name=IRODS_REQUEST_EVENT_CREATE, description='create iRODS data request {irods_request}', status_type='OK', ) @@ -850,21 +858,19 @@ def add_tl_create(cls, irods_request): name=irods_request.get_display_name(), ) - @classmethod - def add_tl_update(cls, irods_request, timeline=None): + def add_tl_update(self, irods_request): """ Create timeline event for iRODS data request update. :param irods_request: IrodsDataRequest object - :param timeline: TimelineAPI instance or None """ - if not timeline: + if not self.timeline: return - tl_event = timeline.add_event( + tl_event = self.timeline.add_event( project=irods_request.project, app_name=APP_NAME, user=irods_request.user, - event_name='irods_request_update', + event_name=IRODS_REQUEST_EVENT_UPDATE, description='update iRODS data request {irods_request}', status_type='OK', ) @@ -874,21 +880,19 @@ def add_tl_update(cls, irods_request, timeline=None): name=irods_request.get_display_name(), ) - @classmethod - def add_tl_delete(cls, irods_request): + def add_tl_delete(self, irods_request): """ Create timeline event for iRODS data request deletion. :param irods_request: IrodsDataRequest object """ - timeline = get_backend_api('timeline_backend') - if not timeline: + if not self.timeline: return - tl_event = timeline.add_event( + tl_event = self.timeline.add_event( project=irods_request.project, app_name=APP_NAME, user=irods_request.user, - event_name='irods_request_delete', + event_name=IRODS_REQUEST_EVENT_DELETE, description='delete iRODS data request {irods_request}', status_type='OK', ) @@ -928,7 +932,7 @@ def add_alerts_create(self, project, app_alerts=None): alert_count = AppAlert.objects.filter( project=project, user=u, - alert_name=IRODS_REQ_CREATE_ALERT, + alert_name=IRODS_REQUEST_EVENT_CREATE, active=True, ).count() if alert_count > 0: @@ -936,7 +940,7 @@ def add_alerts_create(self, project, app_alerts=None): continue # Only have one active alert per user/project app_alerts.add_alert( app_name=APP_NAME, - alert_name=IRODS_REQ_CREATE_ALERT, + alert_name=IRODS_REQUEST_EVENT_CREATE, user=u, message='iRODS delete requests require attention in ' 'project "{}"'.format(project.title), @@ -966,14 +970,15 @@ def handle_alerts_deactivate(cls, irods_request, app_alerts=None): AppAlert = app_alerts.get_model() req_count = ( IrodsDataRequest.objects.filter( - project=irods_request.project, status='ACTIVE' + project=irods_request.project, + status=IRODS_REQUEST_STATUS_ACTIVE, ) .exclude(sodar_uuid=irods_request.sodar_uuid) .count() ) if req_count == 0: alerts = AppAlert.objects.filter( - alert_name=IRODS_REQ_CREATE_ALERT, + alert_name=IRODS_REQUEST_EVENT_CREATE, project=irods_request.project, active=True, ) @@ -1010,11 +1015,11 @@ def accept_request( :raise: FlowSubmitException if taskflow submission fails """ tl_event = None - if irods_request.status == 'ACCEPTED': + if irods_request.status == IRODS_REQUEST_STATUS_ACCEPTED: description = ( 'iRODS data request {irods_request} is already accepted' ) - status = 'FAILED' + status = IRODS_REQUEST_STATUS_FAILED else: description = 'accept iRODS data request {irods_request}' status = 'OK' @@ -1023,7 +1028,7 @@ def accept_request( project=project, app_name=APP_NAME, user=request.user, - event_name='irods_request_accept', + event_name=IRODS_REQUEST_EVENT_ACCEPT, description=description, status_type=status, ) @@ -1033,7 +1038,7 @@ def accept_request( name=irods_request.get_display_name(), ) - if irods_request.status == 'ACCEPTED': + if irods_request.status == IRODS_REQUEST_STATUS_ACCEPTED: raise IrodsDataRequest.DoesNotExist('Request is already accepted') flow_name = 'data_delete' @@ -1049,10 +1054,10 @@ def accept_request( tl_event=tl_event, async_mode=False, ) - irods_request.status = 'ACCEPTED' + irods_request.status = IRODS_REQUEST_STATUS_ACCEPTED irods_request.save() except taskflow.FlowSubmitException as ex: - irods_request.status = 'FAILED' + irods_request.status = IRODS_REQUEST_STATUS_FAILED irods_request.save() raise ex @@ -1085,7 +1090,7 @@ def accept_request( if app_alerts and irods_request.user != request.user: app_alerts.add_alert( app_name=APP_NAME, - alert_name=IRODS_REQ_ACCEPT_ALERT, + alert_name=IRODS_REQUEST_EVENT_ACCEPT, user=irods_request.user, message='iRODS delete request accepted by {}: "{}"'.format( request.user.username, irods_request.get_short_path() @@ -1113,15 +1118,15 @@ def reject_request( :param timeline: Timeline API or None :param app_alerts: Appalerts API or None """ - if irods_request.status == 'REJECTED': + if irods_request.status == IRODS_REQUEST_STATUS_REJECTED: description = ( 'iRODS data request {irods_request} is already rejected' ) - status = 'FAILED' + status = IRODS_REQUEST_STATUS_FAILED else: description = 'reject iRODS data request {irods_request}' status = 'OK' - irods_request.status = 'REJECTED' + irods_request.status = IRODS_REQUEST_STATUS_REJECTED irods_request.save() if timeline: @@ -1159,7 +1164,7 @@ def reject_request( if app_alerts and irods_request.user != request.user: app_alerts.add_alert( app_name=APP_NAME, - alert_name=IRODS_REQ_REJECT_ALERT, + alert_name=IRODS_REQUEST_EVENT_REJECT, user=irods_request.user, message='iRODS delete request rejected by {}: "{}"'.format( request.user.username, irods_request.get_short_path() @@ -1174,14 +1179,17 @@ def reject_request( # Handle project alerts cls.handle_alerts_deactivate(irods_request, app_alerts) - def has_irods_request_perms(self, request, irods_request): - """Check permissions for a landing zone.""" + def has_irods_request_update_perms(self, request, irods_request): + """Check permissions for iRODS data request updating""" if ( request.user.is_superuser or request.user.has_perm( 'samplesheets.manage_sheet', irods_request.project ) - or request.user == irods_request.user + or ( + request.user == irods_request.user + and can_modify_project_data(request.user, irods_request.project) + ) ): return True return False @@ -2449,19 +2457,25 @@ def delete(self, request, *args, **kwargs): return super().delete(request, *args, **kwargs) -class IrodsRequestCreateView( +class IrodsDataRequestCreateView( LoginRequiredMixin, LoggedInPermissionMixin, ProjectPermissionMixin, InvestigationContextMixin, - IrodsRequestModifyMixin, + IrodsDataRequestModifyMixin, FormView, ): """View for creating an iRODS data request""" permission_required = 'samplesheets.edit_sheet' template_name = 'samplesheets/irods_request_form.html' - form_class = IrodsRequestForm + form_class = IrodsDataRequestForm + + def get_form_kwargs(self): + """Pass kwargs to form""" + kwargs = super().get_form_kwargs() + kwargs.update({'project': self.get_project().sodar_uuid}) + return kwargs def form_valid(self, form): project = self.get_project() @@ -2486,12 +2500,12 @@ def form_valid(self, form): ) -class IrodsRequestUpdateView( +class IrodsDataRequestUpdateView( LoginRequiredMixin, LoggedInPermissionMixin, ProjectPermissionMixin, InvestigationContextMixin, - IrodsRequestModifyMixin, + IrodsDataRequestModifyMixin, UpdateView, ): """View for updating an iRODS data request""" @@ -2499,20 +2513,30 @@ class IrodsRequestUpdateView( permission_required = 'samplesheets.edit_sheet' template_name = 'samplesheets/irods_request_form.html' model = IrodsDataRequest - form_class = IrodsRequestForm + form_class = IrodsDataRequestForm slug_url_kwarg = 'irodsdatarequest' slug_field = 'sodar_uuid' + def has_permission(self): + """Override has_permission() to check update perms""" + if not self.has_irods_request_update_perms( + self.request, self.get_object() + ): + return False + return super().has_permission() + + def get_form_kwargs(self): + """Pass kwargs to form""" + kwargs = super().get_form_kwargs() + kwargs.update({'project': self.get_project().sodar_uuid}) + return kwargs + def form_valid(self, form): - timeline = get_backend_api('timeline_backend') - # Create database object obj = form.save(commit=False) obj.user = self.request.user obj.project = self.get_project() obj.save() - - self.add_tl_update(obj, timeline=timeline) - + self.add_tl_update(obj) messages.success( self.request, 'iRODS data request "{}" updated.'.format(obj.get_display_name()), @@ -2525,21 +2549,29 @@ def form_valid(self, form): ) -class IrodsRequestDeleteView( +class IrodsDataRequestDeleteView( LoginRequiredMixin, LoggedInPermissionMixin, ProjectPermissionMixin, InvestigationContextMixin, - IrodsRequestModifyMixin, + IrodsDataRequestModifyMixin, DeleteView, ): """View for deleting an iRODS data request""" - permission_required = 'samplesheets.delete_sheet' - template_name = 'samplesheets/irods_request_confirm_delete.html' model = IrodsDataRequest + permission_required = 'samplesheets.edit_sheet' slug_url_kwarg = 'irodsdatarequest' slug_field = 'sodar_uuid' + template_name = 'samplesheets/irods_request_confirm_delete.html' + + def has_permission(self): + """Override has_permission() to check update perms""" + if not self.has_irods_request_update_perms( + self.request, self.get_object() + ): + return False + return super().has_permission() def get_success_url(self): # Add timeline event @@ -2553,19 +2585,19 @@ def get_success_url(self): ) -class IrodsRequestAcceptView( +class IrodsDataRequestAcceptView( LoginRequiredMixin, LoggedInPermissionMixin, ProjectPermissionMixin, InvestigationContextMixin, - IrodsRequestModifyMixin, + IrodsDataRequestModifyMixin, FormView, ): - """View for accepting iRODS data requests""" + """View for accepting an iRODS data request""" permission_required = 'samplesheets.manage_sheet' template_name = 'samplesheets/irods_request_accept_form.html' - form_class = IrodsRequestAcceptForm + form_class = IrodsDataRequestAcceptForm def get_form_kwargs(self): """Override to pass number of requests to form""" @@ -2657,17 +2689,17 @@ def post(self, request, *args, **kwargs): ) -class IrodsRequestAcceptBatchView( +class IrodsDataRequestAcceptBatchView( LoginRequiredMixin, LoggedInPermissionMixin, ProjectPermissionMixin, InvestigationContextMixin, - IrodsRequestModifyMixin, + IrodsDataRequestModifyMixin, FormView, ): template_name = 'samplesheets/irods_request_accept_form.html' permission_required = 'samplesheets.manage_sheet' - form_class = IrodsRequestAcceptForm + form_class = IrodsDataRequestAcceptForm def get_form_kwargs(self): """Override to pass number of requests to form""" @@ -2717,7 +2749,7 @@ def post(self, request, *args, **kwargs): project = self.get_project() batch = self.get_irods_request_objects() if not batch: - messages.error(self.request, IRODS_NO_REQ_MSG) + messages.error(self.request, NO_REQUEST_MSG) return redirect( reverse( 'samplesheets:irods_requests', @@ -2769,12 +2801,12 @@ def post(self, request, *args, **kwargs): ) -class IrodsRequestRejectView( +class IrodsDataRequestRejectView( LoginRequiredMixin, LoggedInPermissionMixin, ProjectPermissionMixin, InvestigationContextMixin, - IrodsRequestModifyMixin, + IrodsDataRequestModifyMixin, View, ): """View for rejecting iRODS data requests""" @@ -2785,6 +2817,7 @@ def get(self, request, *args, **kwargs): timeline = get_backend_api('timeline_backend') app_alerts = get_backend_api('appalerts_backend') project = self.get_project() + try: obj = IrodsDataRequest.objects.filter( sodar_uuid=self.kwargs['irodsdatarequest'] @@ -2810,13 +2843,13 @@ def get(self, request, *args, **kwargs): obj.get_display_name(), ex ), ) - return redirect( reverse( 'samplesheets:irods_requests', kwargs={'project': self.get_project().sodar_uuid}, ) ) + except Exception as ex: messages.error(request, str(ex)) return redirect( @@ -2827,12 +2860,12 @@ def get(self, request, *args, **kwargs): ) -class IrodsRequestRejectBatchView( +class IrodsDataRequestRejectBatchView( LoginRequiredMixin, LoggedInPermissionMixin, ProjectPermissionMixin, InvestigationContextMixin, - IrodsRequestModifyMixin, + IrodsDataRequestModifyMixin, View, ): """View for rejecting iRODS data requests""" @@ -2843,12 +2876,13 @@ def post(self, request, *args, **kwargs): timeline = get_backend_api('timeline_backend') app_alerts = get_backend_api('appalerts_backend') project = self.get_project() + try: batch = self.get_irods_request_objects() if not batch: messages.error( self.request, - IRODS_NO_REQ_MSG, + NO_REQUEST_MSG, ) return redirect( reverse( @@ -2878,13 +2912,13 @@ def post(self, request, *args, **kwargs): obj.get_display_name(), ex ), ) - return redirect( reverse( 'samplesheets:irods_requests', kwargs={'project': self.get_project().sodar_uuid}, ) ) + except Exception as ex: messages.error(request, str(ex)) return redirect( @@ -2935,7 +2969,12 @@ def get_queryset(self): or project.is_delegate(self.request.user) or project.is_owner(self.request.user) ): - return queryset.filter(status__in=['ACTIVE', 'FAILED']) + return queryset.filter( + status__in=[ + IRODS_REQUEST_STATUS_ACTIVE, + IRODS_REQUEST_STATUS_FAILED, + ] + ) # For regular users, dispaly their own requests regardless of status return queryset.filter(user=self.request.user) diff --git a/samplesheets/views_ajax.py b/samplesheets/views_ajax.py index 67951120..b7941784 100644 --- a/samplesheets/views_ajax.py +++ b/samplesheets/views_ajax.py @@ -47,7 +47,7 @@ get_ext_link_labels, ) from samplesheets.views import ( - IrodsRequestModifyMixin, + IrodsDataRequestModifyMixin, app_settings, APP_NAME, TARGET_ALTAMISA_VERSION, @@ -1842,8 +1842,8 @@ def post(self, request, *args, **kwargs): ) -class IrodsRequestCreateAjaxView( - IrodsRequestModifyMixin, SODARBaseProjectAjaxView +class IrodsDataRequestCreateAjaxView( + IrodsDataRequestModifyMixin, SODARBaseProjectAjaxView ): """Ajax view for creating an iRODS data request""" @@ -1883,8 +1883,8 @@ def post(self, request, *args, **kwargs): ) -class IrodsRequestDeleteAjaxView( - IrodsRequestModifyMixin, SODARBaseProjectAjaxView +class IrodsDataRequestDeleteAjaxView( + IrodsDataRequestModifyMixin, SODARBaseProjectAjaxView ): """Ajax view for deleting an iRODS data request""" diff --git a/samplesheets/views_api.py b/samplesheets/views_api.py index 5808689a..33c2f040 100644 --- a/samplesheets/views_api.py +++ b/samplesheets/views_api.py @@ -18,8 +18,10 @@ PermissionDenied, ) from rest_framework.generics import ( - RetrieveAPIView, CreateAPIView, + DestroyAPIView, + ListAPIView, + RetrieveAPIView, UpdateAPIView, ) from rest_framework.permissions import AllowAny, IsAuthenticated @@ -37,15 +39,21 @@ ) from samplesheets.io import SampleSheetIO -from samplesheets.models import Investigation, ISATab, IrodsDataRequest +from samplesheets.models import ( + Investigation, + ISATab, + IrodsDataRequest, + IRODS_REQUEST_STATUS_ACTIVE, + IRODS_REQUEST_STATUS_FAILED, +) from samplesheets.rendering import SampleSheetTableBuilder from samplesheets.serializers import ( InvestigationSerializer, - IrodsRequestSerializer, + IrodsDataRequestSerializer, ) from samplesheets.views import ( IrodsCollsCreateViewMixin, - IrodsRequestModifyMixin, + IrodsDataRequestModifyMixin, SheetImportMixin, SheetISAExportMixin, SITE_MODE_TARGET, @@ -147,274 +155,6 @@ def post(self, request, *args, **kwargs): ) -class IrodsDataRequestListAPIView( - IrodsRequestModifyMixin, SODARAPIBaseProjectMixin, APIView -): - """ - List iRODS data requests for a project. - - **URL:** ``/samplesheets/api/irods/requests/{Project.sodar_uuid}`` - - **Methods:** ``GET`` - - **Returns:** - - - ``requests``: List of iRODS data requests (JSON) - """ - - http_method_names = ['get'] - permission_required = 'samplesheets.edit_sheet' - - def get(self, request, *args, **kwargs): - """GET request for listing iRODS data requests""" - project = self.get_project() - irods_requests = IrodsDataRequest.objects.filter(project=project) - content = {'detail': 'No iRODS data requests found'} - - # For superusers, owners and delegates, - # display active/failed requests from all users - if self.request.user.is_superuser or project.is_owner_or_delegate( - self.request.user - ): - requests = irods_requests.filter(status__in=['ACTIVE', 'FAILED']) - else: - # For regular users, dispaly their own requests regardless of status - requests = irods_requests.filter(user=self.request.user) - if requests: - content['requests'] = requests - content['detail'] = 'iRODS data requests listed' - return Response(content, status=status.HTTP_200_OK) - - -class IrodsRequestCreateAPIView( - IrodsRequestModifyMixin, SODARAPIBaseProjectMixin, CreateAPIView -): - """ - Create an iRODS data request for a project. - - **URL:** ``/samplesheets/api/irods/request/create/{Project.sodar_uuid}`` - - **Methods:** ``POST`` - """ - - http_method_names = ['post'] - permission_required = 'samplesheets.edit_sheet' - serializer_class = IrodsRequestSerializer - - def post(self, request, *args, **kwargs): - """POST request for creating an iRODS data request""" - project = self.get_project() - serializer = self.get_serializer(data=request.data) - if serializer.is_valid(): - serializer.save( - project=project, - user=self.request.user, - ) - else: - raise ValidationError('Invalid data: {}'.format(serializer.errors)) - - irods_request = IrodsDataRequest.objects.filter( - sodar_uuid=serializer.data.get('sodar_uuid') - ).first() - # Create timeline event - self.add_tl_create(irods_request) - # Add app alerts to owners/delegates - self.add_alerts_create(project) - - return Response( - { - 'detail': 'iRODS data request created', - 'request': irods_request.sodar_uuid, - }, - status=status.HTTP_200_OK, - ) - - -class IrodsRequestUpdateAPIView( - IrodsRequestModifyMixin, SODARAPIBaseProjectMixin, UpdateAPIView -): - """ - Update an iRODS data request for a project. - - **URL:** ``/samplesheets/api/irods/request/update/{IrodsDataRequest.sodar_uuid}`` - - **Methods:** ``PUT``, ``PATCH`` - """ - - http_method_names = ['put', 'patch'] - permission_classes = (IsAuthenticated,) - - def get_object(self): - """Override to allow POST requests""" - return IrodsDataRequest.objects.get( - sodar_uuid=self.kwargs.get('irodsdatarequest') - ) - - def put(self, request, *args, **kwargs): - """PUT request for updating an iRODS data request""" - obj = self.get_object() - if not self.has_irods_request_perms(request, obj): - raise PermissionDenied('Insufficient permissions') - - irods_request = IrodsDataRequest.objects.filter( - sodar_uuid=self.kwargs.get('irodsdatarequest') - ).first() - serializer = IrodsRequestSerializer( - irods_request, data=request.data, partial=True - ) - if serializer.is_valid(): - serializer.save() - else: - raise ValidationError('Invalid data: {}'.format(serializer.errors)) - - irods_request = IrodsDataRequest.objects.filter( - sodar_uuid=serializer.data.get('sodar_uuid') - ).first() - # Create timeline event - self.add_tl_update(irods_request) - - return Response( - { - 'detail': 'iRODS data request updated', - 'request': irods_request.sodar_uuid, - }, - status=status.HTTP_200_OK, - ) - - -class IrodsRequestDeleteAPIView( - IrodsRequestModifyMixin, SODARAPIBaseProjectMixin, APIView -): - """ - Delete an iRODS data request for a project. - - **URL:** ``/samplesheets/api/irods/request/delete/{IrodsDataRequest.sodar_uuid}`` - - **Methods:** ``DELETE`` - """ - - http_method_names = ['delete'] - # permission_required = 'samplesheets.edit_sheet' - permission_classes = (IsAuthenticated,) - - def get_object(self): - """Override to allow POST requests""" - return IrodsDataRequest.objects.get( - sodar_uuid=self.kwargs.get('irodsdatarequest') - ) - - def delete(self, request, *args, **kwargs): - """DELETE request for deleting an iRODS data request""" - obj = self.get_object() - if not self.has_irods_request_perms(request, obj): - raise PermissionDenied('Insufficient permissions') - - irods_request = IrodsDataRequest.objects.filter( - sodar_uuid=self.kwargs.get('irodsdatarequest') - ).first() - try: - irods_request.delete() - except Exception as ex: - raise APIException( - '{} {}'.format('Deleting ' + IRODS_EX_MSG + ':', ex) - ) - - # Add timeline event - self.add_tl_delete(irods_request) - # Handle project alerts - self.handle_alerts_deactivate(irods_request) - return Response( - {'detail': 'iRODS data request deleted'}, - status=status.HTTP_200_OK, - ) - - -class IrodsRequestAcceptAPIView( - IrodsRequestModifyMixin, SODARAPIBaseProjectMixin, APIView -): - """ - Accept an iRODS data request for a project. - - **URL:** ``/samplesheets/api/irods/request/accept/{IrodsDataRequest.sodar_uuid}`` - - **Methods:** ``POST`` - """ - - http_method_names = ['post'] - permission_required = 'samplesheets.manage_sheet' - - def post(self, request, *args, **kwargs): - """POST request for accepting an iRODS data request""" - timeline = get_backend_api('timeline_backend') - taskflow = get_backend_api('taskflow') - app_alerts = get_backend_api('appalerts_backend') - project = self.get_project() - - irods_request = IrodsDataRequest.objects.filter( - sodar_uuid=self.kwargs.get('irodsdatarequest') - ).first() - - try: - self.accept_request( - irods_request, - project, - self.request, - timeline=timeline, - taskflow=taskflow, - app_alerts=app_alerts, - ) - except Exception as ex: - raise ValidationError( - '{} {}'.format('Accepting ' + IRODS_EX_MSG + ':', ex) - ) - return Response( - {'detail': 'iRODS data request accepted'}, status=status.HTTP_200_OK - ) - - -class IrodsRequestRejectAPIView( - IrodsRequestModifyMixin, SODARAPIBaseProjectMixin, APIView -): - """ - Reject an iRODS data request for a project. - - **URL:** ``/samplesheets/api/irods/request/reject/{IrodsDataRequest.sodar_uuid}`` - - **Methods:** ``GET`` - """ - - http_method_names = ['get'] - permission_required = 'samplesheets.manage_sheet' - - def get(self, request, *args, **kwargs): - """POST request for rejecting an iRODS data request""" - timeline = get_backend_api('timeline_backend') - app_alerts = get_backend_api('appalerts_backend') - project = self.get_project() - irods_request = IrodsDataRequest.objects.filter( - sodar_uuid=self.kwargs.get('irodsdatarequest') - ).first() - - irods_request.status = 'REJECTED' - irods_request.save() - - try: - self.reject_request( - irods_request, - project, - self.request, - timeline=timeline, - app_alerts=app_alerts, - ) - except Exception as ex: - raise APIException( - '{} {}'.format('Rejecting ' + IRODS_EX_MSG + ':', ex) - ) - return Response( - {'detail': 'iRODS data request rejected'}, status=status.HTTP_200_OK - ) - - class SheetISAExportAPIView( SheetISAExportMixin, SODARAPIBaseProjectMixin, APIView ): @@ -465,7 +205,7 @@ class SheetImportAPIView(SheetImportMixin, SODARAPIBaseProjectMixin, APIView): **Methods:** ``POST`` - **Return:** + **Returns:** - ``detail``: Detail of project success (string) - ``sodar_warnings``: SODAR import issue warnings (list of srings, optional) @@ -570,6 +310,239 @@ def post(self, request, *args, **kwargs): return Response(ret_data, status=status.HTTP_200_OK) +class IrodsDataRequestRetrieveAPIView( + SODARAPIGenericProjectMixin, RetrieveAPIView +): + """ + Retrieve iRODS data request. + + **URL:** ``/samplesheets/api/irods/request/retrieve/{IrodsDataRequest.sodar_uuid}`` + + **Methods:** ``GET`` + + **Returns:** + + - ``project``: Project UUID (string) + - ``action``: Request action (string) + - ``path``: iRODS path to object or collection (string) + - ``target_path``: Target path (string, currently unused) + - ``user``: User initiating request (dictionary) + - ``status``: Request status (string) + - ``status_info``: Request status info (string) + - ``description``: Request description (string) + - ``date_created``: Request creation date (datetime) + - ``sodar_uuid`: Request UUID (string) + """ + + lookup_field = 'sodar_uuid' + lookup_url_kwarg = 'irodsdatarequest' + permission_required = 'samplesheets.edit_sheet' + serializer_class = IrodsDataRequestSerializer + + +class IrodsDataRequestListAPIView(SODARAPIBaseProjectMixin, ListAPIView): + """ + List iRODS data requests for a project. + + **URL:** ``/samplesheets/api/irods/requests/{Project.sodar_uuid}`` + + **Methods:** ``GET`` + + **Returns:** List of iRODS data requests + """ + + permission_required = 'samplesheets.edit_sheet' + serializer_class = IrodsDataRequestSerializer + + def get_queryset(self): + project = self.get_project() + requests = IrodsDataRequest.objects.filter(project=project) + # For superusers, owners and delegates, display requests from all users + if self.request.user.is_superuser or project.is_owner_or_delegate( + self.request.user + ): + return requests.filter( + status__in=[ + IRODS_REQUEST_STATUS_ACTIVE, + IRODS_REQUEST_STATUS_FAILED, + ] + ) + return requests.filter(user=self.request.user) + + +class IrodsDataRequestCreateAPIView( + IrodsDataRequestModifyMixin, SODARAPIGenericProjectMixin, CreateAPIView +): + """ + Create an iRODS delete request for a project. + + The request must point to a collection or data object within the sample data + repository of the project. The user must have at least the role of + contributor in the project. + + **URL:** ``/samplesheets/api/irods/request/create/{Project.sodar_uuid}`` + + **Methods:** ``POST`` + + **Parameters:** + + - ``path``: iRODS path to object or collection (string) + - ``description``: Request description (string, optional) + """ + + permission_required = 'samplesheets.edit_sheet' + serializer_class = IrodsDataRequestSerializer + + def perform_create(self, serializer): + serializer.save() + # Create timeline event + self.add_tl_create(serializer.instance) + # Add app alerts to owners/delegates + self.add_alerts_create(serializer.instance.project) + + +class IrodsDataRequestUpdateAPIView( + IrodsDataRequestModifyMixin, SODARAPIGenericProjectMixin, UpdateAPIView +): + """ + Update an iRODS data request for a project. + + **URL:** ``/samplesheets/api/irods/request/update/{IrodsDataRequest.sodar_uuid}`` + + **Methods:** ``PUT``, ``PATCH`` + + **Parameters:** + + - ``path``: iRODS path to object or collection (string) + - ``description``: Request description + """ + + # http_method_names = ['put', 'patch'] + lookup_url_kwarg = 'irodsdatarequest' + permission_classes = [IsAuthenticated] + serializer_class = IrodsDataRequestSerializer + + def perform_update(self, serializer): + """Override perform_update() to update IrodsDataRequest""" + if not self.has_irods_request_update_perms( + self.request, serializer.instance + ): + raise PermissionDenied + serializer.save() + # Add timeline event + self.add_tl_update(serializer.instance) + + +class IrodsDataRequestDestroyAPIView( + IrodsDataRequestModifyMixin, SODARAPIGenericProjectMixin, DestroyAPIView +): + """ + Delete an iRODS data request object. + + **URL:** ``/samplesheets/api/irods/request/delete/{IrodsDataRequest.sodar_uuid}`` + + **Methods:** ``DELETE`` + """ + + lookup_url_kwarg = 'irodsdatarequest' + permission_classes = [IsAuthenticated] + serializer_class = IrodsDataRequestSerializer + + def perform_destroy(self, instance): + """ + Override perform_destroy() to delete IrodsDataRequest + """ + if not self.has_irods_request_update_perms(self.request, instance): + raise PermissionDenied + instance.delete() + # Add timeline event + self.add_tl_delete(instance) + # Handle project alerts + self.handle_alerts_deactivate(instance) + + +class IrodsDataRequestAcceptAPIView( + IrodsDataRequestModifyMixin, SODARAPIBaseProjectMixin, APIView +): + """ + Accept an iRODS data request for a project. + + **URL:** ``/samplesheets/api/irods/request/accept/{IrodsDataRequest.sodar_uuid}`` + + **Methods:** ``POST`` + """ + + http_method_names = ['post'] + permission_required = 'samplesheets.manage_sheet' + + def post(self, request, *args, **kwargs): + """POST request for accepting an iRODS data request""" + timeline = get_backend_api('timeline_backend') + taskflow = get_backend_api('taskflow') + app_alerts = get_backend_api('appalerts_backend') + project = self.get_project() + irods_request = IrodsDataRequest.objects.filter( + sodar_uuid=self.kwargs.get('irodsdatarequest') + ).first() + + try: + self.accept_request( + irods_request, + project, + self.request, + timeline=timeline, + taskflow=taskflow, + app_alerts=app_alerts, + ) + except Exception as ex: + raise ValidationError( + '{} {}'.format('Accepting ' + IRODS_EX_MSG + ':', ex) + ) + return Response( + {'detail': 'iRODS data request accepted'}, status=status.HTTP_200_OK + ) + + +class IrodsDataRequestRejectAPIView( + IrodsDataRequestModifyMixin, SODARAPIBaseProjectMixin, APIView +): + """ + Reject an iRODS data request for a project. + + **URL:** ``/samplesheets/api/irods/request/reject/{IrodsDataRequest.sodar_uuid}`` + + **Methods:** ``POST`` + """ + + http_method_names = ['post'] + permission_required = 'samplesheets.manage_sheet' + + def post(self, request, *args, **kwargs): + """POST request for rejecting an iRODS data request""" + timeline = get_backend_api('timeline_backend') + app_alerts = get_backend_api('appalerts_backend') + project = self.get_project() + irods_request = IrodsDataRequest.objects.filter( + sodar_uuid=self.kwargs.get('irodsdatarequest') + ).first() + + try: + self.reject_request( + irods_request, + project, + self.request, + timeline=timeline, + app_alerts=app_alerts, + ) + except Exception as ex: + raise APIException( + '{} {}'.format('Rejecting ' + IRODS_EX_MSG + ':', ex) + ) + return Response( + {'detail': 'iRODS data request rejected'}, status=status.HTTP_200_OK + ) + + class SampleDataFileExistsAPIView(SODARAPIBaseMixin, APIView): """ Return status of data object existing in SODAR iRODS by MD5 checksum.