oncall-engine/engine/common/tests/test_custom_fields.py

320 lines
12 KiB
Python
Raw Permalink Normal View History

import datetime
from zoneinfo import ZoneInfo
import pytest
import pytz
from rest_framework import serializers
import common.api_helpers.custom_fields as cf
from common.api_helpers.exceptions import BadRequest
class TestTimeZoneField:
@pytest.mark.parametrize("tz", pytz.all_timezones)
def test_valid_timezones(self, tz):
class MySerializer(serializers.Serializer):
tz = cf.TimeZoneField()
try:
serializer = MySerializer(data={"tz": tz})
serializer.is_valid(raise_exception=True)
assert serializer.validated_data["tz"] == tz
except Exception:
pytest.fail()
def test_invalid_timezone(self):
class MySerializer(serializers.Serializer):
tz = cf.TimeZoneField()
with pytest.raises(serializers.ValidationError, match="Invalid timezone"):
serializer = MySerializer(data={"tz": "potato"})
serializer.is_valid(raise_exception=True)
def test_it_works_with_allow_null(self):
class MySerializer(serializers.Serializer):
tz = cf.TimeZoneField(allow_null=True)
try:
serializer = MySerializer(data={"tz": None})
serializer.is_valid(raise_exception=True)
assert serializer.validated_data["tz"] is None
serializer = MySerializer(data={"tz": "UTC"})
serializer.is_valid(raise_exception=True)
assert serializer.validated_data["tz"] == "UTC"
except Exception:
pytest.fail()
def test_it_works_with_required(self):
class MySerializer(serializers.Serializer):
tz = cf.TimeZoneField(required=True)
with pytest.raises(serializers.ValidationError, match="This field is required"):
serializer = MySerializer(data={})
serializer.is_valid(raise_exception=True)
try:
serializer = MySerializer(data={"tz": "UTC"})
serializer.is_valid(raise_exception=True)
assert serializer.validated_data["tz"] == "UTC"
except Exception:
pytest.fail()
class TestTimeZoneAwareDatetimeField:
@pytest.mark.parametrize(
"test_case,expected_persisted_value",
[
# UTC format
("2023-07-20T12:00:00Z", datetime.datetime(2023, 7, 20, 12, 0, 0, tzinfo=ZoneInfo("UTC"))),
# UTC format w/ microseconds
("2023-07-20T12:00:00.245652Z", datetime.datetime(2023, 7, 20, 12, 0, 0, 245652, tzinfo=ZoneInfo("UTC"))),
# UTC offset w/ colons + no microseconds
("2023-07-20T12:00:00+07:00", datetime.datetime(2023, 7, 20, 5, 0, 0, tzinfo=ZoneInfo("UTC"))),
# UTC offset w/ colons + microseconds
(
"2023-07-20T12:00:00.245652+07:00",
datetime.datetime(2023, 7, 20, 5, 0, 0, 245652, tzinfo=ZoneInfo("UTC")),
),
# UTC offset w/ no colons + no microseconds
("2023-07-20T12:00:00+0700", datetime.datetime(2023, 7, 20, 5, 0, 0, tzinfo=ZoneInfo("UTC"))),
# UTC offset w/ no colons + microseconds
(
"2023-07-20T12:00:00.245652+0700",
datetime.datetime(2023, 7, 20, 5, 0, 0, 245652, tzinfo=ZoneInfo("UTC")),
),
("2023-07-20 12:00:00", None),
("20230720T120000Z", None),
],
)
def test_various_datetimes(self, test_case, expected_persisted_value):
class MySerializer(serializers.Serializer):
dt = cf.TimeZoneAwareDatetimeField()
serializer = MySerializer(data={"dt": test_case})
if expected_persisted_value:
serializer.is_valid(raise_exception=True)
assert serializer.validated_data["dt"] == expected_persisted_value
else:
with pytest.raises(serializers.ValidationError):
serializer.is_valid(raise_exception=True)
class TestSlackChannelsFilteredByOrganizationSlackWorkspaceField:
class MockRequest:
def __init__(self, user) -> None:
self.user = user
class MySerializer(serializers.Serializer):
slack_channel_id = cf.SlackChannelsFilteredByOrganizationSlackWorkspaceField()
@pytest.mark.django_db
def test_org_does_not_have_slack_connected(
self,
make_organization,
make_user_for_organization,
):
organization = make_organization()
user = make_user_for_organization(organization)
serializer = self.MySerializer(
data={"slack_channel_id": "abcd"},
context={"request": self.MockRequest(user)},
)
with pytest.raises(BadRequest) as excinfo:
serializer.is_valid(raise_exception=True)
assert excinfo.value.detail == "Slack isn't connected to this workspace"
assert excinfo.value.status_code == 400
@pytest.mark.django_db
def test_org_channel_doesnt_belong_to_org(
self,
make_organization,
make_user_for_organization,
make_slack_team_identity,
make_slack_channel,
):
slack_channel1_id = "FOO"
slack_channel2_id = "BAR"
slack_team_identity1 = make_slack_team_identity()
make_slack_channel(slack_team_identity1, slack_id=slack_channel1_id)
slack_team_identity2 = make_slack_team_identity()
make_slack_channel(slack_team_identity2, slack_id=slack_channel2_id)
organization = make_organization(slack_team_identity=slack_team_identity1)
user = make_user_for_organization(organization)
serializer = self.MySerializer(
data={"slack_channel_id": slack_channel2_id},
context={"request": self.MockRequest(user)},
)
with pytest.raises(serializers.ValidationError) as excinfo:
serializer.is_valid(raise_exception=True)
assert excinfo.value.detail == {"slack_channel_id": ["Slack channel does not exist"]}
@pytest.mark.django_db
def test_invalid_slack_channel(
self,
make_organization,
make_user_for_organization,
make_slack_team_identity,
make_slack_channel,
):
slack_channel_id = "FOO"
slack_team_identity = make_slack_team_identity()
make_slack_channel(slack_team_identity, slack_id=slack_channel_id)
organization = make_organization(slack_team_identity=slack_team_identity)
user = make_user_for_organization(organization)
serializer = self.MySerializer(
data={"slack_channel_id": 1},
context={"request": self.MockRequest(user)},
)
with pytest.raises(serializers.ValidationError) as excinfo:
serializer.is_valid(raise_exception=True)
assert excinfo.value.detail == {"slack_channel_id": ["Invalid Slack channel"]}
@pytest.mark.django_db
def test_valid(
self,
make_organization,
make_user_for_organization,
make_slack_team_identity,
make_slack_channel,
):
slack_channel_id = "FOO"
slack_team_identity = make_slack_team_identity()
slack_channel = make_slack_channel(slack_team_identity, slack_id=slack_channel_id)
organization = make_organization(slack_team_identity=slack_team_identity)
user = make_user_for_organization(organization)
context = {"request": self.MockRequest(user)}
serializer = self.MySerializer(data={"slack_channel_id": slack_channel_id}, context=context)
serializer.is_valid(raise_exception=True)
assert serializer.validated_data["slack_channel_id"] == slack_channel
# case insensitive
serializer = self.MySerializer(data={"slack_channel_id": slack_channel_id.lower()}, context=context)
serializer.is_valid(raise_exception=True)
assert serializer.validated_data["slack_channel_id"] == slack_channel
feat: convert `schedule.channel` (char field) to `schedule.slack_channel` (foreign key) (#5199) # What this PR does `OnCallSchedule` equivalent of https://github.com/grafana/oncall/pull/5191. **NOTE**: merge after https://github.com/grafana/oncall/pull/5224 (so that I can use some of the new serializer fields defined in there) ### Migration ```bash Running migrations: │ │ source=engine:app google_trace_id=none logger=apps.schedules.migrations.0019_auto_20241021_1735 Starting migration to populate slack_channel field. │ │ source=engine:app google_trace_id=none logger=apps.schedules.migrations.0019_auto_20241021_1735 Total schedules to process: 1 │ │ source=engine:app google_trace_id=none logger=apps.schedules.migrations.0019_auto_20241021_1735 Schedule 26 updated with SlackChannel 2 (slack_id: C043LL6RTS7). │ │ source=engine:app google_trace_id=none logger=apps.schedules.migrations.0019_auto_20241021_1735 Bulk updated 1 OnCallSchedules with their Slack channel. │ │ source=engine:app google_trace_id=none logger=apps.schedules.migrations.0019_auto_20241021_1735 Finished migration. Total schedules processed: 1. Schedules updated: 1. Missing SlackChannels: 0. │ │ Applying schedules.0019_auto_20241021_1735... OK ``` ### Tested Public API ```txt POST {{oncall_host}}/api/v1/schedules/ Authorization: {{oncall_api_key}} Content-Type: application/json { "name": "Demo testy testy2", "type": "web", "time_zone": "America/Los_Angeles", "slack": { "channel_id": "C05PPLYN1U1" } } HTTP/1.1 201 Created Content-Type: application/json Vary: Accept, Origin Allow: GET, POST, HEAD, OPTIONS X-Frame-Options: DENY Content-Length: 198 X-Content-Type-Options: nosniff Referrer-Policy: same-origin Cross-Origin-Opener-Policy: same-origin { "id": "SBBN73UTUTVCE", "team_id": null, "name": "Demo testy testy2", "time_zone": "America/Los_Angeles", "on_call_now": [], "shifts": [], "slack": { "channel_id": "C05PPLYN1U1", "user_group_id": null }, "type": "web" } ``` ### Tested via UI (eg; internal API) https://www.loom.com/share/e66bf3468b144dd782da5eb6e0bfd0af ## 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.
2024-11-04 14:27:21 -05:00
class TestSlackUserGroupsFilteredByOrganizationSlackWorkspaceField:
class MockRequest:
def __init__(self, user) -> None:
self.user = user
class MySerializer(serializers.Serializer):
slack_user_group_id = cf.SlackUserGroupsFilteredByOrganizationSlackWorkspaceField()
@pytest.mark.django_db
def test_org_does_not_have_slack_connected(
self,
make_organization,
make_user_for_organization,
):
organization = make_organization()
user = make_user_for_organization(organization)
serializer = self.MySerializer(
data={"slack_user_group_id": "abcd"},
context={"request": self.MockRequest(user)},
)
with pytest.raises(BadRequest) as excinfo:
serializer.is_valid(raise_exception=True)
assert excinfo.value.detail == "Slack isn't connected to this workspace"
assert excinfo.value.status_code == 400
@pytest.mark.django_db
def test_org_user_group_doesnt_belong_to_org(
self,
make_organization,
make_user_for_organization,
make_slack_team_identity,
make_slack_user_group,
):
slack_user_group1_id = "FOO"
slack_user_group2_id = "BAR"
slack_team_identity1 = make_slack_team_identity()
make_slack_user_group(slack_team_identity1, slack_id=slack_user_group1_id)
slack_team_identity2 = make_slack_team_identity()
make_slack_user_group(slack_team_identity2, slack_id=slack_user_group2_id)
organization = make_organization(slack_team_identity=slack_team_identity1)
user = make_user_for_organization(organization)
serializer = self.MySerializer(
data={"slack_user_group_id": slack_user_group2_id},
context={"request": self.MockRequest(user)},
)
with pytest.raises(serializers.ValidationError) as excinfo:
serializer.is_valid(raise_exception=True)
assert excinfo.value.detail == {"slack_user_group_id": ["Slack user group does not exist"]}
@pytest.mark.django_db
def test_invalid_slack_user_group(
self,
make_organization,
make_user_for_organization,
make_slack_team_identity,
make_slack_user_group,
):
slack_user_group_id = "FOO"
slack_team_identity = make_slack_team_identity()
make_slack_user_group(slack_team_identity, slack_id=slack_user_group_id)
organization = make_organization(slack_team_identity=slack_team_identity)
user = make_user_for_organization(organization)
serializer = self.MySerializer(
data={"slack_user_group_id": 1},
context={"request": self.MockRequest(user)},
)
with pytest.raises(serializers.ValidationError) as excinfo:
serializer.is_valid(raise_exception=True)
assert excinfo.value.detail == {"slack_user_group_id": ["Invalid Slack user group"]}
@pytest.mark.django_db
def test_valid(
self,
make_organization,
make_user_for_organization,
make_slack_team_identity,
make_slack_user_group,
):
slack_user_group_id = "FOO"
slack_team_identity = make_slack_team_identity()
slack_user_group = make_slack_user_group(slack_team_identity, slack_id=slack_user_group_id)
organization = make_organization(slack_team_identity=slack_team_identity)
user = make_user_for_organization(organization)
context = {"request": self.MockRequest(user)}
serializer = self.MySerializer(data={"slack_user_group_id": slack_user_group_id}, context=context)
serializer.is_valid(raise_exception=True)
assert serializer.validated_data["slack_user_group_id"] == slack_user_group
# case insensitive
serializer = self.MySerializer(data={"slack_user_group_id": slack_user_group_id.lower()}, context=context)
serializer.is_valid(raise_exception=True)
assert serializer.validated_data["slack_user_group_id"] == slack_user_group