# What this PR does Reduces number of calls to db for `/schedules`, `/alertgroups` and `/users` endpoints. Fixes the issue when there was an additional call to db to get organization url to build user avatar full link. ## Which issue(s) this PR closes Related to [issue link here] <!-- *Note*: If you want the issue to be auto-closed once the PR is merged, change "Related to" to "Closes" in the line above. If you have more than one GitHub issue that this PR closes, be sure to preface each issue link with a [closing keyword](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue). This ensures that the issue(s) are auto-closed once the PR has been merged. --> ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes.
802 lines
30 KiB
Python
802 lines
30 KiB
Python
import datetime
|
|
import json
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from django.urls import reverse
|
|
from django.utils import timezone
|
|
from rest_framework import status
|
|
from rest_framework.response import Response
|
|
from rest_framework.test import APIClient
|
|
|
|
from apps.api.permissions import LegacyAccessControlRole
|
|
from apps.schedules.models import CustomOnCallShift, OnCallScheduleWeb, ShiftSwapRequest
|
|
from common.api_helpers.utils import serialize_datetime_as_utc_timestamp
|
|
from common.insight_log import EntityEvent
|
|
|
|
description = "my shift swap request"
|
|
tomorrow = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0) + datetime.timedelta(days=1)
|
|
two_days_from_now = tomorrow + datetime.timedelta(days=1)
|
|
|
|
mock_success_response = Response(status=status.HTTP_200_OK)
|
|
|
|
|
|
@pytest.fixture
|
|
def ssr_setup(
|
|
make_schedule, make_organization_and_user_with_plugin_token, make_user_for_organization, make_shift_swap_request
|
|
):
|
|
def _ssr_setup(beneficiary_role=None, benefactor_role=None, **kwargs):
|
|
organization, beneficiary, token = make_organization_and_user_with_plugin_token(role=beneficiary_role)
|
|
benefactor = make_user_for_organization(organization, role=benefactor_role)
|
|
|
|
schedule = make_schedule(organization, schedule_class=OnCallScheduleWeb)
|
|
ssr = make_shift_swap_request(schedule, beneficiary, swap_start=tomorrow, swap_end=two_days_from_now, **kwargs)
|
|
|
|
return ssr, beneficiary, token, benefactor
|
|
|
|
return _ssr_setup
|
|
|
|
|
|
def _construct_serialized_object(
|
|
ssr: ShiftSwapRequest, status="open", description=None, benefactor=None, list_response=False, expand_users=False
|
|
):
|
|
data = {
|
|
"id": ssr.public_primary_key,
|
|
"created_at": serialize_datetime_as_utc_timestamp(ssr.created_at),
|
|
"updated_at": serialize_datetime_as_utc_timestamp(ssr.updated_at),
|
|
"schedule": ssr.schedule.public_primary_key,
|
|
"swap_start": serialize_datetime_as_utc_timestamp(ssr.swap_start),
|
|
"swap_end": serialize_datetime_as_utc_timestamp(ssr.swap_end),
|
|
"beneficiary": ssr.beneficiary.public_primary_key,
|
|
"status": status,
|
|
"benefactor": benefactor,
|
|
"description": description,
|
|
}
|
|
|
|
if expand_users:
|
|
|
|
def _serialized_user(u):
|
|
if u:
|
|
return {
|
|
"display_name": u.username,
|
|
"email": u.email,
|
|
"pk": u.public_primary_key,
|
|
"avatar_full": u.avatar_full_url(u.organization),
|
|
}
|
|
|
|
data["beneficiary"] = _serialized_user(ssr.beneficiary)
|
|
data["benefactor"] = _serialized_user(ssr.benefactor)
|
|
|
|
if not list_response:
|
|
data["shifts"] = ssr.shifts()
|
|
|
|
return data
|
|
|
|
|
|
def _build_expected_update_response(ssr, modified_data, updated_at_ts, **kwargs):
|
|
"""
|
|
updated_at timestamp will obviously be bumped when we do a PUT/PATCH
|
|
"""
|
|
return _construct_serialized_object(ssr, **kwargs) | modified_data | {"updated_at": updated_at_ts}
|
|
|
|
|
|
@pytest.mark.django_db
|
|
@pytest.mark.parametrize("expand_users", ["true", "false", None])
|
|
def test_list(ssr_setup, make_user_auth_headers, expand_users):
|
|
ssr, beneficiary, token, _ = ssr_setup(description=description)
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-list")
|
|
if expand_users is not None:
|
|
url += "?expand_users={}".format(expand_users)
|
|
|
|
expected_payload = {
|
|
"next": None,
|
|
"previous": None,
|
|
"page_size": 50,
|
|
"count": 1,
|
|
"current_page_number": 1,
|
|
"total_pages": 1,
|
|
"results": [
|
|
_construct_serialized_object(
|
|
ssr, description=description, list_response=True, expand_users=expand_users == "true"
|
|
),
|
|
],
|
|
}
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(beneficiary, token))
|
|
assert response.status_code == status.HTTP_200_OK
|
|
assert response.json() == expected_payload
|
|
|
|
|
|
@patch("apps.api.views.shift_swap.ShiftSwapViewSet.list", return_value=mock_success_response)
|
|
@pytest.mark.django_db
|
|
@pytest.mark.parametrize(
|
|
"role,expected_status",
|
|
[
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
|
|
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
|
|
],
|
|
)
|
|
def test_list_permissions(
|
|
mock_endpoint_handler,
|
|
ssr_setup,
|
|
make_user_auth_headers,
|
|
role,
|
|
expected_status,
|
|
):
|
|
_, beneficiary, token, _ = ssr_setup(beneficiary_role=role)
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-list")
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(beneficiary, token))
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
@pytest.mark.django_db
|
|
@pytest.mark.parametrize("expand_users", ["true", "false", None])
|
|
def test_retrieve(ssr_setup, make_user_auth_headers, expand_users):
|
|
ssr, beneficiary, token, _ = ssr_setup(description=description)
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
|
|
if expand_users is not None:
|
|
url += "?expand_users={}".format(expand_users)
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(beneficiary, token))
|
|
assert response.status_code == status.HTTP_200_OK
|
|
assert response.json() == _construct_serialized_object(
|
|
ssr, description=description, expand_users=expand_users == "true"
|
|
)
|
|
|
|
|
|
@patch("apps.api.views.shift_swap.ShiftSwapViewSet.retrieve", return_value=mock_success_response)
|
|
@pytest.mark.django_db
|
|
@pytest.mark.parametrize(
|
|
"role,expected_status",
|
|
[
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
|
|
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
|
|
],
|
|
)
|
|
def test_retrieve_permissions(
|
|
mock_endpoint_handler,
|
|
ssr_setup,
|
|
make_user_auth_headers,
|
|
role,
|
|
expected_status,
|
|
):
|
|
ssr, _, token, benefactor = ssr_setup(benefactor_role=role)
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(benefactor, token))
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
@patch("apps.schedules.models.shift_swap_request.write_resource_insight_log")
|
|
@patch("apps.schedules.tasks.shift_swaps.create_shift_swap_request_message")
|
|
@pytest.mark.django_db
|
|
def test_create(
|
|
mock_create_shift_swap_request_message,
|
|
mock_write_resource_insight_log,
|
|
make_organization_and_user_with_plugin_token,
|
|
make_schedule,
|
|
make_user_auth_headers,
|
|
):
|
|
organization, user, token = make_organization_and_user_with_plugin_token()
|
|
schedule = make_schedule(organization, schedule_class=OnCallScheduleWeb)
|
|
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-list")
|
|
|
|
data = {
|
|
"schedule": schedule.public_primary_key,
|
|
"description": "hellooooo world",
|
|
"swap_start": tomorrow,
|
|
"swap_end": two_days_from_now,
|
|
}
|
|
response = client.post(url, data, format="json", **make_user_auth_headers(user, token))
|
|
ssr = ShiftSwapRequest.objects.get(public_primary_key=response.json()["id"])
|
|
expected_response = _construct_serialized_object(ssr) | {
|
|
**data,
|
|
"swap_start": serialize_datetime_as_utc_timestamp(tomorrow),
|
|
"swap_end": serialize_datetime_as_utc_timestamp(two_days_from_now),
|
|
}
|
|
|
|
assert response.status_code == status.HTTP_201_CREATED
|
|
assert response.json() == expected_response
|
|
|
|
mock_write_resource_insight_log.assert_called_once_with(instance=ssr, author=user, event=EntityEvent.CREATED)
|
|
mock_create_shift_swap_request_message.apply_async.assert_called_once_with((ssr.pk,))
|
|
|
|
|
|
@pytest.mark.django_db
|
|
@pytest.mark.parametrize(
|
|
"swap_start,expected_persisted_value",
|
|
[
|
|
# UTC format
|
|
("2285-07-20T12:00:00Z", "2285-07-20T12:00:00.000000Z"),
|
|
# UTC format w/ microseconds
|
|
("2285-07-20T12:00:00.245652Z", "2285-07-20T12:00:00.245652Z"),
|
|
# UTC offset w/ colons + no microseconds
|
|
("2285-07-20T12:00:00+07:00", "2285-07-20T05:00:00.000000Z"),
|
|
# UTC offset w/ colons + microseconds
|
|
("2285-07-20T12:00:00.245652+07:00", "2285-07-20T05:00:00.245652Z"),
|
|
# UTC offset w/ no colons + no microseconds
|
|
("2285-07-20T12:00:00+0700", "2285-07-20T05:00:00.000000Z"),
|
|
# UTC offset w/ no colons + microseconds
|
|
("2285-07-20T12:00:00.245652+0700", "2285-07-20T05:00:00.245652Z"),
|
|
("2285-07-20 12:00:00", None),
|
|
("22850720T120000Z", None),
|
|
],
|
|
)
|
|
def test_create_swap_start_and_swap_end_must_include_time_zone(
|
|
make_organization_and_user_with_plugin_token,
|
|
make_schedule,
|
|
make_user_auth_headers,
|
|
swap_start,
|
|
expected_persisted_value,
|
|
):
|
|
organization, user, token = make_organization_and_user_with_plugin_token()
|
|
schedule = make_schedule(organization, schedule_class=OnCallScheduleWeb)
|
|
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-list")
|
|
|
|
start_year = "2285"
|
|
end_year = "2286"
|
|
swap_end = swap_start.replace(start_year, end_year)
|
|
|
|
data = {
|
|
"schedule": schedule.public_primary_key,
|
|
"description": "hellooooo world",
|
|
"swap_start": swap_start,
|
|
"swap_end": swap_end,
|
|
}
|
|
response = client.post(url, data, format="json", **make_user_auth_headers(user, token))
|
|
|
|
if expected_persisted_value:
|
|
ssr = ShiftSwapRequest.objects.get(public_primary_key=response.json()["id"])
|
|
|
|
assert response.status_code == status.HTTP_201_CREATED
|
|
assert response.json() == _construct_serialized_object(ssr) | {
|
|
**data,
|
|
"swap_start": expected_persisted_value,
|
|
"swap_end": expected_persisted_value.replace(start_year, end_year),
|
|
}
|
|
else:
|
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
|
|
|
|
|
@patch("apps.api.views.shift_swap.ShiftSwapViewSet.create", return_value=mock_success_response)
|
|
@pytest.mark.django_db
|
|
@pytest.mark.parametrize(
|
|
"role,expected_status",
|
|
[
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
|
|
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
|
|
],
|
|
)
|
|
def test_create_permissions(
|
|
mock_endpoint_handler,
|
|
ssr_setup,
|
|
make_user_auth_headers,
|
|
role,
|
|
expected_status,
|
|
):
|
|
_, _, token, benefactor = ssr_setup(benefactor_role=role)
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-list")
|
|
|
|
response = client.post(url, format="json", **make_user_auth_headers(benefactor, token))
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
@patch("apps.api.views.shift_swap.write_resource_insight_log")
|
|
@patch("apps.api.views.shift_swap.update_shift_swap_request_message")
|
|
@pytest.mark.django_db
|
|
def test_update(
|
|
mock_update_shift_swap_request_message, mock_write_resource_insight_log, ssr_setup, make_user_auth_headers
|
|
):
|
|
ssr, beneficiary, token, _ = ssr_setup(description=description)
|
|
insights_log_prev_state = ssr.insight_logs_serialized
|
|
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
|
|
auth_headers = make_user_auth_headers(beneficiary, token)
|
|
|
|
data = {
|
|
"description": "hellooooo world",
|
|
"schedule": ssr.schedule.public_primary_key,
|
|
"swap_start": serialize_datetime_as_utc_timestamp(ssr.swap_start),
|
|
"swap_end": serialize_datetime_as_utc_timestamp(ssr.swap_end),
|
|
}
|
|
|
|
response = client.put(url, data=json.dumps(data), content_type="application/json", **auth_headers)
|
|
response_json = response.json()
|
|
expected_response = _build_expected_update_response(ssr, data, response_json["updated_at"], description=description)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
assert response_json == expected_response
|
|
|
|
response = client.get(url, format="json", **auth_headers)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
assert response.json() == expected_response
|
|
|
|
ssr.refresh_from_db()
|
|
mock_write_resource_insight_log.assert_called_once_with(
|
|
instance=ssr,
|
|
author=beneficiary,
|
|
event=EntityEvent.UPDATED,
|
|
prev_state=insights_log_prev_state,
|
|
new_state=ssr.insight_logs_serialized,
|
|
)
|
|
|
|
mock_update_shift_swap_request_message.apply_async.assert_called_once_with((ssr.pk,))
|
|
|
|
|
|
@pytest.mark.django_db
|
|
@pytest.mark.parametrize(
|
|
"swap_start,expected_persisted_value",
|
|
[
|
|
# UTC format
|
|
("2285-07-20T12:00:00Z", "2285-07-20T12:00:00.000000Z"),
|
|
# UTC format w/ microseconds
|
|
("2285-07-20T12:00:00.245652Z", "2285-07-20T12:00:00.245652Z"),
|
|
# UTC offset w/ colons + no microseconds
|
|
("2285-07-20T12:00:00+07:00", "2285-07-20T05:00:00.000000Z"),
|
|
# UTC offset w/ colons + microseconds
|
|
("2285-07-20T12:00:00.245652+07:00", "2285-07-20T05:00:00.245652Z"),
|
|
# UTC offset w/ no colons + no microseconds
|
|
("2285-07-20T12:00:00+0700", "2285-07-20T05:00:00.000000Z"),
|
|
# UTC offset w/ no colons + microseconds
|
|
("2285-07-20T12:00:00.245652+0700", "2285-07-20T05:00:00.245652Z"),
|
|
("2285-07-20 12:00:00", None),
|
|
("22850720T120000Z", None),
|
|
],
|
|
)
|
|
def test_update_swap_start_and_swap_end_must_include_time_zone(
|
|
ssr_setup,
|
|
make_user_auth_headers,
|
|
swap_start,
|
|
expected_persisted_value,
|
|
):
|
|
ssr, beneficiary, token, _ = ssr_setup()
|
|
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
|
|
|
|
start_year = "2285"
|
|
end_year = "2286"
|
|
swap_end = swap_start.replace(start_year, end_year)
|
|
|
|
data = {
|
|
"schedule": ssr.schedule.public_primary_key,
|
|
"swap_start": swap_start,
|
|
"swap_end": swap_end,
|
|
}
|
|
response = client.put(url, data, format="json", **make_user_auth_headers(beneficiary, token))
|
|
|
|
if expected_persisted_value:
|
|
ssr = ShiftSwapRequest.objects.get(public_primary_key=response.json()["id"])
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
assert response.json() == _construct_serialized_object(ssr) | {
|
|
**data,
|
|
"swap_start": expected_persisted_value,
|
|
"swap_end": expected_persisted_value.replace(start_year, end_year),
|
|
}
|
|
else:
|
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
|
|
|
|
|
@pytest.mark.django_db
|
|
@pytest.mark.parametrize(
|
|
"role,expected_status",
|
|
[
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
|
|
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
|
|
],
|
|
)
|
|
def test_update_own_ssr_permissions(ssr_setup, make_user_auth_headers, role, expected_status):
|
|
ssr, beneficiary, token, _ = ssr_setup(beneficiary_role=role)
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
|
|
|
|
data = {
|
|
"description": "hellooooo world",
|
|
"schedule": ssr.schedule.public_primary_key,
|
|
"swap_start": serialize_datetime_as_utc_timestamp(ssr.swap_start),
|
|
"swap_end": serialize_datetime_as_utc_timestamp(ssr.swap_end),
|
|
}
|
|
|
|
response = client.put(
|
|
url, data=json.dumps(data), content_type="application/json", **make_user_auth_headers(beneficiary, token)
|
|
)
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_update_others_ssr_permissions(ssr_setup, make_user_auth_headers):
|
|
ssr, _, token, benefactor = ssr_setup()
|
|
assert benefactor.role == LegacyAccessControlRole.ADMIN
|
|
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
|
|
|
|
response = client.put(
|
|
url, data=json.dumps({}), content_type="application/json", **make_user_auth_headers(benefactor, token)
|
|
)
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
|
@patch("apps.api.views.shift_swap.write_resource_insight_log")
|
|
@patch("apps.api.views.shift_swap.update_shift_swap_request_message")
|
|
@pytest.mark.django_db
|
|
def test_partial_update(
|
|
mock_update_shift_swap_request_message, mock_write_resource_insight_log, ssr_setup, make_user_auth_headers
|
|
):
|
|
ssr, beneficiary, token, _ = ssr_setup(description=description)
|
|
insights_log_prev_state = ssr.insight_logs_serialized
|
|
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
|
|
auth_headers = make_user_auth_headers(beneficiary, token)
|
|
|
|
data = {"description": "this is a shift swap request"}
|
|
|
|
response = client.patch(url, data=json.dumps(data), content_type="application/json", **auth_headers)
|
|
response_json = response.json()
|
|
expected_response = _build_expected_update_response(ssr, data, response_json["updated_at"], description=description)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
assert response.json() == expected_response
|
|
|
|
response = client.get(url, format="json", **auth_headers)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
assert response.json() == expected_response
|
|
|
|
ssr.refresh_from_db()
|
|
mock_write_resource_insight_log.assert_called_once_with(
|
|
instance=ssr,
|
|
author=beneficiary,
|
|
event=EntityEvent.UPDATED,
|
|
prev_state=insights_log_prev_state,
|
|
new_state=ssr.insight_logs_serialized,
|
|
)
|
|
|
|
mock_update_shift_swap_request_message.apply_async.assert_called_once_with((ssr.pk,))
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_partial_update_time_related_fields(ssr_setup, make_user_auth_headers):
|
|
ssr, beneficiary, token, _ = ssr_setup()
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
|
|
auth_headers = make_user_auth_headers(beneficiary, token)
|
|
|
|
# but if we do PATCH a time related field, we must specify all the time fields
|
|
swap_start = {"swap_start": serialize_datetime_as_utc_timestamp(tomorrow + datetime.timedelta(days=5))}
|
|
swap_end = {"swap_end": serialize_datetime_as_utc_timestamp(tomorrow + datetime.timedelta(days=10))}
|
|
valid = swap_start | swap_end
|
|
|
|
for case in [swap_start, swap_end]:
|
|
response = client.patch(url, data=json.dumps(case), content_type="application/json", **auth_headers)
|
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
|
|
|
# valid way to patch time related fields
|
|
response = client.patch(url, data=json.dumps(valid), content_type="application/json", **auth_headers)
|
|
response_json = response.json()
|
|
expected_response = _build_expected_update_response(ssr, valid, response_json["updated_at"])
|
|
assert response.status_code == status.HTTP_200_OK
|
|
assert response.json() == expected_response
|
|
|
|
response = client.get(url, format="json", **auth_headers)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
assert response.json() == expected_response
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_related_shifts(ssr_setup, make_on_call_shift, make_user_auth_headers):
|
|
ssr, beneficiary, token, _ = ssr_setup()
|
|
|
|
schedule = ssr.schedule
|
|
organization = schedule.organization
|
|
user = beneficiary
|
|
|
|
today = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
start = today + timezone.timedelta(days=1)
|
|
duration = timezone.timedelta(hours=8)
|
|
data = {
|
|
"start": start,
|
|
"rotation_start": start,
|
|
"duration": duration,
|
|
"priority_level": 1,
|
|
"frequency": CustomOnCallShift.FREQUENCY_DAILY,
|
|
"schedule": schedule,
|
|
}
|
|
on_call_shift = make_on_call_shift(
|
|
organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data
|
|
)
|
|
on_call_shift.add_rolling_users([[user]])
|
|
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
|
|
auth_headers = make_user_auth_headers(beneficiary, token)
|
|
response = client.get(url, **auth_headers)
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
response_json = response.json()
|
|
expected = [
|
|
# start, end, user, swap request ID
|
|
(
|
|
start.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
(start + duration).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
user.public_primary_key,
|
|
ssr.public_primary_key,
|
|
),
|
|
]
|
|
returned_events = [
|
|
(e["start"], e["end"], e["users"][0]["pk"], e["users"][0]["swap_request"]["pk"])
|
|
for e in response_json["shifts"]
|
|
]
|
|
assert returned_events == expected
|
|
|
|
|
|
@pytest.mark.django_db
|
|
@pytest.mark.parametrize(
|
|
"role,expected_status",
|
|
[
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
|
|
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
|
|
],
|
|
)
|
|
def test_partial_update_own_ssr_permissions(ssr_setup, make_user_auth_headers, role, expected_status):
|
|
ssr, beneficiary, token, _ = ssr_setup(beneficiary_role=role)
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
|
|
|
|
response = client.patch(
|
|
url,
|
|
data=json.dumps({"description": "foo"}),
|
|
content_type="application/json",
|
|
**make_user_auth_headers(beneficiary, token),
|
|
)
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_partial_update_others_ssr_permissions(ssr_setup, make_user_auth_headers):
|
|
ssr, _, token, benefactor = ssr_setup()
|
|
assert benefactor.role == LegacyAccessControlRole.ADMIN
|
|
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
|
|
|
|
response = client.patch(
|
|
url, data=json.dumps({}), content_type="application/json", **make_user_auth_headers(benefactor, token)
|
|
)
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_benefactor_and_beneficiary_are_read_only_fields(ssr_setup, make_user_auth_headers):
|
|
ssr, beneficiary, token, benefactor = ssr_setup(description=description)
|
|
|
|
client = APIClient()
|
|
list_url = reverse("api-internal:shift_swap-list")
|
|
detail_url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
|
|
auth_headers = make_user_auth_headers(beneficiary, token)
|
|
|
|
base_data = {
|
|
"description": "hellooooo world",
|
|
"schedule": ssr.schedule.public_primary_key,
|
|
"swap_start": serialize_datetime_as_utc_timestamp(ssr.swap_start),
|
|
"swap_end": serialize_datetime_as_utc_timestamp(ssr.swap_end),
|
|
}
|
|
|
|
update_beneficiary = {"beneficiary": benefactor.public_primary_key}
|
|
update_benefactor = {"benefactor": beneficiary.public_primary_key}
|
|
|
|
def _assert_beneficiary_hasnt_changed(resp):
|
|
assert resp.json()["beneficiary"] == beneficiary.public_primary_key
|
|
|
|
def _assert_benefactor_is_still_none(resp):
|
|
assert resp.json()["benefactor"] is None
|
|
|
|
response = client.post(
|
|
list_url, data=json.dumps(base_data | update_beneficiary), content_type="application/json", **auth_headers
|
|
)
|
|
_assert_beneficiary_hasnt_changed(response)
|
|
|
|
response = client.post(
|
|
list_url, data=json.dumps(base_data | update_benefactor), content_type="application/json", **auth_headers
|
|
)
|
|
_assert_benefactor_is_still_none(response)
|
|
|
|
response = client.put(
|
|
detail_url, data=json.dumps(base_data | update_beneficiary), content_type="application/json", **auth_headers
|
|
)
|
|
_assert_beneficiary_hasnt_changed(response)
|
|
|
|
response = client.put(
|
|
detail_url, data=json.dumps(base_data | update_benefactor), content_type="application/json", **auth_headers
|
|
)
|
|
_assert_benefactor_is_still_none(response)
|
|
|
|
response = client.patch(
|
|
detail_url, data=json.dumps(base_data | update_beneficiary), content_type="application/json", **auth_headers
|
|
)
|
|
_assert_beneficiary_hasnt_changed(response)
|
|
|
|
response = client.patch(
|
|
detail_url, data=json.dumps(base_data | update_benefactor), content_type="application/json", **auth_headers
|
|
)
|
|
_assert_benefactor_is_still_none(response)
|
|
|
|
|
|
@patch("apps.api.views.shift_swap.write_resource_insight_log")
|
|
@patch("apps.api.views.shift_swap.update_shift_swap_request_message")
|
|
@pytest.mark.django_db
|
|
def test_delete(
|
|
mock_update_shift_swap_request_message, mock_write_resource_insight_log, ssr_setup, make_user_auth_headers
|
|
):
|
|
ssr, beneficiary, token, _ = ssr_setup()
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
|
|
auth_headers = make_user_auth_headers(beneficiary, token)
|
|
|
|
response = client.delete(url, **auth_headers)
|
|
assert response.status_code == status.HTTP_204_NO_CONTENT
|
|
|
|
response = client.get(url, format="json", **auth_headers)
|
|
assert response.status_code == status.HTTP_404_NOT_FOUND
|
|
|
|
mock_write_resource_insight_log.assert_called_once_with(
|
|
instance=ssr,
|
|
author=beneficiary,
|
|
event=EntityEvent.DELETED,
|
|
)
|
|
|
|
mock_update_shift_swap_request_message.apply_async.assert_called_once_with((ssr.pk,))
|
|
|
|
|
|
@pytest.mark.django_db
|
|
@pytest.mark.parametrize(
|
|
"role,expected_status",
|
|
[
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_204_NO_CONTENT),
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_204_NO_CONTENT),
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
|
|
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
|
|
],
|
|
)
|
|
def test_delete_own_ssr_permissions(ssr_setup, make_user_auth_headers, role, expected_status):
|
|
ssr, beneficiary, token, _ = ssr_setup(beneficiary_role=role)
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
|
|
|
|
response = client.delete(url, format="json", **make_user_auth_headers(beneficiary, token))
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_delete_others_ssr_permissions(ssr_setup, make_user_auth_headers):
|
|
ssr, _, token, benefactor = ssr_setup()
|
|
assert benefactor.role == LegacyAccessControlRole.ADMIN
|
|
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
|
|
|
|
response = client.delete(url, format="json", **make_user_auth_headers(benefactor, token))
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_take(ssr_setup, make_user_auth_headers):
|
|
ssr, _, token, benefactor = ssr_setup()
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-take", kwargs={"pk": ssr.public_primary_key})
|
|
auth_headers = make_user_auth_headers(benefactor, token)
|
|
|
|
response = client.post(url, format="json", **auth_headers)
|
|
response_json = response.json()
|
|
updated_at = response_json["updated_at"]
|
|
expected_response = _build_expected_update_response(
|
|
ssr, {}, updated_at, status="taken", benefactor=benefactor.public_primary_key
|
|
)
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
assert response_json == expected_response
|
|
assert updated_at != serialize_datetime_as_utc_timestamp(
|
|
ssr.updated_at
|
|
) # validate that updated_at is auto-updated on take
|
|
|
|
url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
|
|
response = client.get(url, format="json", **auth_headers)
|
|
response_json = response.json()
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
assert response_json == expected_response
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_benficiary_tries_to_take_their_own_ssr(ssr_setup, make_user_auth_headers):
|
|
ssr, beneficiary, token, _ = ssr_setup()
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-take", kwargs={"pk": ssr.public_primary_key})
|
|
|
|
response = client.post(url, format="json", **make_user_auth_headers(beneficiary, token))
|
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_take_already_taken_ssr(ssr_setup, make_user_auth_headers):
|
|
ssr, _, token, benefactor = ssr_setup()
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-take", kwargs={"pk": ssr.public_primary_key})
|
|
|
|
response = client.post(url, format="json", **make_user_auth_headers(benefactor, token))
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
# try to take the SSR again
|
|
response = client.post(url, format="json", **make_user_auth_headers(benefactor, token))
|
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_take_past_due_ssr(ssr_setup, make_user_auth_headers):
|
|
ssr, _, token, benefactor = ssr_setup()
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-take", kwargs={"pk": ssr.public_primary_key})
|
|
|
|
ssr.swap_start = tomorrow - datetime.timedelta(days=5)
|
|
ssr.save()
|
|
|
|
response = client.post(url, format="json", **make_user_auth_headers(benefactor, token))
|
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_take_deleted_ssr(ssr_setup, make_user_auth_headers):
|
|
ssr, _, token, benefactor = ssr_setup()
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-take", kwargs={"pk": ssr.public_primary_key})
|
|
|
|
ssr.delete()
|
|
|
|
response = client.post(url, format="json", **make_user_auth_headers(benefactor, token))
|
|
assert response.status_code == status.HTTP_404_NOT_FOUND
|
|
|
|
|
|
@patch("apps.api.views.shift_swap.ShiftSwapViewSet.take", return_value=mock_success_response)
|
|
@pytest.mark.django_db
|
|
@pytest.mark.parametrize(
|
|
"role,expected_status",
|
|
[
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
|
|
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
|
|
],
|
|
)
|
|
def test_take_permissions(
|
|
mock_endpoint_handler,
|
|
ssr_setup,
|
|
make_user_auth_headers,
|
|
role,
|
|
expected_status,
|
|
):
|
|
ssr, _, token, benefactor = ssr_setup(benefactor_role=role)
|
|
client = APIClient()
|
|
url = reverse("api-internal:shift_swap-take", kwargs={"pk": ssr.public_primary_key})
|
|
|
|
response = client.post(url, format="json", **make_user_auth_headers(benefactor, token))
|
|
assert response.status_code == expected_status
|