Skip to content

Commit

Permalink
chore: add more slack related tests (#5227)
Browse files Browse the repository at this point in the history
Follow up test PR to #5199 and
#5224
  • Loading branch information
joeyorlando authored Nov 4, 2024
1 parent 4a5c426 commit 14c1b37
Show file tree
Hide file tree
Showing 13 changed files with 318 additions and 24 deletions.
4 changes: 2 additions & 2 deletions engine/apps/slack/scenarios/slack_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def process_scenario(
clean_slack_channel_leftovers.apply_async((slack_team_identity.id, slack_id))


class SlackChannelUnArchivedEventStep(scenario_step.ScenarioStep):
class SlackChannelUnarchivedEventStep(scenario_step.ScenarioStep):
def process_scenario(
self,
slack_user_identity: "SlackUserIdentity",
Expand Down Expand Up @@ -126,6 +126,6 @@ def process_scenario(
{
"payload_type": PayloadType.EVENT_CALLBACK,
"event_type": EventType.CHANNEL_UNARCHIVED,
"step": SlackChannelUnArchivedEventStep,
"step": SlackChannelUnarchivedEventStep,
},
]
41 changes: 32 additions & 9 deletions engine/apps/slack/tasks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
import random
from typing import Optional
import typing

from celery import uuid as celery_uuid
from celery.exceptions import Retry
Expand Down Expand Up @@ -433,7 +433,7 @@ def populate_slack_channels():


def start_populate_slack_channels_for_team(
slack_team_identity_id: int, delay: int, cursor: Optional[str] = None
slack_team_identity_id: int, delay: int, cursor: typing.Optional[str] = None
) -> None:
# save active task id in cache to make only one populate task active per team
task_id = celery_uuid()
Expand All @@ -445,7 +445,7 @@ def start_populate_slack_channels_for_team(
@shared_dedicated_queue_retry_task(
autoretry_for=(Exception,), retry_backoff=True, max_retries=1 if settings.DEBUG else None
)
def populate_slack_channels_for_team(slack_team_identity_id: int, cursor: Optional[str] = None) -> None:
def populate_slack_channels_for_team(slack_team_identity_id: int, cursor: typing.Optional[str] = None) -> None:
"""
Make paginated request to get slack channels. On ratelimit - update info for got channels, save collected channels
ids in cache and restart the task with the last successful pagination cursor to avoid any data loss during delay
Expand Down Expand Up @@ -539,7 +539,7 @@ def populate_slack_channels_for_team(slack_team_identity_id: int, cursor: Option


@shared_dedicated_queue_retry_task(autoretry_for=(Exception,), retry_backoff=True, max_retries=0)
def clean_slack_integration_leftovers(organization_id, *args, **kwargs):
def clean_slack_integration_leftovers(organization_id: int, *args, **kwargs) -> None:
"""
This task removes binding to slack (e.g ChannelFilter's slack channel) for a given organization.
It is used when user changes slack integration.
Expand All @@ -549,19 +549,23 @@ def clean_slack_integration_leftovers(organization_id, *args, **kwargs):

logger.info(f"Cleaning up for organization {organization_id}")
ChannelFilter.objects.filter(alert_receive_channel__organization_id=organization_id).update(slack_channel=None)
OnCallSchedule.objects.filter(organization_id=organization_id).update(channel=None, user_group=None)
OnCallSchedule.objects.filter(organization_id=organization_id).update(slack_channel=None, user_group=None)


@shared_dedicated_queue_retry_task(autoretry_for=(Exception,), retry_backoff=True, max_retries=10)
def clean_slack_channel_leftovers(slack_team_identity_id, slack_channel_id):
def clean_slack_channel_leftovers(slack_team_identity_id: int, slack_channel_id: str) -> None:
"""
This task removes binding to slack channel after a channel is archived in Slack.
**NOTE**: this is only needed for Slack Channel archive. If a channel is deleted, we simply remove references
to that channel via `on_delete=models.SET_NULL`.
"""
from apps.alerts.models import ChannelFilter
from apps.schedules.models import OnCallSchedule
from apps.slack.models import SlackTeamIdentity
from apps.user_management.models import Organization

orgs_to_clean_default_slack_channel: typing.List[Organization] = []

try:
sti = SlackTeamIdentity.objects.get(id=slack_team_identity_id)
Expand All @@ -572,6 +576,25 @@ def clean_slack_channel_leftovers(slack_team_identity_id, slack_channel_id):
return

for org in sti.organizations.all():
ChannelFilter.objects.filter(alert_receive_channel__organization=org, slack_channel_id=slack_channel_id).update(
slack_channel_id=None
)
org_id = org.id

if org.default_slack_channel_slack_id == slack_channel_id:
logger.info(
f"Set default_slack_channel to None for org_id={org_id} slack_channel_id={slack_channel_id} since slack_channel is arcived or deleted"
)
org.default_slack_channel = None
orgs_to_clean_default_slack_channel.append(org)

# The channel no longer exists, so update any integration routes (ie. ChannelFilter) or schedules
# that reference it
ChannelFilter.objects.filter(
alert_receive_channel__organization=org,
slack_channel__slack_id=slack_channel_id,
).update(slack_channel=None)

OnCallSchedule.objects.filter(
organization_id=org_id,
slack_channel__slack_id=slack_channel_id,
).update(slack_channel=None)

Organization.objects.bulk_update(orgs_to_clean_default_slack_channel, ["default_slack_channel"], batch_size=5000)
217 changes: 217 additions & 0 deletions engine/apps/slack/tests/scenario_steps/test_slack_channel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
from unittest.mock import patch

import pytest
from django.utils import timezone

from apps.slack.models import SlackChannel
from apps.slack.scenarios import slack_channel as slack_channel_scenarios


@pytest.mark.django_db
class TestSlackChannelCreatedOrRenamedEventStep:
def test_process_scenario_channel_created(
self,
make_organization_and_user_with_slack_identities,
) -> None:
(
organization,
user,
slack_team_identity,
slack_user_identity,
) = make_organization_and_user_with_slack_identities()
slack_channel_id = "C12345678"
channel_name = "new-channel"
payload = {
"event": {
"channel": {
"id": slack_channel_id,
"name": channel_name,
}
}
}

# Ensure the SlackChannel does not exist
assert not SlackChannel.objects.filter(
slack_id=slack_channel_id,
slack_team_identity=slack_team_identity,
).exists()

step = slack_channel_scenarios.SlackChannelCreatedOrRenamedEventStep(slack_team_identity, organization, user)
step.process_scenario(slack_user_identity, slack_team_identity, payload)

# Now the SlackChannel should exist with correct data
slack_channel = SlackChannel.objects.get(
slack_id=slack_channel_id,
slack_team_identity=slack_team_identity,
)
assert slack_channel.name == channel_name
assert slack_channel.last_populated == timezone.now().date()

def test_process_scenario_channel_renamed(
self,
make_organization_and_user_with_slack_identities,
make_slack_channel,
) -> None:
(
organization,
user,
slack_team_identity,
slack_user_identity,
) = make_organization_and_user_with_slack_identities()
slack_channel = make_slack_channel(slack_team_identity)
slack_channel_id = slack_channel.slack_id
new_name = "renamed-channel"
payload = {
"event": {
"channel": {
"id": slack_channel_id,
"name": new_name,
}
}
}

step = slack_channel_scenarios.SlackChannelCreatedOrRenamedEventStep(slack_team_identity, organization, user)
step.process_scenario(slack_user_identity, slack_team_identity, payload)

slack_channel.refresh_from_db()
assert slack_channel.name == new_name
assert slack_channel.last_populated == timezone.now().date()


@pytest.mark.django_db
class TestSlackChannelDeletedEventStep:
def test_process_scenario_channel_deleted(
self,
make_organization_and_user_with_slack_identities,
make_slack_channel,
) -> None:
(
organization,
user,
slack_team_identity,
slack_user_identity,
) = make_organization_and_user_with_slack_identities()
slack_channel = make_slack_channel(slack_team_identity)
slack_channel_id = slack_channel.slack_id

# Ensure the SlackChannel exists
assert SlackChannel.objects.filter(
slack_id=slack_channel_id,
slack_team_identity=slack_team_identity,
).exists()

step = slack_channel_scenarios.SlackChannelDeletedEventStep(slack_team_identity, organization, user)
step.process_scenario(slack_user_identity, slack_team_identity, {"event": {"channel": slack_channel_id}})

# Now the SlackChannel should not exist
assert not SlackChannel.objects.filter(
slack_id=slack_channel_id,
slack_team_identity=slack_team_identity,
).exists()

def test_process_scenario_channel_does_not_exist(
self,
make_organization_and_user_with_slack_identities,
) -> None:
(
organization,
user,
slack_team_identity,
slack_user_identity,
) = make_organization_and_user_with_slack_identities()
slack_channel_id = "C12345678"

# Ensure the SlackChannel does not exist
assert not SlackChannel.objects.filter(
slack_id=slack_channel_id,
slack_team_identity=slack_team_identity,
).exists()

step = slack_channel_scenarios.SlackChannelDeletedEventStep(slack_team_identity, organization, user)
# The step should not raise an exception even if the channel does not exist
step.process_scenario(slack_user_identity, slack_team_identity, {"event": {"channel": slack_channel_id}})

# Still, the SlackChannel does not exist
assert not SlackChannel.objects.filter(
slack_id=slack_channel_id,
slack_team_identity=slack_team_identity,
).exists()


@pytest.mark.django_db
class TestSlackChannelArchivedEventStep:
@patch("apps.slack.scenarios.slack_channel.clean_slack_channel_leftovers")
def test_process_scenario(
self,
mock_clean_slack_channel_leftovers,
make_organization_and_user_with_slack_identities,
make_slack_channel,
) -> None:
(
organization,
user,
slack_team_identity,
slack_user_identity,
) = make_organization_and_user_with_slack_identities()
slack_channel = make_slack_channel(slack_team_identity)
slack_channel_id = slack_channel.slack_id

assert slack_channel.is_archived is False

step = slack_channel_scenarios.SlackChannelArchivedEventStep(slack_team_identity, organization, user)
step.process_scenario(slack_user_identity, slack_team_identity, {"event": {"channel": slack_channel_id}})

slack_channel.refresh_from_db()

assert slack_channel.is_archived is True
mock_clean_slack_channel_leftovers.apply_async.assert_called_once_with(
(slack_team_identity.id, slack_channel_id)
)


@pytest.mark.django_db
class TestSlackChannelUnarchivedEventStep:
def test_process_scenario_channel_unarchived(
self,
make_organization_and_user_with_slack_identities,
make_slack_channel,
) -> None:
(
organization,
user,
slack_team_identity,
slack_user_identity,
) = make_organization_and_user_with_slack_identities()
slack_channel = make_slack_channel(slack_team_identity, is_archived=True)
slack_channel_id = slack_channel.slack_id

assert slack_channel.is_archived is True

step = slack_channel_scenarios.SlackChannelUnarchivedEventStep(slack_team_identity, organization, user)
step.process_scenario(slack_user_identity, slack_team_identity, {"event": {"channel": slack_channel_id}})

slack_channel.refresh_from_db()
assert slack_channel.is_archived is False

def test_process_scenario_channel_already_unarchived(
self,
make_organization_and_user_with_slack_identities,
make_slack_channel,
) -> None:
(
organization,
user,
slack_team_identity,
slack_user_identity,
) = make_organization_and_user_with_slack_identities()
slack_channel = make_slack_channel(slack_team_identity, is_archived=False)
slack_channel_id = slack_channel.slack_id

assert slack_channel.is_archived is False

step = slack_channel_scenarios.SlackChannelUnarchivedEventStep(slack_team_identity, organization, user)
step.process_scenario(slack_user_identity, slack_team_identity, {"event": {"channel": slack_channel_id}})

slack_channel.refresh_from_db()
# Ensure that is_archived remains False
assert slack_channel.is_archived is False
Loading

0 comments on commit 14c1b37

Please sign in to comment.