diff --git a/api/draft_registrations/permissions.py b/api/draft_registrations/permissions.py index 83bf44a612e..5232ee9d546 100644 --- a/api/draft_registrations/permissions.py +++ b/api/draft_registrations/permissions.py @@ -8,6 +8,8 @@ OSFUser, ) from api.nodes.permissions import ContributorDetailPermissions +from osf.utils.permissions import WRITE, ADMIN + class IsContributorOrAdminContributor(permissions.BasePermission): """ @@ -57,3 +59,34 @@ class DraftContributorDetailPermissions(ContributorDetailPermissions): def load_resource(self, context, view): return DraftRegistration.load(context['draft_id']) + + +class DraftRegistrationPermission(permissions.BasePermission): + """ + Check permissions for draft and node, Admin can create (POST) or edit (PATCH, PUT) to a DraftRegistration, but write + users can only edit them. Node permissions are inherited by the DraftRegistration when they are higher. + """ + acceptable_models = (DraftRegistration, AbstractNode) + + def has_object_permission(self, request, view, obj): + auth = get_user_auth(request) + + if not auth.user: + return False + + if request.method in permissions.SAFE_METHODS: + if isinstance(obj, DraftRegistration): + return obj.can_view(auth) + elif isinstance(obj, AbstractNode): + return obj.can_view(auth) + elif request.method == 'POST': # Only Admin can create a draft registration + if isinstance(obj, DraftRegistration): + return obj.is_contributor(auth.user) and obj.has_permission(auth.user, ADMIN) + elif isinstance(obj, AbstractNode): + return obj.has_permission(auth.user, ADMIN) + else: + if isinstance(obj, DraftRegistration): + return obj.is_contributor(auth.user) and obj.has_permission(auth.user, WRITE) + elif isinstance(obj, AbstractNode): + return obj.has_permission(auth.user, WRITE) + return False diff --git a/api/draft_registrations/views.py b/api/draft_registrations/views.py index 16443195492..30c583dd94a 100644 --- a/api/draft_registrations/views.py +++ b/api/draft_registrations/views.py @@ -6,7 +6,7 @@ from api.base.pagination import DraftRegistrationContributorPagination from api.draft_registrations.permissions import ( DraftContributorDetailPermissions, - IsContributorOrAdminContributor, + DraftRegistrationPermission, IsAdminContributor, ) from api.draft_registrations.serializers import ( @@ -50,9 +50,9 @@ def check_resource_permissions(self, resource): class DraftRegistrationList(NodeDraftRegistrationsList): permission_classes = ( - IsContributorOrAdminContributor, drf_permissions.IsAuthenticatedOrReadOnly, base_permissions.TokenHasScope, + DraftRegistrationPermission, ) view_category = 'draft_registrations' @@ -70,10 +70,9 @@ def get_queryset(self): # Returns DraftRegistrations for which a user is a contributor return user.draft_registrations_active - class DraftRegistrationDetail(NodeDraftRegistrationDetail, DraftRegistrationMixin): permission_classes = ( - ContributorOrPublic, + DraftRegistrationPermission, AdminDeletePermissions, drf_permissions.IsAuthenticatedOrReadOnly, base_permissions.TokenHasScope, diff --git a/api/nodes/views.py b/api/nodes/views.py index f43dbf17ad2..c19b7a2762a 100644 --- a/api/nodes/views.py +++ b/api/nodes/views.py @@ -5,7 +5,7 @@ from django.db.models import F, Max, Q, Subquery from django.utils import timezone from django.contrib.contenttypes.models import ContentType -from rest_framework import generics, permissions as drf_permissions +from rest_framework import generics, permissions as drf_permissions, exceptions from rest_framework.exceptions import PermissionDenied, ValidationError, NotFound, MethodNotAllowed, NotAuthenticated from rest_framework.response import Response from rest_framework.status import HTTP_202_ACCEPTED, HTTP_204_NO_CONTENT @@ -66,6 +66,7 @@ NodeCommentSerializer, ) from api.draft_registrations.serializers import DraftRegistrationSerializer, DraftRegistrationDetailSerializer +from api.draft_registrations.permissions import DraftRegistrationPermission from api.files.serializers import FileSerializer, OsfStorageFileSerializer from api.files import annotations as file_annotations from api.identifiers.serializers import NodeIdentifierSerializer @@ -75,7 +76,6 @@ from api.nodes.filters import NodesFilterMixin from api.nodes.permissions import ( IsAdmin, - IsAdminContributor, IsPublic, AdminOrPublic, WriteAdmin, @@ -626,7 +626,7 @@ class NodeDraftRegistrationsList(JSONAPIBaseView, generics.ListCreateAPIView, No Use DraftRegistrationsList endpoint instead. """ permission_classes = ( - IsAdminContributor, + DraftRegistrationPermission, drf_permissions.IsAuthenticatedOrReadOnly, base_permissions.TokenHasScope, ) @@ -649,8 +649,11 @@ def get_serializer_class(self): # overrides ListCreateAPIView def get_queryset(self): + user = self.request.user node = self.get_node() - return node.draft_registrations_active + if user.is_anonymous: + raise exceptions.NotAuthenticated() + return user.draft_registrations_active.filter(branched_from=node) class NodeDraftRegistrationDetail(JSONAPIBaseView, generics.RetrieveUpdateDestroyAPIView, DraftMixin): @@ -660,9 +663,9 @@ class NodeDraftRegistrationDetail(JSONAPIBaseView, generics.RetrieveUpdateDestro Use DraftRegistrationDetail endpoint instead. """ permission_classes = ( + DraftRegistrationPermission, drf_permissions.IsAuthenticatedOrReadOnly, base_permissions.TokenHasScope, - IsAdminContributor, ) parser_classes = (JSONAPIMultipleRelationshipsParser, JSONAPIMultipleRelationshipsParserForRegularJSON) diff --git a/api_tests/draft_registrations/views/test_draft_registration_detail.py b/api_tests/draft_registrations/views/test_draft_registration_detail.py index 18b00014f94..2106f87fb5a 100644 --- a/api_tests/draft_registrations/views/test_draft_registration_detail.py +++ b/api_tests/draft_registrations/views/test_draft_registration_detail.py @@ -2,10 +2,10 @@ from api.base.settings.defaults import API_BASE from api_tests.nodes.views.test_node_draft_registration_detail import ( - TestDraftRegistrationDetail, TestDraftRegistrationUpdate, TestDraftRegistrationPatch, TestDraftRegistrationDelete, + AbstractDraftRegistrationTestCase ) from osf.models import DraftNode, Node, NodeLicense, RegistrationSchema from osf.utils.permissions import ADMIN, READ, WRITE @@ -16,58 +16,34 @@ SubjectFactory, ProjectFactory, ) +from website.settings import API_DOMAIN @pytest.mark.django_db -class TestDraftRegistrationDetailEndpoint(TestDraftRegistrationDetail): +class TestDraftRegistrationDetailEndpoint(AbstractDraftRegistrationTestCase): @pytest.fixture() def url_draft_registrations(self, project_public, draft_registration): - return '/{}draft_registrations/{}/'.format( - API_BASE, draft_registration._id) - - # Overrides TestDraftRegistrationDetail - def test_admin_group_member_can_view(self, app, user, draft_registration, project_public, - schema, url_draft_registrations, group_mem): - - res = app.get(url_draft_registrations, auth=group_mem.auth, expect_errors=True) - assert res.status_code == 403 + return f'/{API_BASE}draft_registrations/{draft_registration._id}/' - def test_can_view_draft( - self, app, user_write_contrib, project_public, - user_read_contrib, user_non_contrib, - url_draft_registrations, group, group_mem): - - # test_read_only_contributor_can_view_draft - res = app.get( - url_draft_registrations, - auth=user_read_contrib.auth, - expect_errors=False) + def test_read_only_contributor_can_view_draft(self, app, user_read_contrib, url_draft_registrations): + res = app.get(url_draft_registrations, auth=user_read_contrib.auth) assert res.status_code == 200 - # test_read_write_contributor_can_view_draft - res = app.get( - url_draft_registrations, - auth=user_write_contrib.auth, - expect_errors=False) + def test_read_write_contributor_can_view_draft(self, app, user_write_contrib, url_draft_registrations): + res = app.get(url_draft_registrations, auth=user_write_contrib.auth) assert res.status_code == 200 - def test_cannot_view_draft( - self, app, project_public, - user_non_contrib, url_draft_registrations): - - # test_logged_in_non_contributor_cannot_view_draft - res = app.get( - url_draft_registrations, - auth=user_non_contrib.auth, - expect_errors=True) + def test_logged_in_non_contributor_cannot_view_draft(self, app, user_non_contrib, url_draft_registrations): + res = app.get(url_draft_registrations, auth=user_non_contrib.auth, expect_errors=True) assert res.status_code == 403 - # test_unauthenticated_user_cannot_view_draft + def test_unauthenticated_user_cannot_view_draft(self, app, url_draft_registrations): res = app.get(url_draft_registrations, expect_errors=True) assert res.status_code == 401 - def test_detail_view_returns_editable_fields(self, app, user, draft_registration, - url_draft_registrations, project_public): + def test_detail_view_returns_editable_fields( + self, app, user, draft_registration, url_draft_registrations, project_public + ): res = app.get(url_draft_registrations, auth=user.auth, expect_errors=True) attributes = res.json['data']['attributes'] @@ -77,7 +53,7 @@ def test_detail_view_returns_editable_fields(self, app, user, draft_registration assert attributes['category'] == project_public.category assert attributes['has_project'] - res.json['data']['links']['self'] == url_draft_registrations + assert res.json['data']['links']['self'] == f'{API_DOMAIN}{url_draft_registrations.lstrip("/")}' relationships = res.json['data']['relationships'] assert Node.load(relationships['branched_from']['data']['id']) == draft_registration.branched_from @@ -89,8 +65,7 @@ def test_detail_view_returns_editable_fields(self, app, user, draft_registration def test_detail_view_returns_editable_fields_no_specified_node(self, app, user): draft_registration = DraftRegistrationFactory(initiator=user, branched_from=None) - url = '/{}draft_registrations/{}/'.format( - API_BASE, draft_registration._id) + url = f'{API_DOMAIN}{API_BASE}draft_registrations/{draft_registration._id}/' res = app.get(url, auth=user.auth, expect_errors=True) attributes = res.json['data']['attributes'] @@ -101,7 +76,7 @@ def test_detail_view_returns_editable_fields_no_specified_node(self, app, user): assert attributes['node_license'] is None assert not attributes['has_project'] - res.json['data']['links']['self'] == url + assert res.json['data']['links']['self'] == url relationships = res.json['data']['relationships'] assert 'affiliated_institutions' in relationships @@ -112,16 +87,13 @@ def test_detail_view_returns_editable_fields_no_specified_node(self, app, user): res = app.get(draft_node_link, auth=user.auth) assert DraftNode.load(res.json['data']['id']) == draft_registration.branched_from - def test_draft_registration_perms_checked_on_draft_not_node(self, app, user, project_public, - draft_registration, url_draft_registrations): - - # Admin on node and draft + def test_admin_node_and_draft(self, app, user, project_public, draft_registration, url_draft_registrations): assert project_public.has_permission(user, ADMIN) is True assert draft_registration.has_permission(user, ADMIN) is True res = app.get(url_draft_registrations, auth=user.auth) assert res.status_code == 200 - # Admin on node but not draft + def test_admin_node_not_draft(self, app, user, project_public, draft_registration, url_draft_registrations): node_admin = AuthUserFactory() project_public.add_contributor(node_admin, ADMIN) assert project_public.has_permission(node_admin, ADMIN) is True @@ -129,7 +101,7 @@ def test_draft_registration_perms_checked_on_draft_not_node(self, app, user, pro res = app.get(url_draft_registrations, auth=node_admin.auth, expect_errors=True) assert res.status_code == 403 - # Admin on draft but not node + def test_admin_draft_not_node(self, app, user, project_public, draft_registration, url_draft_registrations): draft_admin = AuthUserFactory() draft_registration.add_contributor(draft_admin, ADMIN) assert project_public.has_permission(draft_admin, ADMIN) is False @@ -137,19 +109,66 @@ def test_draft_registration_perms_checked_on_draft_not_node(self, app, user, pro res = app.get(url_draft_registrations, auth=draft_admin.auth) assert res.status_code == 200 - # Overwrites TestDraftRegistrationDetail - def test_can_view_after_added( - self, app, schema, draft_registration, url_draft_registrations): - # Draft Registration permissions are no longer based on the branched from project + def test_write_node_and_draft(self, app, user, project_public, draft_registration, url_draft_registrations): + assert project_public.has_permission(user, WRITE) is True + assert draft_registration.has_permission(user, WRITE) is True + res = app.get(url_draft_registrations, auth=user.auth) + assert res.status_code == 200 + + def test_write_node_not_draft(self, app, user, project_public, draft_registration, url_draft_registrations): + node_admin = AuthUserFactory() + project_public.add_contributor(node_admin, WRITE) + assert project_public.has_permission(node_admin, WRITE) is True + assert draft_registration.has_permission(node_admin, WRITE) is False + res = app.get(url_draft_registrations, auth=node_admin.auth, expect_errors=True) + assert res.status_code == 403 + + def test_write_draft_not_node(self, app, user, project_public, draft_registration, url_draft_registrations): + draft_admin = AuthUserFactory() + draft_registration.add_contributor(draft_admin, WRITE) + assert project_public.has_permission(draft_admin, WRITE) is False + assert draft_registration.has_permission(draft_admin, WRITE) is True + res = app.get(url_draft_registrations, auth=draft_admin.auth) + assert res.status_code == 200 + + def test_read_node_and_draft(self, app, user, project_public, draft_registration, url_draft_registrations): + assert project_public.has_permission(user, READ) is True + assert draft_registration.has_permission(user, READ) is True + res = app.get(url_draft_registrations, auth=user.auth) + assert res.status_code == 200 + def test_read_node_not_draft(self, app, user, project_public, draft_registration, url_draft_registrations): + node_admin = AuthUserFactory() + project_public.add_contributor(node_admin, READ) + assert project_public.has_permission(node_admin, READ) is True + assert draft_registration.has_permission(node_admin, READ) is False + res = app.get(url_draft_registrations, auth=node_admin.auth, expect_errors=True) + assert res.status_code == 403 + + def test_read_draft_not_node(self, app, user, project_public, draft_registration, url_draft_registrations): + draft_admin = AuthUserFactory() + draft_registration.add_contributor(draft_admin, READ) + assert project_public.has_permission(draft_admin, READ) is False + assert draft_registration.has_permission(draft_admin, READ) is True + res = app.get(url_draft_registrations, auth=draft_admin.auth) + assert res.status_code == 200 + + def test_can_view_after_added(self, app, schema, draft_registration, url_draft_registrations): + """ + Ensure Draft Registration permissions are no longer based on the branched from project + """ user = AuthUserFactory() project = draft_registration.branched_from project.add_contributor(user, ADMIN) res = app.get(url_draft_registrations, auth=user.auth, expect_errors=True) assert res.status_code == 403 + draft_registration.add_contributor(user, ADMIN) + res = app.get(url_draft_registrations, auth=user.auth) + assert res.status_code == 200 - def test_current_permissions_field(self, app, user_read_contrib, - user_write_contrib, user, draft_registration, url_draft_registrations): + def test_current_permissions_field( + self, app, user_read_contrib, user_write_contrib, user, draft_registration, url_draft_registrations + ): res = app.get(url_draft_registrations, auth=user_read_contrib.auth, expect_errors=False) assert res.json['data']['attributes']['current_user_permissions'] == [READ] @@ -548,9 +567,8 @@ def test_write_contributor_can_update_draft( assert data['attributes']['registration_metadata'] == payload['data']['attributes']['registration_metadata'] -class TestDraftRegistrationDelete(TestDraftRegistrationDelete): +class TestDraftRegistrationDeleteDetail(TestDraftRegistrationDelete): @pytest.fixture() def url_draft_registrations(self, project_public, draft_registration): # Overrides TestDraftRegistrationDelete - return '/{}draft_registrations/{}/'.format( - API_BASE, draft_registration._id) + return f'/{API_BASE}draft_registrations/{draft_registration._id}/' diff --git a/api_tests/draft_registrations/views/test_draft_registration_list.py b/api_tests/draft_registrations/views/test_draft_registration_list.py index ba49520a174..1126af09ad3 100644 --- a/api_tests/draft_registrations/views/test_draft_registration_list.py +++ b/api_tests/draft_registrations/views/test_draft_registration_list.py @@ -2,20 +2,19 @@ import pytest from framework.auth.core import Auth -from api_tests.nodes.views.test_node_draft_registration_list import ( - TestDraftRegistrationList, - TestDraftRegistrationCreate -) +from django.utils import timezone +from api_tests.nodes.views.test_node_draft_registration_list import AbstractDraftRegistrationTestCase from api.base.settings.defaults import API_BASE from osf.migrations import ensure_invisible_and_inactive_schema -from osf.models import DraftRegistration, NodeLicense, RegistrationProvider +from osf.models import DraftRegistration, NodeLicense, RegistrationProvider, RegistrationSchema from osf_tests.factories import ( RegistrationFactory, CollectionFactory, ProjectFactory, AuthUserFactory, - InstitutionFactory + InstitutionFactory, + DraftRegistrationFactory, ) from osf.utils.permissions import READ, WRITE, ADMIN @@ -28,52 +27,143 @@ def invisible_and_inactive_schema(): @pytest.mark.django_db -class TestDraftRegistrationListNewWorkflow(TestDraftRegistrationList): +class TestDraftRegistrationListTopLevelEndpoint: + @pytest.fixture() - def url_draft_registrations(self, project_public): - return f'/{API_BASE}draft_registrations/?' + def url_draft_registrations(self): + return f'/{API_BASE}draft_registrations/' - # Overrides TestDraftRegistrationList - def test_osf_group_with_admin_permissions_can_view(self): - # DraftRegistration endpoints permissions are not calculated from the node - return + @pytest.fixture() + def user(self): + return AuthUserFactory() - # Overrides TestDraftRegistrationList - def test_cannot_view_draft_list( - self, app, user_write_contrib, project_public, - user_read_contrib, user_non_contrib, draft_registration, - url_draft_registrations, group, group_mem): + @pytest.fixture() + def user_admin_contrib(self): + return AuthUserFactory() + + @pytest.fixture() + def user_write_contrib(self): + return AuthUserFactory() + + @pytest.fixture() + def user_read_contrib(self): + return AuthUserFactory() + + @pytest.fixture() + def user_non_contrib(self): + return AuthUserFactory() + + @pytest.fixture() + def group_mem(self): + return AuthUserFactory() + + @pytest.fixture() + def project(self, user): + return ProjectFactory(creator=user) + + @pytest.fixture() + def schema(self): + return RegistrationSchema.objects.get(name='Open-Ended Registration', schema_version=3) - # test_read_only_contributor_can_view_draft_list + @pytest.fixture() + def draft_registration(self, user, project, schema, user_write_contrib, user_read_contrib, user_admin_contrib): + draft = DraftRegistrationFactory( + initiator=user, + registration_schema=schema, + branched_from=project + ) + draft.add_contributor(user_read_contrib, permissions=READ) + draft.add_contributor(user_write_contrib, permissions=WRITE) + draft.add_contributor(user_admin_contrib, permissions=ADMIN) + return draft + + def test_read_only_contributor_can_view_draft_list( + self, app, user_read_contrib, draft_registration, url_draft_registrations + ): res = app.get( url_draft_registrations, - auth=user_read_contrib.auth) + auth=user_read_contrib.auth + ) assert res.status_code == 200 assert len(res.json['data']) == 1 - # test_read_write_contributor_can_view_draft_list - res = app.get( - url_draft_registrations, - auth=user_write_contrib.auth) + def test_read_write_contributor_can_view_draft_list( + self, app, user_write_contrib, draft_registration, url_draft_registrations + ): + res = app.get(url_draft_registrations, auth=user_write_contrib.auth) assert res.status_code == 200 assert len(res.json['data']) == 1 - # test_logged_in_non_contributor_can_view_draft_list - res = app.get( - url_draft_registrations, - auth=user_non_contrib.auth, - expect_errors=True) + def test_admin_can_view_draft_list( + self, app, user_admin_contrib, draft_registration, schema, url_draft_registrations + ): + res = app.get(url_draft_registrations, auth=user_admin_contrib.auth) + + assert res.status_code == 200 + data = res.json['data'] + assert len(data) == 1 + + assert schema._id in data[0]['relationships']['registration_schema']['links']['related']['href'] + assert data[0]['id'] == draft_registration._id + assert data[0]['attributes']['registration_metadata'] == {} + + def test_logged_in_non_contributor_has_empty_list( + self, app, user_non_contrib, url_draft_registrations + ): + res = app.get(url_draft_registrations, auth=user_non_contrib.auth) assert res.status_code == 200 assert len(res.json['data']) == 0 - # test_unauthenticated_user_cannot_view_draft_list + def test_unauthenticated_user_cannot_view_draft_list(self, app, url_draft_registrations): res = app.get(url_draft_registrations, expect_errors=True) assert res.status_code == 401 + def test_logged_in_non_contributor_cannot_view_draft_list(self, app, user_non_contrib, url_draft_registrations): + res = app.get(url_draft_registrations, auth=user_non_contrib.auth) + assert res.status_code == 200 + assert len(res.json['data']) == 0 + + def test_deleted_draft_registration_does_not_show_up_in_draft_list(self, app, user, draft_registration, url_draft_registrations): + draft_registration.deleted = timezone.now() + draft_registration.save() + res = app.get(url_draft_registrations, auth=user.auth) + assert res.status_code == 200 + assert not res.json['data'] + + def test_draft_with_registered_node_does_not_show_up_in_draft_list( + self, app, user, project, draft_registration, url_draft_registrations + ): + registration = RegistrationFactory( + project=project, + draft_registration=draft_registration + ) + draft_registration.registered_node = registration + draft_registration.save() + res = app.get(url_draft_registrations, auth=user.auth) + assert res.status_code == 200 + assert not res.json['data'] + + def test_draft_with_deleted_registered_node_shows_up_in_draft_list( + self, app, user, project, draft_registration, schema, url_draft_registrations + ): + registration = RegistrationFactory(project=project, draft_registration=draft_registration) + draft_registration.registered_node = registration + draft_registration.save() + registration.deleted = timezone.now() + registration.save() + draft_registration.deleted = None + draft_registration.save() + res = app.get(url_draft_registrations, auth=user.auth) + assert res.status_code == 200 + data = res.json['data'] + assert len(data) == 1 + assert schema._id in data[0]['relationships']['registration_schema']['links']['related']['href'] + assert data[0]['id'] == draft_registration._id + assert data[0]['attributes']['registration_metadata'] == {} + -class TestDraftRegistrationCreateWithNode(TestDraftRegistrationCreate): +class TestDraftRegistrationCreateWithNode(AbstractDraftRegistrationTestCase): - # Overrides `url_draft_registrations` in `TestDraftRegistrationCreate` @pytest.fixture() def url_draft_registrations(self, project_public): return f'/{API_BASE}draft_registrations/?' @@ -125,29 +215,35 @@ def payload_alt(self, payload, provider_alt): new_payload['data']['relationships']['provider']['data']['id'] = provider_alt._id return new_payload - # Overrides TestDraftRegistrationList - def test_cannot_create_draft_errors(self, app, user, payload_alt, project_public, url_draft_registrations): - # test_cannot_create_draft_from_a_registration + def test_cannot_create_draft_from_a_registration(self, app, user, payload_alt, project_public, url_draft_registrations): registration = RegistrationFactory( - project=project_public, creator=user) + project=project_public, + creator=user + ) payload_alt['data']['relationships']['branched_from']['data']['id'] = registration._id res = app.post_json_api( - url_draft_registrations, payload_alt, auth=user.auth, - expect_errors=True) + url_draft_registrations, + payload_alt, + auth=user.auth, + expect_errors=True + ) assert res.status_code == 404 - # test_cannot_create_draft_from_deleted_node + def test_cannot_create_draft_from_deleted_node(self, app, user, payload_alt, project_public, url_draft_registrations): project = ProjectFactory(is_public=True, creator=user) project.is_deleted = True project.save() payload_alt['data']['relationships']['branched_from']['data']['id'] = project._id res = app.post_json_api( - url_draft_registrations, payload_alt, - auth=user.auth, expect_errors=True) + url_draft_registrations, + payload_alt, + auth=user.auth, + expect_errors=True + ) assert res.status_code == 410 assert res.json['errors'][0]['detail'] == 'The requested node is no longer available.' - # test_cannot_create_draft_from_collection + def test_cannot_create_draft_from_collection(self, app, user, payload_alt, project_public, url_draft_registrations): collection = CollectionFactory(creator=user) payload_alt['data']['relationships']['branched_from']['data']['id'] = collection._id res = app.post_json_api( @@ -155,8 +251,9 @@ def test_cannot_create_draft_errors(self, app, user, payload_alt, project_public expect_errors=True) assert res.status_code == 404 - def test_draft_registration_attributes_copied_from_node(self, app, project_public, - url_draft_registrations, user, payload_alt): + def test_draft_registration_attributes_copied_from_node( + self, app, project_public, url_draft_registrations, user, payload_alt + ): write_contrib = AuthUserFactory() read_contrib = AuthUserFactory() @@ -175,8 +272,9 @@ def test_draft_registration_attributes_copied_from_node(self, app, project_publi project_public.add_contributor(write_contrib, WRITE) project_public.add_contributor(read_contrib, READ) + # Only an admin can create a DraftRegistration res = app.post_json_api(url_draft_registrations, payload_alt, auth=write_contrib.auth, expect_errors=True) - assert res.status_code == 201 + assert res.status_code == 403 res = app.post_json_api(url_draft_registrations, payload_alt, auth=read_contrib.auth, expect_errors=True) assert res.status_code == 403 @@ -196,67 +294,55 @@ def test_draft_registration_attributes_copied_from_node(self, app, project_publi assert 'subjects' in relationships assert 'contributors' in relationships - def test_cannot_create_draft( - self, app, user_write_contrib, - user_read_contrib, user_non_contrib, - project_public, payload_alt, group, - url_draft_registrations, group_mem): - - # test_write_only_contributor_cannot_create_draft + def test_write_only_contributor_cannot_create_draft( + self, app, user_write_contrib, project_public, payload_alt, url_draft_registrations + ): assert user_write_contrib in project_public.contributors.all() res = app.post_json_api( url_draft_registrations, payload_alt, auth=user_write_contrib.auth, - expect_errors=True) - assert res.status_code == 201 + expect_errors=True + ) + assert res.status_code == 403 - # test_read_only_contributor_cannot_create_draft + def test_read_only_contributor_cannot_create_draft( + self, app, user_write_contrib, user_read_contrib, project_public, payload_alt, url_draft_registrations + ): assert user_read_contrib in project_public.contributors.all() res = app.post_json_api( url_draft_registrations, payload_alt, auth=user_read_contrib.auth, - expect_errors=True) + expect_errors=True + ) assert res.status_code == 403 - # test_non_authenticated_user_cannot_create_draft + def test_non_authenticated_user_cannot_create_draft( + self, app, user_write_contrib, payload_alt, group, url_draft_registrations + ): res = app.post_json_api( url_draft_registrations, - payload_alt, expect_errors=True) + payload_alt, + expect_errors=True + ) assert res.status_code == 401 - # test_logged_in_non_contributor_cannot_create_draft + def test_logged_in_non_contributor_cannot_create_draft( + self, app, user_non_contrib, payload_alt, url_draft_registrations + ): + res = app.post_json_api( url_draft_registrations, payload_alt, auth=user_non_contrib.auth, - expect_errors=True) + expect_errors=True + ) assert res.status_code == 403 - # test_group_admin_cannot_create_draft - res = app.post_json_api( - url_draft_registrations, - payload_alt, - auth=group_mem.auth, - expect_errors=True) - assert res.status_code == 201 - - # test_group_write_contrib_cannot_create_draft - project_public.remove_osf_group(group) - project_public.add_osf_group(group, WRITE) - res = app.post_json_api( - url_draft_registrations, - payload_alt, - auth=group_mem.auth, - expect_errors=True) - assert res.status_code == 201 - - def test_create_project_based_draft_does_not_email_initiator( - self, app, user, url_draft_registrations, payload): - post_url = url_draft_registrations + 'embed=branched_from&embed=initiator' + def test_create_project_based_draft_does_not_email_initiator(self, app, user, url_draft_registrations, payload): with mock.patch.object(mails, 'send_mail') as mock_send_mail: - app.post_json_api(post_url, payload, auth=user.auth) + app.post_json_api(f'{url_draft_registrations}?embed=branched_from&embed=initiator', payload, auth=user.auth) assert not mock_send_mail.called @@ -320,7 +406,7 @@ def test_affiliated_institutions_are_copied_from_user(self, app, user, url_draft assert list(draft_registration.affiliated_institutions.all()) == list(user.get_affiliated_institutions()) -class TestDraftRegistrationCreateWithoutNode(TestDraftRegistrationCreate): +class TestDraftRegistrationCreateWithoutNode(AbstractDraftRegistrationTestCase): @pytest.fixture() def url_draft_registrations(self): return f'/{API_BASE}draft_registrations/?' @@ -346,13 +432,14 @@ def test_admin_can_create_draft( assert draft.creator == user assert draft.has_permission(user, ADMIN) is True - def test_create_no_project_draft_emails_initiator( - self, app, user, url_draft_registrations, payload): - post_url = url_draft_registrations + 'embed=branched_from&embed=initiator' - + def test_create_no_project_draft_emails_initiator(self, app, user, url_draft_registrations, payload): # Intercepting the send_mail call from website.project.views.contributor.notify_added_contributor with mock.patch.object(mails, 'send_mail') as mock_send_mail: - resp = app.post_json_api(post_url, payload, auth=user.auth) + resp = app.post_json_api( + f'{url_draft_registrations}?embed=branched_from&embed=initiator', + payload, + auth=user.auth + ) assert mock_send_mail.called # Python 3.6 does not support mock.call_args.args/kwargs @@ -363,7 +450,9 @@ def test_create_no_project_draft_emails_initiator( assert mock_send_kwargs['user'] == user assert mock_send_kwargs['node'] == DraftRegistration.load(resp.json['data']['id']) - def test_create_draft_with_provider(self, app, user, url_draft_registrations, non_default_provider, payload_with_non_default_provider): + def test_create_draft_with_provider( + self, app, user, url_draft_registrations, non_default_provider, payload_with_non_default_provider + ): res = app.post_json_api(url_draft_registrations, payload_with_non_default_provider, auth=user.auth) assert res.status_code == 201 data = res.json['data'] @@ -373,14 +462,9 @@ def test_create_draft_with_provider(self, app, user, url_draft_registrations, no draft = DraftRegistration.load(data['id']) assert draft.provider == non_default_provider - # Overrides TestDraftRegistrationList - def test_cannot_create_draft( - self, app, user_write_contrib, - user_read_contrib, user_non_contrib, - project_public, payload, group, - url_draft_registrations, group_mem): - - # test_write_contrib (no node supplied, so any logged in user can create) + def test_write_contrib(self, app, user, project_public, payload, url_draft_registrations, user_write_contrib): + """(no node supplied, so any logged in user can create) + """ assert user_write_contrib in project_public.contributors.all() res = app.post_json_api( url_draft_registrations, @@ -388,7 +472,9 @@ def test_cannot_create_draft( auth=user_write_contrib.auth) assert res.status_code == 201 - # test_read_only (no node supplied, so any logged in user can create) + def test_read_only(self, app, user, url_draft_registrations, user_read_contrib, project_public, payload): + '''(no node supplied, so any logged in user can create) + ''' assert user_read_contrib in project_public.contributors.all() res = app.post_json_api( url_draft_registrations, @@ -396,24 +482,24 @@ def test_cannot_create_draft( auth=user_read_contrib.auth) assert res.status_code == 201 - # test_non_authenticated_user_cannot_create_draft + def test_non_authenticated_user_cannot_create_draft(self, app, user, url_draft_registrations, payload): res = app.post_json_api( url_draft_registrations, - payload, expect_errors=True) + payload, + expect_errors=True + ) assert res.status_code == 401 - # test_logged_in_non_contributor (no node supplied, so any logged in user can create) + def test_logged_in_non_contributor(self, app, user, url_draft_registrations, user_non_contrib, payload): + '''(no node supplied, so any logged in user can create) + ''' res = app.post_json_api( url_draft_registrations, payload, - auth=user_non_contrib.auth) + auth=user_non_contrib.auth + ) assert res.status_code == 201 - # Overrides TestDraftRegistrationList - def test_cannot_create_draft_errors(self): - # The original test assumes a node is being passed in - return - def test_draft_registration_attributes_not_copied_from_node(self, app, project_public, url_draft_registrations, user, payload): diff --git a/api_tests/nodes/views/test_node_draft_registration_detail.py b/api_tests/nodes/views/test_node_draft_registration_detail.py index a4acf62be51..33e0a25b21a 100644 --- a/api_tests/nodes/views/test_node_draft_registration_detail.py +++ b/api_tests/nodes/views/test_node_draft_registration_detail.py @@ -9,41 +9,21 @@ AuthUserFactory, RegistrationFactory, ) -from osf.utils.permissions import WRITE, READ, ADMIN -from api_tests.nodes.views.test_node_draft_registration_list import DraftRegistrationTestCase +from osf.utils.permissions import ADMIN +from api_tests.nodes.views.test_node_draft_registration_list import AbstractDraftRegistrationTestCase +from framework.auth.core import Auth SCHEMA_VERSION = 2 @pytest.mark.django_db -class TestDraftRegistrationDetail(DraftRegistrationTestCase): - - @pytest.fixture() - def schema(self): - return RegistrationSchema.objects.get( - name='OSF-Standard Pre-Data Collection Registration', - schema_version=SCHEMA_VERSION) - - @pytest.fixture() - def draft_registration(self, user, project_public, schema): - return DraftRegistrationFactory( - initiator=user, - registration_schema=schema, - branched_from=project_public - ) - - @pytest.fixture() - def project_other(self, user): - return ProjectFactory(creator=user) +class TestDraftRegistrationDetail(AbstractDraftRegistrationTestCase): @pytest.fixture() def url_draft_registrations(self, project_public, draft_registration): - return '/{}nodes/{}/draft_registrations/{}/?{}'.format( - API_BASE, project_public._id, draft_registration._id, 'version=2.19') + return f'/{API_BASE}nodes/{project_public._id}/draft_registrations/{draft_registration._id}/?version=2.19' - def test_admin_can_view_draft( - self, app, user, draft_registration, project_public, - schema, url_draft_registrations, group_mem): + def test_node_admin_can_view_draft(self, app, user, draft_registration, schema, url_draft_registrations): res = app.get(url_draft_registrations, auth=user.auth) assert res.status_code == 200 data = res.json['data'] @@ -51,75 +31,55 @@ def test_admin_can_view_draft( assert data['id'] == draft_registration._id assert data['attributes']['registration_metadata'] == {} - def test_admin_group_member_can_view( - self, app, user, draft_registration, project_public, - schema, url_draft_registrations, group_mem): - - res = app.get(url_draft_registrations, auth=group_mem.auth) + def test_read_contributor_can_view_draft(self, app, user_read_contrib, url_draft_registrations): + """ + Note this is the Node permissions not DraftRegistration permission + """ + res = app.get(url_draft_registrations, auth=user_read_contrib.auth) assert res.status_code == 200 - def test_cannot_view_draft( - self, app, user_write_contrib, project_public, - user_read_contrib, user_non_contrib, - url_draft_registrations, group, group_mem): - - # test_read_only_contributor_cannot_view_draft - res = app.get( - url_draft_registrations, - auth=user_read_contrib.auth, - expect_errors=True) - assert res.status_code == 403 - - # test_read_write_contributor_cannot_view_draft - res = app.get( - url_draft_registrations, - auth=user_write_contrib.auth, - expect_errors=True) - assert res.status_code == 403 + def test_write_contributor_can_view_draft(self, app, user_write_contrib, url_draft_registrations): + """ + Note this is the Node permissions not DraftRegistration permission + """ + res = app.get(url_draft_registrations, auth=user_write_contrib.auth) + assert res.status_code == 200 - # test_logged_in_non_contributor_cannot_view_draft - res = app.get( - url_draft_registrations, - auth=user_non_contrib.auth, - expect_errors=True) - assert res.status_code == 403 + def test_logged_in_non_contributor_cannot_view_draft(self, app, user_non_contrib, url_draft_registrations): + res = app.get(url_draft_registrations, auth=user_non_contrib.auth, expect_errors=True) + assert res.status_code == 200 - # test_unauthenticated_user_cannot_view_draft + def test_unauthenticated_user_cannot_view_draft(self, app, url_draft_registrations): res = app.get(url_draft_registrations, expect_errors=True) assert res.status_code == 401 - # test_group_mem_read_cannot_view - project_public.remove_osf_group(group) - project_public.add_osf_group(group, READ) - res = app.get(url_draft_registrations, auth=group_mem.auth, expect_errors=True) - assert res.status_code == 403 - - def test_cannot_view_deleted_draft( - self, app, user, url_draft_registrations): + def test_cannot_view_deleted_draft(self, app, user, url_draft_registrations): res = app.delete_json_api(url_draft_registrations, auth=user.auth) assert res.status_code == 204 res = app.get( url_draft_registrations, auth=user.auth, - expect_errors=True) + expect_errors=True + ) assert res.status_code == 410 - def test_draft_must_be_branched_from_node_in_kwargs( - self, app, user, project_other, draft_registration): - url = '/{}nodes/{}/draft_registrations/{}/'.format( - API_BASE, project_other._id, draft_registration._id) - res = app.get(url, auth=user.auth, expect_errors=True) + def test_draft_must_be_branched_from_node_in_kwargs(self, app, user, project_other, draft_registration): + res = app.get( + f'/{API_BASE}nodes/{project_other._id}/draft_registrations/{draft_registration._id}/', + auth=user.auth, + expect_errors=True + ) assert res.status_code == 400 errors = res.json['errors'][0] assert errors['detail'] == 'This draft registration is not created from the given node.' def test_draft_registration_serializer_usage(self, app, user, project_public, draft_registration): # Tests the usage of DraftRegistrationDetailSerializer for version 2.20 - url_draft_registrations = '/{}nodes/{}/draft_registrations/{}/?{}'.format( - API_BASE, project_public._id, draft_registration._id, 'version=2.20') - - res = app.get(url_draft_registrations, auth=user.auth) + res = app.get( + f'/{API_BASE}nodes/{project_public._id}/draft_registrations/{draft_registration._id}/?version=2.20', + auth=user.auth + ) assert res.status_code == 200 data = res.json['data'] @@ -128,8 +88,7 @@ def test_draft_registration_serializer_usage(self, app, user, project_public, dr assert data['attributes']['description'] assert data['relationships']['affiliated_institutions'] - def test_can_view_after_added( - self, app, schema, draft_registration, url_draft_registrations): + def test_can_view_after_added(self, app, schema, draft_registration, url_draft_registrations): user = AuthUserFactory() project = draft_registration.branched_from project.add_contributor(user, ADMIN) @@ -138,21 +97,11 @@ def test_can_view_after_added( @pytest.mark.django_db -class TestDraftRegistrationUpdate(DraftRegistrationTestCase): - - @pytest.fixture() - def schema(self): - return RegistrationSchema.objects.get( - name='OSF-Standard Pre-Data Collection Registration', - schema_version=SCHEMA_VERSION) +class TestDraftRegistrationUpdate(AbstractDraftRegistrationTestCase): @pytest.fixture() - def draft_registration(self, user, project_public, schema): - return DraftRegistrationFactory( - initiator=user, - registration_schema=schema, - branched_from=project_public - ) + def url_draft_registrations(self, project_public, draft_registration): + return f'/{API_BASE}nodes/{project_public._id}/draft_registrations/{draft_registration._id}/?version=2.19' @pytest.fixture() def reg_schema(self): @@ -170,20 +119,13 @@ def draft_registration_prereg(self, user, project_public, reg_schema): ) @pytest.fixture() - def metadata_registration( - self, metadata, - draft_registration_prereg): + def metadata_registration(self, metadata, draft_registration_prereg): return metadata(draft_registration_prereg) @pytest.fixture() def project_other(self, user): return ProjectFactory(creator=user) - @pytest.fixture() - def url_draft_registrations(self, project_public, draft_registration): - return '/{}nodes/{}/draft_registrations/{}/?{}'.format( - API_BASE, project_public._id, draft_registration._id, 'version=2.19') - @pytest.fixture() def payload(self, draft_registration): return { @@ -267,12 +209,10 @@ def test_draft_must_be_branched_from_node( errors = res.json['errors'][0] assert errors['detail'] == 'This draft registration is not created from the given node.' - def test_cannot_update_draft( - self, app, user_write_contrib, project_public, - user_read_contrib, user_non_contrib, - payload, url_draft_registrations, group, group_mem): + def test_read_only_contributor_cannot_update_draft( + self, app, user_read_contrib, payload, url_draft_registrations, + ): - # test_read_only_contributor_cannot_update_draft res = app.put_json_api( url_draft_registrations, payload, @@ -280,37 +220,20 @@ def test_cannot_update_draft( expect_errors=True) assert res.status_code == 403 - # test_logged_in_non_contributor_cannot_update_draft + def test_logged_in_non_contributor_cannot_update_draft( + self, app, user_non_contrib, payload, url_draft_registrations, + ): res = app.put_json_api( url_draft_registrations, payload, auth=user_non_contrib.auth, - expect_errors=True) - assert res.status_code == 403 - - # test_unauthenticated_user_cannot_update_draft - res = app.put_json_api( - url_draft_registrations, - payload, expect_errors=True) - assert res.status_code == 401 - - # test_osf_group_member_admin_cannot_update_draft - res = app.put_json_api( - url_draft_registrations, - payload, expect_errors=True, - auth=group_mem.auth + expect_errors=True ) assert res.status_code == 403 - # test_osf_group_member_write_cannot_update_draft - project_public.remove_osf_group(group) - project_public.add_osf_group(group, WRITE) - res = app.put_json_api( - url_draft_registrations, - payload, expect_errors=True, - auth=group_mem.auth - ) - assert res.status_code == 403 + def test_unauthenticated_user_cannot_update_draft(self, app, payload, url_draft_registrations): + res = app.put_json_api(url_draft_registrations, payload, expect_errors=True) + assert res.status_code == 401 def test_registration_metadata_does_not_need_to_be_supplied( self, app, user, payload, url_draft_registrations): @@ -528,21 +451,7 @@ def test_multiple_choice_question_value_in_registration_responses_must_match_val @pytest.mark.django_db -class TestDraftRegistrationPatch(DraftRegistrationTestCase): - - @pytest.fixture() - def schema(self): - return RegistrationSchema.objects.get( - name='OSF-Standard Pre-Data Collection Registration', - schema_version=SCHEMA_VERSION) - - @pytest.fixture() - def draft_registration(self, user, project_public, schema): - return DraftRegistrationFactory( - initiator=user, - registration_schema=schema, - branched_from=project_public - ) +class TestDraftRegistrationPatch(AbstractDraftRegistrationTestCase): @pytest.fixture() def reg_schema(self): @@ -562,10 +471,6 @@ def draft_registration_prereg(self, user, project_public, reg_schema): def metadata_registration(self, metadata, draft_registration_prereg): return metadata(draft_registration_prereg) - @pytest.fixture() - def project_other(self, user): - return ProjectFactory(creator=user) - @pytest.fixture() def url_draft_registrations(self, project_public, draft_registration): return '/{}nodes/{}/draft_registrations/{}/?{}'.format( @@ -604,111 +509,69 @@ def test_admin_can_update_draft( assert schema._id in data['relationships']['registration_schema']['links']['related']['href'] assert data['attributes']['registration_metadata'] == payload['data']['attributes']['registration_metadata'] - def test_cannot_update_draft( - self, app, user_write_contrib, - user_read_contrib, user_non_contrib, - payload, url_draft_registrations, group_mem): - - # test_read_only_contributor_cannot_update_draft + def test_read_only_contributor_cannot_update_draft( + self, app, user_read_contrib, payload, url_draft_registrations + ): res = app.patch_json_api( url_draft_registrations, payload, auth=user_read_contrib.auth, - expect_errors=True) + expect_errors=True + ) assert res.status_code == 403 - # test_logged_in_non_contributor_cannot_update_draft + def test_logged_in_non_contributor_cannot_update_draft( + self, app, user_non_contrib, payload, url_draft_registrations + ): res = app.patch_json_api( url_draft_registrations, payload, auth=user_non_contrib.auth, - expect_errors=True) + expect_errors=True + ) assert res.status_code == 403 - # test_unauthenticated_user_cannot_update_draft + def test_unauthenticated_user_cannot_update_draft( + self, app, user_non_contrib, payload, url_draft_registrations + ): res = app.patch_json_api( url_draft_registrations, - payload, expect_errors=True) + payload, + expect_errors=True + ) assert res.status_code == 401 - # group admin cannot update draft - res = app.patch_json_api( - url_draft_registrations, - payload, - auth=group_mem.auth, - expect_errors=True) - assert res.status_code == 403 @pytest.mark.django_db -class TestDraftRegistrationDelete(DraftRegistrationTestCase): - - @pytest.fixture() - def schema(self): - return RegistrationSchema.objects.get( - name='OSF-Standard Pre-Data Collection Registration', - schema_version=SCHEMA_VERSION) - - @pytest.fixture() - def draft_registration(self, user, project_public, schema): - return DraftRegistrationFactory( - initiator=user, - registration_schema=schema, - branched_from=project_public - ) - - @pytest.fixture() - def project_other(self, user): - return ProjectFactory(creator=user) +class TestDraftRegistrationDelete(AbstractDraftRegistrationTestCase): @pytest.fixture() def url_draft_registrations(self, project_public, draft_registration): - return '/{}nodes/{}/draft_registrations/{}/?{}'.format( - API_BASE, project_public._id, draft_registration._id, 'version=2.19') + return f'/{API_BASE}nodes/{project_public._id}/draft_registrations/{draft_registration._id}/?version=2.19' def test_admin_can_delete_draft(self, app, user, url_draft_registrations, project_public): res = app.delete_json_api(url_draft_registrations, auth=user.auth) assert res.status_code == 204 - def test_cannot_delete_draft( - self, app, user_write_contrib, project_public, - user_read_contrib, user_non_contrib, - url_draft_registrations, group, group_mem): - - # test_read_only_contributor_cannot_delete_draft - res = app.delete_json_api( - url_draft_registrations, - auth=user_read_contrib.auth, - expect_errors=True) + def test_read_only_contributor_cannot_delete_draft(self, app, user_read_contrib, url_draft_registrations): + res = app.delete_json_api(url_draft_registrations, auth=user_read_contrib.auth, expect_errors=True) assert res.status_code == 403 - # test_read_write_contributor_cannot_delete_draft - res = app.delete_json_api( - url_draft_registrations, - auth=user_write_contrib.auth, - expect_errors=True) + def test_read_write_draft_contributor_cannot_delete_draft( + self, app, user_write_contrib, url_draft_registrations, project_public + ): + project_public.remove_contributor(user_write_contrib, Auth(user_write_contrib)) # Draft contributor only + res = app.delete_json_api(url_draft_registrations, auth=user_write_contrib.auth, expect_errors=True) assert res.status_code == 403 - # test_logged_in_non_contributor_cannot_delete_draft - res = app.delete_json_api( - url_draft_registrations, - auth=user_non_contrib.auth, - expect_errors=True) + def test_logged_in_non_contributor_cannot_delete_draft(self, app, user_non_contrib, url_draft_registrations): + res = app.delete_json_api(url_draft_registrations, auth=user_non_contrib.auth, expect_errors=True) assert res.status_code == 403 - # test_unauthenticated_user_cannot_delete_draft + def test_unauthenticated_user_cannot_delete_draft(self, app, url_draft_registrations): res = app.delete_json_api(url_draft_registrations, expect_errors=True) assert res.status_code == 401 - # test_group_member_admin_cannot_delete_draft - res = app.delete_json_api(url_draft_registrations, expect_errors=True, auth=group_mem.auth) - assert res.status_code == 403 - - # test_group_member_write_cannot_delete_draft - project_public.remove_osf_group(group) - project_public.add_osf_group(group, WRITE) - res = app.delete_json_api(url_draft_registrations, expect_errors=True, auth=group_mem.auth) - assert res.status_code == 403 - def test_draft_that_has_been_registered_cannot_be_deleted( self, app, user, project_public, draft_registration, url_draft_registrations): reg = RegistrationFactory(project=project_public) diff --git a/api_tests/nodes/views/test_node_draft_registration_list.py b/api_tests/nodes/views/test_node_draft_registration_list.py index 3a3c06f6c18..7668e5e9516 100644 --- a/api_tests/nodes/views/test_node_draft_registration_list.py +++ b/api_tests/nodes/views/test_node_draft_registration_list.py @@ -28,12 +28,16 @@ def invisible_and_inactive_schema(): @pytest.mark.django_db -class DraftRegistrationTestCase: +class AbstractDraftRegistrationTestCase: @pytest.fixture() def user(self): return AuthUserFactory() + @pytest.fixture() + def user_admin_contrib(self, user): + return AuthUserFactory() + @pytest.fixture() def user_write_contrib(self): return AuthUserFactory() @@ -55,7 +59,7 @@ def group(self, group_mem): return OSFGroupFactory(creator=group_mem) @pytest.fixture() - def project_public(self, user, user_write_contrib, user_read_contrib, group, group_mem): + def project_public(self, user, user_admin_contrib, user_write_contrib, user_read_contrib, group, group_mem): project_public = ProjectFactory(is_public=True, creator=user) project_public.add_contributor( user_write_contrib, @@ -63,11 +67,22 @@ def project_public(self, user, user_write_contrib, user_read_contrib, group, gro project_public.add_contributor( user_read_contrib, permissions=permissions.READ) + project_public.add_contributor( + user_admin_contrib, + permissions=permissions.ADMIN) project_public.save() project_public.add_osf_group(group, permissions.ADMIN) project_public.add_tag('hello', Auth(user), save=True) return project_public + @pytest.fixture() + def draft_registration(self, user, project_public, schema): + return DraftRegistrationFactory( + initiator=user, + registration_schema=schema, + branched_from=project_public + ) + @pytest.fixture() def metadata(self): def metadata(draft): @@ -90,15 +105,96 @@ def metadata(draft): return test_metadata return metadata + @pytest.fixture() + def schema(self): + return RegistrationSchema.objects.get( + name='OSF-Standard Pre-Data Collection Registration', + schema_version=SCHEMA_VERSION + ) + + @pytest.fixture() + def metaschema_open_ended(self): + return RegistrationSchema.objects.get( + name='Open-Ended Registration', + schema_version=OPEN_ENDED_SCHEMA_VERSION + ) + + @pytest.fixture() + def project_other(self, user): + return ProjectFactory(creator=user) + + @pytest.fixture() + def payload(self, metaschema_open_ended, provider): + return { + 'data': { + 'type': 'draft_registrations', + 'attributes': {}, + 'relationships': { + 'registration_schema': { + 'data': { + 'type': 'registration_schema', + 'id': metaschema_open_ended._id + } + }, + 'provider': { + 'data': { + 'type': 'registration-providers', + 'id': provider._id, + } + } + } + } + } + + @pytest.fixture() + def provider(self): + return RegistrationProvider.get_default() + + @pytest.fixture() + def non_default_provider(self, metaschema_open_ended): + non_default_provider = RegistrationProviderFactory() + non_default_provider.schemas.add(metaschema_open_ended) + non_default_provider.save() + return non_default_provider + + @pytest.fixture() + def payload_with_non_default_provider(self, metaschema_open_ended, non_default_provider): + return { + 'data': { + 'type': 'draft_registrations', + 'attributes': {}, + 'relationships': { + 'registration_schema': { + 'data': { + 'type': 'registration_schema', + 'id': metaschema_open_ended._id + } + }, + 'provider': { + 'data': { + 'type': 'registration-providers', + 'id': non_default_provider._id, + } + } + } + } + } + @pytest.mark.django_db -class TestDraftRegistrationList(DraftRegistrationTestCase): +class TestDraftRegistrationList(AbstractDraftRegistrationTestCase): + + @pytest.fixture() + def url_draft_registrations(self, project_public): + # Specifies version to test functionality when using DraftRegistrationLegacySerializer + return f'/{API_BASE}nodes/{project_public._id}/draft_registrations/?version=2.19' @pytest.fixture() def schema(self): return RegistrationSchema.objects.get( name='Open-Ended Registration', - schema_version=OPEN_ENDED_SCHEMA_VERSION) + schema_version=OPEN_ENDED_SCHEMA_VERSION + ) @pytest.fixture() def draft_registration(self, user, project_public, schema): @@ -108,15 +204,9 @@ def draft_registration(self, user, project_public, schema): branched_from=project_public ) - @pytest.fixture() - def url_draft_registrations(self, project_public): - # Specifies version to test functionality when using DraftRegistrationLegacySerializer - return '/{}nodes/{}/draft_registrations/?{}'.format( - API_BASE, project_public._id, 'version=2.19') - - def test_admin_can_view_draft_list( - self, app, user, draft_registration, project_public, - schema, url_draft_registrations): + def test_draft_admin_can_view_draft_list( + self, app, user, draft_registration, project_public, schema, url_draft_registrations + ): res = app.get(url_draft_registrations, auth=user.auth) assert res.status_code == 200 data = res.json['data'] @@ -126,54 +216,27 @@ def test_admin_can_view_draft_list( assert data[0]['id'] == draft_registration._id assert data[0]['attributes']['registration_metadata'] == {} - def test_osf_group_with_admin_permissions_can_view( - self, app, user, draft_registration, project_public, - schema, url_draft_registrations): - group_mem = AuthUserFactory() - group = OSFGroupFactory(creator=group_mem) - project_public.add_osf_group(group, permissions.ADMIN) - res = app.get(url_draft_registrations, auth=group_mem.auth, expect_errors=True) + def test_read_only_contributor_can_view_draft_list(self, app, user_read_contrib, url_draft_registrations): + res = app.get(url_draft_registrations, auth=user_read_contrib.auth) assert res.status_code == 200 - data = res.json['data'] - assert len(data) == 1 - assert schema._id in data[0]['relationships']['registration_schema']['links']['related']['href'] - - def test_cannot_view_draft_list( - self, app, user_write_contrib, project_public, - user_read_contrib, user_non_contrib, - url_draft_registrations, group, group_mem): - - # test_read_only_contributor_cannot_view_draft_list - res = app.get( - url_draft_registrations, - auth=user_read_contrib.auth, - expect_errors=True) - assert res.status_code == 403 - # test_read_write_contributor_cannot_view_draft_list - res = app.get( - url_draft_registrations, - auth=user_write_contrib.auth, - expect_errors=True) - assert res.status_code == 403 + def test_read_write_contributor_can_view_draft_list(self, app, user_write_contrib, url_draft_registrations): + res = app.get(url_draft_registrations, auth=user_write_contrib.auth) + assert res.status_code == 200 - # test_logged_in_non_contributor_cannot_view_draft_list - res = app.get( - url_draft_registrations, - auth=user_non_contrib.auth, - expect_errors=True) - assert res.status_code == 403 + def test_draft_contributor_not_project_contributor_can_view_draft_list(self, app, user_non_contrib, draft_registration, project_public, url_draft_registrations): + draft_registration.add_contributor(contributor=user_non_contrib, auth=Auth(draft_registration.initiator), save=True) + assert not project_public.is_contributor(user_non_contrib) + assert draft_registration.is_contributor(user_non_contrib) + res = app.get(url_draft_registrations, auth=user_non_contrib.auth) + assert res.status_code == 200 + data = res.json['data'] + assert len(data) == 1 - # test_unauthenticated_user_cannot_view_draft_list + def test_unauthenticated_user_cannot_view_draft_list(self, app, url_draft_registrations): res = app.get(url_draft_registrations, expect_errors=True) assert res.status_code == 401 - # test_osf_group_with_read_permissions - project_public.remove_osf_group(group) - project_public.add_osf_group(group, permissions.READ) - res = app.get(url_draft_registrations, auth=group_mem.auth, expect_errors=True) - assert res.status_code == 403 - def test_deleted_draft_registration_does_not_show_up_in_draft_list( self, app, user, draft_registration, url_draft_registrations): draft_registration.deleted = timezone.now() @@ -227,24 +290,11 @@ def test_draft_registration_serializer_usage(self, app, user, project_public, dr @pytest.mark.django_db -class TestDraftRegistrationCreate(DraftRegistrationTestCase): - - @pytest.fixture() - def provider(self): - return RegistrationProvider.get_default() - - @pytest.fixture() - def non_default_provider(self, metaschema_open_ended): - non_default_provider = RegistrationProviderFactory() - non_default_provider.schemas.add(metaschema_open_ended) - non_default_provider.save() - return non_default_provider +class TestDraftRegistrationCreate(AbstractDraftRegistrationTestCase): @pytest.fixture() - def metaschema_open_ended(self): - return RegistrationSchema.objects.get( - name='Open-Ended Registration', - schema_version=OPEN_ENDED_SCHEMA_VERSION) + def url_draft_registrations(self, project_public): + return f'/{API_BASE}nodes/{project_public._id}/draft_registrations/?version=2.19' @pytest.fixture() def payload(self, metaschema_open_ended, provider): @@ -269,37 +319,7 @@ def payload(self, metaschema_open_ended, provider): } } - @pytest.fixture() - def payload_with_non_default_provider(self, metaschema_open_ended, non_default_provider): - return { - 'data': { - 'type': 'draft_registrations', - 'attributes': {}, - 'relationships': { - 'registration_schema': { - 'data': { - 'type': 'registration_schema', - 'id': metaschema_open_ended._id - } - }, - 'provider': { - 'data': { - 'type': 'registration-providers', - 'id': non_default_provider._id, - } - } - } - } - } - - @pytest.fixture() - def url_draft_registrations(self, project_public): - return '/{}nodes/{}/draft_registrations/?{}'.format( - API_BASE, project_public._id, 'version=2.19') - - def test_type_is_draft_registrations( - self, app, user, metaschema_open_ended, - url_draft_registrations): + def test_type_is_draft_registrations(self, app, user, metaschema_open_ended, url_draft_registrations): draft_data = { 'data': { 'type': 'nodes', @@ -322,10 +342,13 @@ def test_type_is_draft_registrations( assert res.status_code == 409 def test_admin_can_create_draft( - self, app, user, project_public, url_draft_registrations, - payload, metaschema_open_ended): - url = f'{url_draft_registrations}&embed=branched_from&embed=initiator' - res = app.post_json_api(url, payload, auth=user.auth) + self, app, user, project_public, url_draft_registrations, payload, metaschema_open_ended + ): + res = app.post_json_api( + f'{url_draft_registrations}&embed=branched_from&embed=initiator', + payload, + auth=user.auth + ) assert res.status_code == 201 data = res.json['data'] assert metaschema_open_ended._id in data['relationships']['registration_schema']['links']['related']['href'] @@ -335,13 +358,10 @@ def test_admin_can_create_draft( assert data['embeds']['branched_from']['data']['id'] == project_public._id assert data['embeds']['initiator']['data']['id'] == user._id - def test_cannot_create_draft( - self, app, user_write_contrib, - user_read_contrib, user_non_contrib, - project_public, payload, group, - url_draft_registrations, group_mem): + def test_write_only_contributor_cannot_create_draft( + self, app, user_write_contrib, project_public, payload, url_draft_registrations + ): - # test_write_only_contributor_cannot_create_draft assert user_write_contrib in project_public.contributors.all() res = app.post_json_api( url_draft_registrations, @@ -350,7 +370,9 @@ def test_cannot_create_draft( expect_errors=True) assert res.status_code == 403 - # test_read_only_contributor_cannot_create_draft + def test_read_only_contributor_cannot_create_draft( + self, app, user_read_contrib, project_public, payload, url_draft_registrations + ): assert user_read_contrib in project_public.contributors.all() res = app.post_json_api( url_draft_registrations, @@ -359,13 +381,17 @@ def test_cannot_create_draft( expect_errors=True) assert res.status_code == 403 - # test_non_authenticated_user_cannot_create_draft + def test_non_authenticated_user_cannot_create_draft(self, app, user_read_contrib, payload, url_draft_registrations): res = app.post_json_api( url_draft_registrations, - payload, expect_errors=True) + payload, + expect_errors=True + ) assert res.status_code == 401 - # test_logged_in_non_contributor_cannot_create_draft + def test_logged_in_non_contributor_cannot_create_draft( + self, app, user_non_contrib, payload, url_draft_registrations + ): res = app.post_json_api( url_draft_registrations, payload, @@ -373,24 +399,6 @@ def test_cannot_create_draft( expect_errors=True) assert res.status_code == 403 - # test_group_admin_cannot_create_draft - res = app.post_json_api( - url_draft_registrations, - payload, - auth=group_mem.auth, - expect_errors=True) - assert res.status_code == 403 - - # test_group_write_contrib_cannot_create_draft - project_public.remove_osf_group(group) - project_public.add_osf_group(group, permissions.WRITE) - res = app.post_json_api( - url_draft_registrations, - payload, - auth=group_mem.auth, - expect_errors=True) - assert res.status_code == 403 - def test_schema_validation( self, app, user, provider, non_default_provider, payload, payload_with_non_default_provider, url_draft_registrations, metaschema_open_ended): # Schema validation for a default provider without defined schemas with any schema is tested by `test_admin_can_create_draft` diff --git a/api_tests/registrations/views/test_registration_list.py b/api_tests/registrations/views/test_registration_list.py index b9604b83501..4eca7295f7a 100644 --- a/api_tests/registrations/views/test_registration_list.py +++ b/api_tests/registrations/views/test_registration_list.py @@ -7,7 +7,7 @@ from api.base.settings.defaults import API_BASE from api.base.versioning import CREATE_REGISTRATION_FIELD_CHANGE_VERSION -from api_tests.nodes.views.test_node_draft_registration_list import DraftRegistrationTestCase +from api_tests.nodes.views.test_node_draft_registration_list import AbstractDraftRegistrationTestCase from api_tests.subjects.mixins import SubjectsFilterMixin from api_tests.registrations.filters.test_filters import RegistrationListFilteringMixin from api_tests.utils import create_test_file @@ -585,7 +585,7 @@ def url(self): return f'/{API_BASE}registrations/' -class TestNodeRegistrationCreate(DraftRegistrationTestCase): +class TestNodeRegistrationCreate(AbstractDraftRegistrationTestCase): """ Tests for creating registration through old workflow - POST NodeRegistrationList diff --git a/api_tests/users/views/test_user_draft_registration_list.py b/api_tests/users/views/test_user_draft_registration_list.py index 272a9a73d9c..a002ffe9a7e 100644 --- a/api_tests/users/views/test_user_draft_registration_list.py +++ b/api_tests/users/views/test_user_draft_registration_list.py @@ -3,7 +3,7 @@ from api.base.settings.defaults import API_BASE from api.users.views import UserDraftRegistrations -from api_tests.nodes.views.test_node_draft_registration_list import DraftRegistrationTestCase +from api_tests.nodes.views.test_node_draft_registration_list import AbstractDraftRegistrationTestCase from api_tests.utils import only_supports_methods from osf.models import RegistrationSchema from osf_tests.factories import ( @@ -17,7 +17,11 @@ @pytest.mark.django_db -class TestDraftRegistrationList(DraftRegistrationTestCase): +class TestUserDraftRegistrationList(AbstractDraftRegistrationTestCase): + + @pytest.fixture() + def url_draft_registrations(self, project_public): + return f'/{API_BASE}users/me/draft_registrations/' @pytest.fixture() def other_admin(self, project_public): @@ -29,7 +33,8 @@ def other_admin(self, project_public): def schema(self): return RegistrationSchema.objects.get( name='Open-Ended Registration', - schema_version=SCHEMA_VERSION) + schema_version=SCHEMA_VERSION + ) @pytest.fixture() def draft_registration(self, user, project_public, schema): @@ -39,17 +44,12 @@ def draft_registration(self, user, project_public, schema): branched_from=project_public ) - @pytest.fixture() - def url_draft_registrations(self, project_public): - return f'/{API_BASE}users/me/draft_registrations/' - def test_unacceptable_methods(self): assert only_supports_methods(UserDraftRegistrations, ['GET']) - def test_view_permissions( - self, app, user, other_admin, draft_registration, - user_write_contrib, user_read_contrib, user_non_contrib, - schema, url_draft_registrations): + def test_non_contrib_view_permissions( + self, app, user, other_admin, draft_registration, schema, url_draft_registrations + ): res = app.get(url_draft_registrations, auth=user.auth) assert res.status_code == 200 data = res.json['data'] @@ -58,33 +58,33 @@ def test_view_permissions( assert data[0]['id'] == draft_registration._id assert data[0]['attributes']['registration_metadata'] == {} - res = app.get(url_draft_registrations, auth=user.auth) - assert res.status_code == 200 - data = res.json['data'] - assert len(data) == 1 - assert schema._id in data[0]['relationships']['registration_schema']['links']['related']['href'] - assert data[0]['id'] == draft_registration._id - assert data[0]['attributes']['registration_metadata'] == {} - - # test_read_only_contributor_can_view_draft_list + def test_read_only_contributor_can_view_draft_list( + self, app, draft_registration, user_read_contrib, url_draft_registrations + ): res = app.get( url_draft_registrations, - auth=user_read_contrib.auth) + auth=user_read_contrib.auth + ) assert len(res.json['data']) == 1 - # test_read_write_contributor_can_view_draft_list + def test_read_write_contributor_can_view_draft_list( + self, app, user, other_admin, draft_registration, user_write_contrib, url_draft_registrations + ): res = app.get( url_draft_registrations, - auth=user_write_contrib.auth) + auth=user_write_contrib.auth + ) assert len(res.json['data']) == 1 - # test_logged_in_non_contributor_cannot_view_draft_list + def test_logged_in_non_contributor_cannot_view_draft_list( + self, app, user, draft_registration, user_non_contrib, url_draft_registrations + ): res = app.get( url_draft_registrations, auth=user_non_contrib.auth) assert len(res.json['data']) == 0 - # test_unauthenticated_user_cannot_view_draft_list + def test_unauthenticated_user_cannot_view_draft_list(self, app, url_draft_registrations): res = app.get(url_draft_registrations, expect_errors=True) assert res.status_code == 401 @@ -133,16 +133,16 @@ def test_draft_with_deleted_registered_node_shows_up_in_draft_list( assert data[0]['id'] == draft_registration._id assert data[0]['attributes']['registration_metadata'] == {} - def test_cannot_access_other_users_draft_registration( - self, app, user, other_admin, project_public, - draft_registration, schema): - url = f'/{API_BASE}users/{user._id}/draft_registrations/' - res = app.get(url, auth=other_admin.auth, expect_errors=True) + def test_cannot_access_other_users_draft_registration(self, app, user, other_admin, draft_registration, schema): + res = app.get( + f'/{API_BASE}users/{user._id}/draft_registrations/', + auth=other_admin.auth, + expect_errors=True + ) assert res.status_code == 403 - def test_can_access_own_draft_registrations_with_guid( - self, app, user, draft_registration): - url = f'/{API_BASE}users/{user._id}/draft_registrations/' + def test_can_access_own_draft_registrations_with_guid(self, app, user, draft_registration): + url = '/{}users/{}/draft_registrations/'.format(API_BASE, user._id) res = app.get(url, auth=user.auth, expect_errors=True) assert res.status_code == 200 assert len(res.json['data']) == 1