forked from CenterForOpenScience/osf.io
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
split off node request file again, added descriptive message when nod…
…e request access is turned off and added test case
- Loading branch information
John Tordoff
committed
Dec 16, 2024
1 parent
8b5c183
commit efc5dd2
Showing
3 changed files
with
302 additions
and
272 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
300 changes: 300 additions & 0 deletions
300
api_tests/requests/views/test_node_request_institutional_access.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,300 @@ | ||
from unittest import mock | ||
import pytest | ||
|
||
from api.base.settings.defaults import API_BASE | ||
from api_tests.requests.mixins import NodeRequestTestMixin | ||
|
||
from osf_tests.factories import NodeFactory, InstitutionFactory, AuthUserFactory | ||
from osf.utils.workflows import DefaultStates, NodeRequestTypes | ||
from website.mails import NODE_REQUEST_INSTITUTIONAL_ACCESS_REQUEST | ||
|
||
|
||
@pytest.mark.django_db | ||
class TestNodeRequestListInstitutionalAccess(NodeRequestTestMixin): | ||
|
||
@pytest.fixture() | ||
def url(self, project): | ||
return f'/{API_BASE}nodes/{project._id}/requests/' | ||
|
||
@pytest.fixture() | ||
def institution(self): | ||
return InstitutionFactory() | ||
|
||
@pytest.fixture() | ||
def institution2(self): | ||
return InstitutionFactory() | ||
|
||
@pytest.fixture() | ||
def user_with_affiliation(self, institution): | ||
user = AuthUserFactory() | ||
user.add_or_update_affiliated_institution(institution) | ||
return user | ||
|
||
@pytest.fixture() | ||
def user_without_affiliation(self): | ||
return AuthUserFactory() | ||
|
||
@pytest.fixture() | ||
def institutional_admin(self, institution): | ||
admin_user = AuthUserFactory() | ||
institution.get_group('institutional_admins').user_set.add(admin_user) | ||
return admin_user | ||
|
||
@pytest.fixture() | ||
def create_payload(self, institution, user_with_affiliation): | ||
return { | ||
'data': { | ||
'attributes': { | ||
'comment': 'Wanna Philly Philly?', | ||
'request_type': NodeRequestTypes.INSTITUTIONAL_REQUEST.value, | ||
}, | ||
'relationships': { | ||
'institution': { | ||
'data': { | ||
'id': institution._id, | ||
'type': 'institutions' | ||
} | ||
}, | ||
'message_recipient': { | ||
'data': { | ||
'id': user_with_affiliation._id, | ||
'type': 'users' | ||
} | ||
} | ||
}, | ||
'type': 'node-requests' | ||
} | ||
} | ||
|
||
@pytest.fixture() | ||
def node_with_disabled_access_requests(self, institution): | ||
node = NodeFactory() | ||
node.access_requests_enabled = False | ||
creator = node.creator | ||
creator.add_or_update_affiliated_institution(institution) | ||
creator.save() | ||
node.add_affiliated_institution(institution, creator) | ||
node.save() | ||
return node | ||
|
||
def test_institutional_admin_can_make_institutional_request(self, app, project, institutional_admin, url, create_payload): | ||
""" | ||
Test that an institutional admin can make an institutional access request. | ||
""" | ||
res = app.post_json_api(url, create_payload, auth=institutional_admin.auth) | ||
assert res.status_code == 201 | ||
|
||
# Verify the NodeRequest object is created | ||
node_request = project.requests.get(creator=institutional_admin) | ||
assert node_request.request_type == NodeRequestTypes.INSTITUTIONAL_REQUEST.value | ||
assert node_request.comment == 'Wanna Philly Philly?' | ||
assert node_request.machine_state == DefaultStates.PENDING.value | ||
|
||
def test_non_admin_cant_make_institutional_request(self, app, project, noncontrib, url, create_payload): | ||
""" | ||
Test that a non-institutional admin cannot make an institutional access request. | ||
""" | ||
res = app.post_json_api(url, create_payload, auth=noncontrib.auth, expect_errors=True) | ||
assert res.status_code == 403 | ||
assert 'You do not have permission to perform this action for this institution.' in res.json['errors'][0]['detail'] | ||
|
||
def test_institutional_admin_can_add_requested_permission(self, app, project, institutional_admin, url, create_payload): | ||
""" | ||
Test that an institutional admin can make an institutional access request with requested_permissions. | ||
""" | ||
create_payload['data']['attributes']['requested_permissions'] = 'admin' | ||
|
||
res = app.post_json_api(url, create_payload, auth=institutional_admin.auth) | ||
assert res.status_code == 201 | ||
|
||
# Verify the NodeRequest object is created with the correct requested_permissions | ||
node_request = project.requests.get(creator=institutional_admin) | ||
assert node_request.request_type == NodeRequestTypes.INSTITUTIONAL_REQUEST.value | ||
assert node_request.requested_permissions == 'admin' | ||
|
||
def test_institutional_admin_needs_institution(self, app, project, institutional_admin, url, create_payload): | ||
""" | ||
Test that the payload needs the institution relationship and gives the correct error message. | ||
""" | ||
del create_payload['data']['relationships']['institution'] | ||
|
||
res = app.post_json_api(url, create_payload, auth=institutional_admin.auth, expect_errors=True) | ||
assert res.status_code == 400 | ||
assert 'Institution is required.' in res.json['errors'][0]['detail'] | ||
|
||
def test_institutional_admin_invalid_institution(self, app, project, institutional_admin, url, create_payload): | ||
""" | ||
Test that the payload validates the institution relationship and gives the correct error message when it's | ||
invalid. | ||
""" | ||
create_payload['data']['relationships']['institution']['data']['id'] = 'invalid_id' | ||
|
||
res = app.post_json_api(url, create_payload, auth=institutional_admin.auth, expect_errors=True) | ||
assert res.status_code == 400 | ||
assert 'Institution is does not exist.' in res.json['errors'][0]['detail'] | ||
|
||
def test_institutional_admin_unauth_institution(self, app, project, institution2, institutional_admin, url, create_payload): | ||
""" | ||
Test that the view authenticates the relationship between the institution and the user and gives the correct | ||
error message when it's unauthorized.' | ||
""" | ||
create_payload['data']['relationships']['institution']['data']['id'] = institution2._id | ||
|
||
res = app.post_json_api(url, create_payload, auth=institutional_admin.auth, expect_errors=True) | ||
assert res.status_code == 403 | ||
assert 'You do not have permission to perform this action for this institution.' in res.json['errors'][0]['detail'] | ||
|
||
@mock.patch('api.requests.serializers.send_mail') | ||
def test_email_not_sent_without_recipient(self, mock_mail, app, project, institutional_admin, url, | ||
create_payload, institution): | ||
""" | ||
Test that an email is not sent when no recipient is listed when an institutional access request is made, | ||
but the request is still made anyway without email. | ||
""" | ||
del create_payload['data']['relationships']['message_recipient'] | ||
res = app.post_json_api(url, create_payload, auth=institutional_admin.auth) | ||
assert res.status_code == 201 | ||
|
||
# Check that an email is sent | ||
assert not mock_mail.called | ||
|
||
@mock.patch('api.requests.serializers.send_mail') | ||
def test_email_not_sent_outside_institution(self, mock_mail, app, project, institutional_admin, url, | ||
create_payload, user_without_affiliation, institution): | ||
""" | ||
Test that you are prevented from requesting a user with the correct institutional affiliation. | ||
""" | ||
create_payload['data']['relationships']['message_recipient']['data']['id'] = user_without_affiliation._id | ||
res = app.post_json_api(url, create_payload, auth=institutional_admin.auth, expect_errors=True) | ||
assert res.status_code == 403 | ||
assert f'User {user_without_affiliation._id} is not affiliated with the institution.' in res.json['errors'][0]['detail'] | ||
|
||
# Check that an email is sent | ||
assert not mock_mail.called | ||
|
||
@mock.patch('api.requests.serializers.send_mail') | ||
def test_email_sent_on_creation( | ||
self, | ||
mock_mail, | ||
app, | ||
project, | ||
institutional_admin, | ||
url, | ||
user_with_affiliation, | ||
create_payload, | ||
institution | ||
): | ||
""" | ||
Test that an email is sent to the appropriate recipients when an institutional access request is made. | ||
""" | ||
res = app.post_json_api(url, create_payload, auth=institutional_admin.auth) | ||
assert res.status_code == 201 | ||
|
||
assert mock_mail.call_count == 1 | ||
|
||
mock_mail.assert_called_with( | ||
to_addr=user_with_affiliation.username, | ||
mail=NODE_REQUEST_INSTITUTIONAL_ACCESS_REQUEST, | ||
user=user_with_affiliation, | ||
bcc_addr=None, | ||
reply_to=None, | ||
**{ | ||
'sender': institutional_admin, | ||
'recipient': user_with_affiliation, | ||
'comment': create_payload['data']['attributes']['comment'], | ||
'institution': institution, | ||
'osf_url': mock.ANY, | ||
'node': project, | ||
} | ||
) | ||
|
||
@mock.patch('api.requests.serializers.send_mail') | ||
def test_bcc_institutional_admin( | ||
self, | ||
mock_mail, | ||
app, | ||
project, | ||
institutional_admin, | ||
url, | ||
user_with_affiliation, | ||
create_payload, | ||
institution | ||
): | ||
""" | ||
Ensure BCC option works as expected, sending messages to sender giving them a copy for themselves. | ||
""" | ||
create_payload['data']['attributes']['bcc_sender'] = True | ||
|
||
res = app.post_json_api(url, create_payload, auth=institutional_admin.auth) | ||
assert res.status_code == 201 | ||
|
||
assert mock_mail.call_count == 1 | ||
|
||
mock_mail.assert_called_with( | ||
to_addr=user_with_affiliation.username, | ||
mail=NODE_REQUEST_INSTITUTIONAL_ACCESS_REQUEST, | ||
user=user_with_affiliation, | ||
bcc_addr=[institutional_admin.username], | ||
reply_to=None, | ||
**{ | ||
'sender': institutional_admin, | ||
'recipient': user_with_affiliation, | ||
'comment': create_payload['data']['attributes']['comment'], | ||
'institution': institution, | ||
'osf_url': mock.ANY, | ||
'node': project, | ||
} | ||
) | ||
|
||
@mock.patch('api.requests.serializers.send_mail') | ||
def test_reply_to_institutional_admin( | ||
self, | ||
mock_mail, | ||
app, | ||
project, | ||
institutional_admin, | ||
url, | ||
user_with_affiliation, | ||
create_payload, | ||
institution | ||
): | ||
""" | ||
Ensure reply-to option works as expected, allowing a reply to header be added to the email. | ||
""" | ||
create_payload['data']['attributes']['reply_to'] = True | ||
|
||
res = app.post_json_api(url, create_payload, auth=institutional_admin.auth) | ||
assert res.status_code == 201 | ||
|
||
assert mock_mail.call_count == 1 | ||
|
||
mock_mail.assert_called_with( | ||
to_addr=user_with_affiliation.username, | ||
mail=NODE_REQUEST_INSTITUTIONAL_ACCESS_REQUEST, | ||
user=user_with_affiliation, | ||
bcc_addr=None, | ||
reply_to=institutional_admin.username, | ||
**{ | ||
'sender': institutional_admin, | ||
'recipient': user_with_affiliation, | ||
'comment': create_payload['data']['attributes']['comment'], | ||
'institution': institution, | ||
'osf_url': mock.ANY, | ||
'node': project, | ||
} | ||
) | ||
|
||
def test_access_requests_disabled_raises_permission_denied( | ||
self, app, node_with_disabled_access_requests, user_with_affiliation, institutional_admin, create_payload | ||
): | ||
""" | ||
Ensure that when `access_requests_enabled` is `False`, a PermissionDenied error is raised. | ||
""" | ||
res = app.post_json_api( | ||
f'/{API_BASE}nodes/{node_with_disabled_access_requests._id}/requests/', | ||
create_payload, | ||
auth=institutional_admin.auth, | ||
expect_errors=True | ||
) | ||
assert res.status_code == 403 | ||
assert f"{node_with_disabled_access_requests._id} does not have Access Requests enabled" in res.json['errors'][0]['detail'] |
Oops, something went wrong.