diff --git a/CHANGELOG.md b/CHANGELOG.md index 21ca880541..2547d8791e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Fixed + +- Prohibit creating & updating past overrides ([1474](https://github.com/grafana/oncall/pull/1474)) + ## v1.1.33 (2023-03-07) ### Fixed diff --git a/engine/apps/api/serializers/on_call_shifts.py b/engine/apps/api/serializers/on_call_shifts.py index 3ad11ef4c4..607dca30a6 100644 --- a/engine/apps/api/serializers/on_call_shifts.py +++ b/engine/apps/api/serializers/on_call_shifts.py @@ -1,3 +1,4 @@ +from django.utils import timezone from rest_framework import serializers from apps.schedules.models import CustomOnCallShift, OnCallScheduleWeb @@ -93,10 +94,13 @@ def validate_rolling_users(self, rolling_users): result.append(users_dict) return result - def _validate_shift_end(self, start, end): + def _validate_shift_end(self, start, end, event_type): if end <= start: raise serializers.ValidationError({"shift_end": ["Incorrect shift end date"]}) + if event_type == CustomOnCallShift.TYPE_OVERRIDE and timezone.now() > end: + raise serializers.ValidationError({"shift_end": ["Cannot create or update an override in the past"]}) + def _validate_frequency(self, frequency, event_type, rolling_users, interval, by_day, until): if frequency is None: if rolling_users and len(rolling_users) > 1: @@ -157,7 +161,7 @@ def _correct_validated_data(self, event_type, validated_data): # convert shift_end into internal value and validate raw_shift_end = self.initial_data["shift_end"] shift_end = serializers.DateTimeField().to_internal_value(raw_shift_end) - self._validate_shift_end(validated_data["start"], shift_end) + self._validate_shift_end(validated_data["start"], shift_end, event_type) validated_data["duration"] = shift_end - validated_data["start"] if validated_data.get("schedule"): diff --git a/engine/apps/api/tests/test_oncall_shift.py b/engine/apps/api/tests/test_oncall_shift.py index ee4a6bea56..7351abb000 100644 --- a/engine/apps/api/tests/test_oncall_shift.py +++ b/engine/apps/api/tests/test_oncall_shift.py @@ -921,6 +921,30 @@ def test_create_on_call_shift_override_invalid_data(on_call_shift_internal_api_s assert response.data["frequency"][0] == "Cannot set 'frequency' for shifts with type 'override'" +@pytest.mark.django_db +def test_create_on_call_shift_override_in_past(on_call_shift_internal_api_setup, make_user_auth_headers): + token, user1, _, _, schedule = on_call_shift_internal_api_setup + client = APIClient() + url = reverse("api-internal:oncall_shifts-list") + start_date = timezone.now().replace(microsecond=0, tzinfo=None) - timezone.timedelta(hours=2) + + data = { + "title": "Test Shift Override", + "type": CustomOnCallShift.TYPE_OVERRIDE, + "schedule": schedule.public_primary_key, + "priority_level": 0, + "shift_start": start_date.strftime("%Y-%m-%dT%H:%M:%SZ"), + "shift_end": (start_date + timezone.timedelta(hours=1)).strftime("%Y-%m-%dT%H:%M:%SZ"), + "rotation_start": start_date.strftime("%Y-%m-%dT%H:%M:%SZ"), + "rolling_users": [[user1.public_primary_key]], + } + + response = client.post(url, data, format="json", **make_user_auth_headers(user1, token)) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data["shift_end"][0] == "Cannot create or update an override in the past" + + @pytest.mark.django_db @pytest.mark.parametrize( "role,expected_status",