oncall-engine/engine/apps/slack/tests/test_user_group.py
Joey Orlando 93c92a7a4c
Update Slack user group for a schedule - handle paid_team_only Slack API error (#4793)
# What this PR does

Semi-related to https://github.com/grafana/oncall-private/issues/2131

Addresses occasional task failures for
`apps.slack.tasks.update_slack_user_group_for_schedules` when trying to
update a Slack user group for a non-paid Slack account. [Slack's
documentation](https://slack.com/help/articles/212906697-Create-a-user-group)
mentions this is a paid only feature, hence the error
([logs](https://ops.grafana-ops.net/goto/-AWfsrrIR?orgId=1) from an
actual task):
```
2024-08-08 16:20:36,613 source=engine:celery worker=ForkPoolWorker-16 task_id=6bdaae94-1552-4b6d-93e2-e2fa0bae57b1 task_name=apps.slack.tasks.update_slack_user_group_for_schedules name=apps.slack.models.slack_usergroup level=WARNING Slack usergroup S06LW5GJ88Z update failed: Slack API error! Response: {'ok': False, 'error': 'paid_teams_only'}
```

Updated our docs on our Slack integration to emphasize that this feature
_only_ works for paid Slack accounts

## 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-08-09 14:51:01 +00:00

245 lines
8.8 KiB
Python

from unittest.mock import PropertyMock, patch
import pytest
from apps.schedules.models.on_call_schedule import OnCallScheduleQuerySet, OnCallScheduleWeb
from apps.slack.client import SlackClient
from apps.slack.errors import (
SlackAPIError,
SlackAPIInvalidUsersError,
SlackAPITokenError,
SlackAPIUsergroupNotFoundError,
SlackAPIUsergroupPaidTeamOnlyError,
)
from apps.slack.models import SlackUserGroup
from apps.slack.tasks import (
populate_slack_usergroups_for_team,
start_update_slack_user_group_for_schedules,
update_slack_user_group_for_schedules,
)
from apps.slack.tests.conftest import build_slack_response
from apps.user_management.models import Organization
@pytest.mark.django_db
def test_update_members(make_organization_with_slack_team_identity, make_slack_user_group):
_, slack_team_identity = make_organization_with_slack_team_identity()
user_group = make_slack_user_group(slack_team_identity)
slack_ids = ["slack_id_1", "slack_id_2"]
with patch("apps.slack.client.SlackClient.usergroups_users_update") as mock_update:
user_group.update_members(slack_ids)
mock_update.assert_called_once()
assert user_group.members == slack_ids
@pytest.mark.django_db
@pytest.mark.parametrize(
"exception",
[
SlackAPITokenError,
SlackAPIUsergroupNotFoundError,
SlackAPIInvalidUsersError,
SlackAPIUsergroupPaidTeamOnlyError,
],
)
def test_slack_user_group_update_errors(
make_organization_with_slack_team_identity,
make_slack_user_group,
exception,
):
_, slack_team_identity = make_organization_with_slack_team_identity()
user_group = make_slack_user_group(slack_team_identity=slack_team_identity)
slack_ids = ["slack_id_1", "slack_id_2"]
with patch("apps.slack.client.SlackClient.usergroups_users_update", side_effect=exception("Error")) as mock_update:
user_group.update_members(slack_ids)
mock_update.assert_called_once()
assert user_group.members is None
@pytest.mark.django_db
def test_slack_user_group_update_errors_raise(
make_organization_with_slack_team_identity,
make_slack_user_group,
):
_, slack_team_identity = make_organization_with_slack_team_identity()
user_group = make_slack_user_group(slack_team_identity=slack_team_identity)
slack_ids = ["slack_id_1", "slack_id_2"]
with patch(
"apps.slack.client.SlackClient.usergroups_users_update", side_effect=SlackAPIError("Error")
) as mock_update:
with pytest.raises(SlackAPIError):
user_group.update_members(slack_ids)
mock_update.assert_called_once()
@pytest.mark.django_db
def test_oncall_slack_user_identities(
make_organization_with_slack_team_identity,
make_slack_user_group,
make_user_with_slack_user_identity,
make_user_for_organization,
):
organization, slack_team_identity = make_organization_with_slack_team_identity()
user_group = make_slack_user_group(slack_team_identity)
user_1, slack_user_identity_1 = make_user_with_slack_user_identity(
slack_team_identity, organization, slack_id="user_1"
)
user_2, slack_user_identity_2 = make_user_with_slack_user_identity(
slack_team_identity, organization, slack_id="user_2"
)
user_3 = make_user_for_organization(organization)
with patch.object(OnCallScheduleQuerySet, "get_oncall_users", return_value={"schedule1": [user_1, user_2, user_3]}):
assert set(user_group.oncall_slack_user_identities) == {slack_user_identity_1, slack_user_identity_2}
@pytest.mark.django_db
def test_update_oncall_members(
make_organization_with_slack_team_identity,
make_slack_user_group,
make_user_with_slack_user_identity,
):
organization, slack_team_identity = make_organization_with_slack_team_identity()
user_group = make_slack_user_group(slack_team_identity)
_, slack_user_identity_1 = make_user_with_slack_user_identity(
slack_team_identity, organization, slack_id="slack_id_1"
)
_, slack_user_identity_2 = make_user_with_slack_user_identity(
slack_team_identity, organization, slack_id="slack_id_2"
)
with patch.object(
SlackUserGroup, "oncall_slack_user_identities", new_callable=PropertyMock
) as oncall_slack_user_identities_mock:
oncall_slack_user_identities_mock.return_value = [slack_user_identity_1, slack_user_identity_2]
with patch.object(SlackUserGroup, "update_members") as update_members_mock:
user_group.update_oncall_members()
update_members_mock.assert_called()
@pytest.mark.django_db
def test_start_update_slack_user_group_for_schedules_organization_deleted(
make_organization_with_slack_team_identity, make_slack_user_group, make_schedule
):
organization, slack_team_identity = make_organization_with_slack_team_identity()
user_group = make_slack_user_group(slack_team_identity)
make_schedule(organization, schedule_class=OnCallScheduleWeb, user_group=user_group)
# check user group is updated
with patch.object(update_slack_user_group_for_schedules, "delay") as mock:
start_update_slack_user_group_for_schedules()
mock.assert_called_once_with(user_group_pk=user_group.pk)
# soft delete the organization
Organization.objects.filter(pk=organization.pk).delete()
# check user group is not updated for deleted organization
with patch.object(update_slack_user_group_for_schedules, "delay") as mock:
start_update_slack_user_group_for_schedules()
mock.assert_not_called()
@patch.object(
SlackClient,
"usergroups_users_list",
return_value=build_slack_response({"ok": True, "users": ["test_user_1", "test_user_2"]}),
)
@patch.object(
SlackClient,
"usergroups_list",
return_value=build_slack_response(
{
"ok": True,
"usergroups": [{"id": "test_slack_id", "name": "test_name", "handle": "test_handle", "date_delete": 0}],
}
),
)
@pytest.mark.django_db
def test_update_or_create_slack_usergroup_from_slack(
mock_usergroups_list, mock_usergroups_users_list, make_organization_with_slack_team_identity
):
_, slack_team_identity = make_organization_with_slack_team_identity()
SlackUserGroup.update_or_create_slack_usergroup_from_slack("test_slack_id", slack_team_identity)
usergroup = SlackUserGroup.objects.get()
assert usergroup.name == "test_name"
assert usergroup.handle == "test_handle"
assert usergroup.members == ["test_user_1", "test_user_2"]
assert usergroup.is_active
@patch.object(
SlackClient,
"usergroups_list",
return_value=build_slack_response(
{
"ok": True,
"usergroups": [{"id": "test_slack_id", "name": "test_name", "handle": "test_handle", "date_delete": 0}],
}
),
)
@pytest.mark.django_db
def test_update_or_create_slack_usergroup_from_slack_group_not_found(
mock_usergroups_list, make_organization_with_slack_team_identity
):
_, slack_team_identity = make_organization_with_slack_team_identity()
SlackUserGroup.update_or_create_slack_usergroup_from_slack("other_id", slack_team_identity)
# no group is created, no error is raised
assert SlackUserGroup.objects.count() == 0
@patch.object(
SlackClient,
"usergroups_users_list",
return_value=build_slack_response({"ok": True, "users": ["test_user_1", "test_user_2"]}),
)
@patch.object(
SlackClient,
"usergroups_list",
return_value=build_slack_response(
{
"ok": True,
"usergroups": [{"id": "test_slack_id", "name": "test_name", "handle": "test_handle", "date_delete": 0}],
}
),
)
@pytest.mark.django_db
def test_populate_slack_usergroups_for_team(
mock_usergroups_list, mock_usergroups_users_list, make_organization_with_slack_team_identity
):
_, slack_team_identity = make_organization_with_slack_team_identity()
populate_slack_usergroups_for_team(slack_team_identity.pk)
usergroup = SlackUserGroup.objects.get()
assert usergroup.name == "test_name"
assert usergroup.handle == "test_handle"
assert usergroup.members == ["test_user_1", "test_user_2"]
assert usergroup.is_active
@pytest.mark.django_db
def test_get_users_from_members_for_organization(
make_organization_with_slack_team_identity,
make_slack_user_group,
make_user_with_slack_user_identity,
):
organization, slack_team_identity = make_organization_with_slack_team_identity()
user_1, _ = make_user_with_slack_user_identity(slack_team_identity, organization, slack_id="slack_id_1")
user_2, _ = make_user_with_slack_user_identity(slack_team_identity, organization, slack_id="slack_id_2")
user_group = make_slack_user_group(slack_team_identity)
user_group.members = ["slack_id_1", "slack_id_2"]
user_group.save(update_fields=["members"])
users = user_group.get_users_from_members_for_organization(organization)
assert set(users) == {user_1, user_2}