Files
2024-02-15 17:01:50 +00:00

357 lines
15 KiB
Python

import time
from unittest.mock import patch
import responses
from django.http import HttpResponse
from django.urls import reverse
from sentry.api.client import ApiClient
from sentry.integrations.msteams.card_builder.identity import build_linking_card
from sentry.integrations.msteams.link_identity import build_linking_url
from sentry.integrations.msteams.utils import ACTION_TYPE
from sentry.models.activity import Activity, ActivityIntegration
from sentry.models.authidentity import AuthIdentity
from sentry.models.authprovider import AuthProvider
from sentry.models.group import Group, GroupStatus
from sentry.models.groupassignee import GroupAssignee
from sentry.models.identity import Identity, IdentityStatus
from sentry.silo import SiloMode
from sentry.testutils.asserts import assert_mock_called_once_with_partial
from sentry.testutils.cases import APITestCase
from sentry.testutils.silo import assume_test_silo_mode, region_silo_test
from sentry.testutils.skips import requires_snuba
from sentry.utils import json
pytestmark = [requires_snuba]
@region_silo_test
class StatusActionTest(APITestCase):
def setUp(self):
super().setUp()
self.user = self.create_user(is_superuser=False)
owner = self.create_user()
self.org = self.create_organization(owner=owner)
self.team = self.create_team(organization=self.org, members=[self.user])
with assume_test_silo_mode(SiloMode.CONTROL):
self.integration = self.create_provider_integration(
provider="msteams",
name="Fellowship of the Ring",
external_id="f3ll0wsh1p",
metadata={
"service_url": "https://smba.trafficmanager.net/amer",
"access_token": "y0u_5h4ll_n07_p455",
"expires_at": int(time.time()) + 86400,
},
)
self.create_organization_integration(
organization_id=self.org.id, integration=self.integration
)
self.idp = self.create_identity_provider(type="msteams", external_id="f3ll0wsh1p")
self.identity = Identity.objects.create(
external_id="g4nd4lf",
idp=self.idp,
user=self.user,
status=IdentityStatus.VALID,
scopes=[],
)
self.project1 = self.create_project(organization=self.org)
self.event1 = self.store_event(
data={"message": "oh no"},
project_id=self.project1.id,
)
assert self.event1.group is not None
self.group1 = self.event1.group
def post_webhook(
self,
action_type="dummy",
user_id="g4nd4lf",
team_id="f3ll0wsh1p",
tenant_id="m17hr4nd1r",
conversation_type="channel",
channel_id=None,
group_id=None,
resolve_input=None,
ignore_input=None,
assign_input=None,
):
replyToId = "12345"
channel_data = {"tenant": {"id": tenant_id}}
if conversation_type == "channel":
conversation_id = channel_id if channel_id else team_id
channel_data["team"] = {"id": team_id}
channel_data["channel"] = {"id": conversation_id}
else:
conversation_id = "user_conversation_id"
responses.add(
method=responses.PUT,
url="https://smba.trafficmanager.net/amer/v3/conversations/%s/activities/%s"
% (conversation_id, replyToId),
json={},
)
payload = {
"type": "message",
"from": {"id": user_id},
"channelData": channel_data,
"conversation": {"conversationType": conversation_type, "id": conversation_id},
"value": {
"payload": {
"groupId": group_id or self.group1.id,
"eventId": self.event1.event_id,
"actionType": action_type,
"rules": [],
"integrationId": self.integration.id,
},
"resolveInput": resolve_input,
"ignoreInput": ignore_input,
"assignInput": assign_input,
},
"replyToId": replyToId,
}
webhook_url = reverse("sentry-integration-msteams-webhooks")
return self.client.post(webhook_url, data=payload)
@patch("sentry.integrations.msteams.webhook.verify_signature", return_vaue=True)
@patch("sentry.integrations.msteams.link_identity.sign")
@responses.activate
def test_ask_linking(self, sign, verify):
sign.return_value = "signed_parameters"
def user_conversation_id_callback(request):
payload = json.loads(request.body)
if payload["members"] == [{"id": "s4ur0n"}] and payload["channelData"] == {
"tenant": {"id": "7h3_gr347"}
}:
return (200, {}, json.dumps({"id": "d4rk_l0rd"}))
return (404, {}, json.dumps({}))
responses.add_callback(
method=responses.POST,
url="https://smba.trafficmanager.net/amer/v3/conversations",
callback=user_conversation_id_callback,
)
responses.add(
method=responses.POST,
url="https://smba.trafficmanager.net/amer/v3/conversations/d4rk_l0rd/activities",
status=200,
json={},
)
resp = self.post_webhook(user_id="s4ur0n", tenant_id="7h3_gr347")
# assert sign is called with the right arguments
assert sign.call_args.kwargs == {
"integration_id": self.integration.id,
"organization_id": self.org.id,
"teams_user_id": "s4ur0n",
"team_id": "f3ll0wsh1p",
"tenant_id": "7h3_gr347",
}
linking_url = build_linking_url(
self.integration, self.org, "s4ur0n", "f3ll0wsh1p", "7h3_gr347"
)
data = json.loads(responses.calls[1].request.body)
assert resp.status_code == 201
assert "attachments" in data
assert data["attachments"][0]["content"] == build_linking_card(linking_url)
@responses.activate
@patch("sentry.integrations.msteams.webhook.verify_signature", return_value=True)
def test_ignore_issue(self, verify):
resp = self.post_webhook(action_type=ACTION_TYPE.IGNORE, ignore_input="-1")
self.group1 = Group.objects.get(id=self.group1.id)
assert resp.status_code == 200, resp.content
assert self.group1.get_status() == GroupStatus.IGNORED
@responses.activate
@patch("sentry.integrations.msteams.webhook.verify_signature", return_value=True)
def test_no_ignore_input(self, verify):
resp = self.post_webhook(action_type=ACTION_TYPE.IGNORE, ignore_input="")
self.group1 = Group.objects.get(id=self.group1.id)
assert resp.status_code == 200, resp.content
assert self.group1.get_status() == GroupStatus.UNRESOLVED
@responses.activate
@patch("sentry.integrations.msteams.webhook.verify_signature", return_value=True)
def test_ignore_issue_with_additional_user_auth(self, verify):
with assume_test_silo_mode(SiloMode.CONTROL):
auth_idp = AuthProvider.objects.create(organization_id=self.org.id, provider="nobody")
AuthIdentity.objects.create(auth_provider=auth_idp, user=self.user)
resp = self.post_webhook(action_type=ACTION_TYPE.IGNORE, ignore_input="-1")
self.group1 = Group.objects.get(id=self.group1.id)
assert resp.status_code == 200, resp.content
assert self.group1.get_status() == GroupStatus.IGNORED
@responses.activate
@patch.object(ApiClient, "put")
@patch("sentry.integrations.msteams.webhook.verify_signature", return_value=True)
def test_ignore_with_params(self, verify, client_put):
client_put.return_value = HttpResponse(status=200)
self.post_webhook(action_type=ACTION_TYPE.IGNORE, ignore_input="100")
expected_data = {"status": "ignored", "statusDetails": {"ignoreCount": 100}}
assert_mock_called_once_with_partial(client_put, data=expected_data)
@responses.activate
@patch("sentry.integrations.msteams.webhook.verify_signature", return_value=True)
def test_assign_to_team(self, verify):
resp = self.post_webhook(
action_type=ACTION_TYPE.ASSIGN, assign_input=f"team:{self.team.id}"
)
assert resp.status_code == 200, resp.content
assert GroupAssignee.objects.filter(group=self.group1, team=self.team).exists()
activity = Activity.objects.filter(group=self.group1).first()
assert activity.data == {
"assignee": str(self.team.id),
"assigneeEmail": None,
"assigneeType": "team",
"integration": ActivityIntegration.MSTEAMS.value,
}
@responses.activate
@patch("sentry.integrations.msteams.webhook.verify_signature", return_value=True)
def test_assign_to_me(self, verify):
resp = self.post_webhook(action_type=ACTION_TYPE.ASSIGN, assign_input="ME")
assert resp.status_code == 200, resp.content
assert GroupAssignee.objects.filter(group=self.group1, user_id=self.user.id).exists()
assert b"Unassign" in responses.calls[0].request.body
assert f"Assigned to {self.user.email}".encode() in responses.calls[0].request.body
activity = Activity.objects.filter(group=self.group1).first()
assert activity.data == {
"assignee": str(self.user.id),
"assigneeEmail": self.user.email,
"assigneeType": "user",
"integration": ActivityIntegration.MSTEAMS.value,
}
@responses.activate
@patch("sentry.integrations.msteams.webhook.verify_signature", return_value=True)
def test_assign_to_me_personal_message(self, verify):
resp = self.post_webhook(
action_type=ACTION_TYPE.ASSIGN, assign_input="ME", conversation_type="personal"
)
assert resp.status_code == 200, resp.content
assert GroupAssignee.objects.filter(group=self.group1, user_id=self.user.id).exists()
assert b"Unassign" in responses.calls[0].request.body
assert "user_conversation_id" in responses.calls[0].request.url
assert f"Assigned to {self.user.email}".encode() in responses.calls[0].request.body
@responses.activate
@patch("sentry.integrations.msteams.webhook.verify_signature", return_value=True)
def test_assign_to_me_channel_message(self, verify):
resp = self.post_webhook(
action_type=ACTION_TYPE.ASSIGN, assign_input="ME", channel_id="some_channel_id"
)
assert resp.status_code == 200, resp.content
assert GroupAssignee.objects.filter(group=self.group1, user_id=self.user.id).exists()
assert b"Unassign" in responses.calls[0].request.body
assert "some_channel_id" in responses.calls[0].request.url
assert f"Assigned to {self.user.email}".encode() in responses.calls[0].request.body
@responses.activate
@patch("sentry.integrations.msteams.webhook.verify_signature", return_value=True)
def test_assign_to_me_multiple_identities(self, verify):
org2 = self.create_organization(owner=None)
with assume_test_silo_mode(SiloMode.CONTROL):
integration2 = self.create_provider_integration(
provider="msteams",
name="Army of Mordor",
external_id="54rum4n",
metadata={
"service_url": "https://smba.trafficmanager.net/amer",
"access_token": "y0u_h4v3_ch053n_d347h",
"expires_at": int(time.time()) + 86400,
},
)
self.create_organization_integration(organization_id=org2.id, integration=integration2)
idp2 = self.create_identity_provider(type="msteams", external_id="54rum4n")
Identity.objects.create(
external_id="7h3_gr3y",
idp=idp2,
user=self.user,
status=IdentityStatus.VALID,
scopes=[],
)
resp = self.post_webhook(action_type=ACTION_TYPE.ASSIGN, assign_input="ME")
assert resp.status_code == 200, resp.content
assert GroupAssignee.objects.filter(group=self.group1, user_id=self.user.id).exists()
@responses.activate
@patch("sentry.integrations.msteams.webhook.verify_signature", return_value=True)
def test_resolve_issue(self, verify):
resp = self.post_webhook(action_type=ACTION_TYPE.RESOLVE, resolve_input="resolved")
self.group1 = Group.objects.get(id=self.group1.id)
assert resp.status_code == 200, resp.content
assert self.group1.get_status() == GroupStatus.RESOLVED
assert b"Unresolve" in responses.calls[0].request.body
@responses.activate
@patch("sentry.integrations.msteams.webhook.verify_signature", return_value=True)
def test_no_resolve_input(self, verify):
resp = self.post_webhook(action_type=ACTION_TYPE.RESOLVE, resolve_input="")
self.group1 = Group.objects.get(id=self.group1.id)
assert resp.status_code == 200, resp.content
assert self.group1.get_status() == GroupStatus.UNRESOLVED
assert b"Resolve" in responses.calls[0].request.body
@responses.activate
@patch("sentry.integrations.msteams.webhook.verify_signature", return_value=True)
def test_unassign_issue(self, verify):
GroupAssignee.objects.create(group=self.group1, project=self.project1, user_id=self.user.id)
resp = self.post_webhook(action_type=ACTION_TYPE.UNASSIGN, resolve_input="resolved")
self.group1 = Group.objects.get(id=self.group1.id)
assert resp.status_code == 200, resp.content
assert not GroupAssignee.objects.filter(group=self.group1, user_id=self.user.id).exists()
assert b"Assign" in responses.calls[0].request.body
@responses.activate
@patch.object(ApiClient, "put")
@patch("sentry.integrations.msteams.webhook.verify_signature", return_value=True)
def test_resolve_with_params(self, verify, client_put):
client_put.return_value = HttpResponse(status=200)
self.post_webhook(
action_type=ACTION_TYPE.RESOLVE, resolve_input="resolved:inCurrentRelease"
)
expected_data = {"status": "resolved", "statusDetails": {"inRelease": "latest"}}
assert_mock_called_once_with_partial(client_put, data=expected_data)
@responses.activate
@patch("sentry.integrations.msteams.webhook.verify_signature", return_value=True)
def test_no_integration(self, verify):
with assume_test_silo_mode(SiloMode.CONTROL):
self.integration.delete()
resp = self.post_webhook()
assert resp.status_code == 404