Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add schedule shift type validation on create/preview #2789

Merged
merged 2 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix Slack acknowledgment reminders by @vadimkerr ([#2769](https://github.com/grafana/oncall/pull/2769))
- Fix issue with updating "Require resolution note" setting by @Ferril ([#2782](https://github.com/grafana/oncall/pull/2782))
- Don't send notifications about past SSRs when turning on info notifications by @vadimkerr ([#2783](https://github.com/grafana/oncall/pull/2783))
- Add schedule shift type validation on create/preview ([#2789](https://github.com/grafana/oncall/pull/2789))

### Added

Expand Down
8 changes: 7 additions & 1 deletion engine/apps/api/serializers/on_call_shifts.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.utils import timezone
from rest_framework import serializers

from apps.schedules.models import CustomOnCallShift, OnCallSchedule
from apps.schedules.models import CustomOnCallShift, OnCallSchedule, OnCallScheduleWeb
from apps.user_management.models import User
from common.api_helpers.custom_fields import (
OrganizationFilteredPrimaryKeyRelatedField,
Expand Down Expand Up @@ -87,6 +87,11 @@ def validate_by_day(self, by_day):
raise serializers.ValidationError(["Invalid day value."])
return by_day

def _validate_type(self, schedule, event_type):
if schedule and not isinstance(schedule, OnCallScheduleWeb) and event_type != CustomOnCallShift.TYPE_OVERRIDE:
# if this is not related to a web schedule, only allow override web events
raise serializers.ValidationError({"type": ["Invalid event type"]})

def validate_week_start(self, week_start):
if week_start is None:
week_start = CustomOnCallShift.MONDAY
Expand Down Expand Up @@ -158,6 +163,7 @@ def _correct_validated_data(self, event_type, validated_data):
"priority_level",
"rotation_start",
]
self._validate_type(validated_data.get("schedule"), event_type)
if event_type == CustomOnCallShift.TYPE_OVERRIDE:
for field in fields_to_update_for_overrides:
value = None
Expand Down
78 changes: 77 additions & 1 deletion engine/apps/api/tests/test_oncall_shift.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from rest_framework.test import APIClient

from apps.api.permissions import LegacyAccessControlRole
from apps.schedules.models import CustomOnCallShift, OnCallSchedule, OnCallScheduleWeb
from apps.schedules.models import CustomOnCallShift, OnCallSchedule, OnCallScheduleCalendar, OnCallScheduleWeb


@pytest.fixture()
Expand Down Expand Up @@ -59,6 +59,46 @@ def test_create_on_call_shift_rotation(on_call_shift_internal_api_setup, make_us
assert mock_refresh_schedule.called


@pytest.mark.django_db
def test_create_on_call_shift_rotation_invalid_type(
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=OnCallScheduleCalendar)

client = APIClient()
url = reverse("api-internal:oncall_shifts-list")
start_date = timezone.now().replace(microsecond=0, tzinfo=None)

data = {
"name": "Test Shift",
"type": CustomOnCallShift.TYPE_ROLLING_USERS_EVENT,
"schedule": schedule.public_primary_key,
"priority_level": 1,
"shift_start": start_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
"shift_end": (start_date + timezone.timedelta(hours=2)).strftime("%Y-%m-%dT%H:%M:%SZ"),
"rotation_start": start_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
"until": None,
"frequency": 1,
"interval": 1,
"by_day": [
CustomOnCallShift.ICAL_WEEKDAY_MAP[CustomOnCallShift.MONDAY],
CustomOnCallShift.ICAL_WEEKDAY_MAP[CustomOnCallShift.FRIDAY],
],
"week_start": CustomOnCallShift.ICAL_WEEKDAY_MAP[CustomOnCallShift.MONDAY],
"rolling_users": [[user.public_primary_key]],
}

with patch("apps.schedules.models.CustomOnCallShift.refresh_schedule") as mock_refresh_schedule:
response = client.post(url, data, format="json", **make_user_auth_headers(user, token))

assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.data["type"][0] == "Invalid event type"
assert not mock_refresh_schedule.called


@pytest.mark.django_db
def test_create_on_call_shift_rotation_missing_users(on_call_shift_internal_api_setup, make_user_auth_headers):
token, user1, user2, _, schedule = on_call_shift_internal_api_setup
Expand Down Expand Up @@ -1557,6 +1597,42 @@ def test_on_call_shift_preview(
assert returned_events == expected_events


@pytest.mark.django_db
def test_on_call_shift_preview_invalid_type(
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
make_schedule,
):
organization, user, token = make_organization_and_user_with_plugin_token()
client = APIClient()

schedule = make_schedule(organization, schedule_class=OnCallScheduleCalendar)

now = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
start_date = now - timezone.timedelta(days=7)
request_date = start_date

url = "{}?date={}&days={}".format(
reverse("api-internal:oncall_shifts-preview"), request_date.strftime("%Y-%m-%d"), 1
)
shift_start = (start_date + timezone.timedelta(hours=12)).strftime("%Y-%m-%dT%H:%M:%SZ")
shift_end = (start_date + timezone.timedelta(hours=13)).strftime("%Y-%m-%dT%H:%M:%SZ")
shift_data = {
"schedule": schedule.public_primary_key,
"type": CustomOnCallShift.TYPE_ROLLING_USERS_EVENT,
"rotation_start": shift_start,
"shift_start": shift_start,
"shift_end": shift_end,
"rolling_users": [[user.public_primary_key]],
"priority_level": 2,
"frequency": CustomOnCallShift.FREQUENCY_DAILY,
"interval": 1,
}
response = client.post(url, shift_data, format="json", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.data["type"][0] == "Invalid event type"


@pytest.mark.django_db
def test_on_call_shift_preview_without_users(
make_organization_and_user_with_plugin_token,
Expand Down