2023-03-13 18:19:22 -03:00
|
|
|
import json
|
|
|
|
|
from unittest.mock import call, patch
|
|
|
|
|
|
|
|
|
|
import pytest
|
2023-03-14 14:21:46 -03:00
|
|
|
from django.utils import timezone
|
2023-03-13 18:19:22 -03:00
|
|
|
|
2023-04-05 09:03:55 -03:00
|
|
|
from apps.alerts.models import AlertGroupLogRecord, EscalationPolicy
|
2023-04-06 14:52:23 -03:00
|
|
|
from apps.base.models import UserNotificationPolicyLogRecord
|
2023-03-14 14:21:46 -03:00
|
|
|
from apps.public_api.serializers import IncidentSerializer
|
2023-03-13 18:19:22 -03:00
|
|
|
from apps.webhooks.models import Webhook
|
|
|
|
|
from apps.webhooks.tasks import execute_webhook, send_webhook_event
|
2023-04-18 13:03:33 -06:00
|
|
|
from apps.webhooks.tasks.trigger_webhook import NOT_FROM_SELECTED_INTEGRATION
|
2023-04-26 15:55:08 -06:00
|
|
|
from settings.base import WEBHOOK_RESPONSE_LIMIT
|
2023-03-13 18:19:22 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class MockResponse:
|
2023-04-26 15:55:08 -06:00
|
|
|
def __init__(self, status_code=200, content_length=0):
|
2023-03-13 18:19:22 -03:00
|
|
|
self.status_code = status_code
|
2023-04-26 15:55:08 -06:00
|
|
|
self.headers = {
|
|
|
|
|
"Content-Length": str(content_length),
|
|
|
|
|
}
|
2023-03-13 18:19:22 -03:00
|
|
|
|
|
|
|
|
def json(self):
|
|
|
|
|
return {"response": self.status_code}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
2023-03-14 14:21:46 -03:00
|
|
|
def test_send_webhook_event_filters(
|
|
|
|
|
make_organization, make_team, make_alert_receive_channel, make_alert_group, make_custom_webhook
|
|
|
|
|
):
|
2023-03-13 18:19:22 -03:00
|
|
|
organization = make_organization()
|
|
|
|
|
other_organization = make_organization()
|
2023-03-14 14:21:46 -03:00
|
|
|
alert_receive_channel = make_alert_receive_channel(organization)
|
|
|
|
|
alert_group = make_alert_group(alert_receive_channel)
|
2023-03-13 18:19:22 -03:00
|
|
|
|
|
|
|
|
webhooks = {}
|
|
|
|
|
for trigger_type, _ in Webhook.TRIGGER_TYPES:
|
|
|
|
|
webhooks[trigger_type] = make_custom_webhook(
|
2023-04-26 15:55:08 -06:00
|
|
|
organization=organization, trigger_type=trigger_type, team=make_team(organization)
|
2023-03-13 18:19:22 -03:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for trigger_type, _ in Webhook.TRIGGER_TYPES:
|
|
|
|
|
with patch("apps.webhooks.tasks.trigger_webhook.execute_webhook.apply_async") as mock_execute:
|
2023-03-14 14:21:46 -03:00
|
|
|
send_webhook_event(trigger_type, alert_group.pk, organization_id=organization.pk)
|
2023-04-05 09:03:55 -03:00
|
|
|
assert mock_execute.call_args == call((webhooks[trigger_type].pk, alert_group.pk, None, None))
|
2023-03-13 18:19:22 -03:00
|
|
|
|
|
|
|
|
# other org
|
2023-04-26 15:55:08 -06:00
|
|
|
other_org_webhook = make_custom_webhook(
|
|
|
|
|
organization=other_organization, trigger_type=Webhook.TRIGGER_ALERT_GROUP_CREATED
|
|
|
|
|
)
|
|
|
|
|
|
2023-03-14 14:21:46 -03:00
|
|
|
alert_receive_channel = make_alert_receive_channel(other_organization)
|
|
|
|
|
alert_group = make_alert_group(alert_receive_channel)
|
2023-03-13 18:19:22 -03:00
|
|
|
with patch("apps.webhooks.tasks.trigger_webhook.execute_webhook.apply_async") as mock_execute:
|
2023-04-26 15:55:08 -06:00
|
|
|
send_webhook_event(Webhook.TRIGGER_ALERT_GROUP_CREATED, alert_group.pk, organization_id=other_organization.pk)
|
2023-04-05 09:03:55 -03:00
|
|
|
assert mock_execute.call_args == call((other_org_webhook.pk, alert_group.pk, None, None))
|
2023-03-13 18:19:22 -03:00
|
|
|
|
|
|
|
|
|
2023-04-18 13:03:33 -06:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_execute_webhook_disabled(
|
|
|
|
|
make_organization, make_team, make_alert_receive_channel, make_alert_group, make_custom_webhook
|
|
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
alert_receive_channel = make_alert_receive_channel(organization)
|
|
|
|
|
alert_group = make_alert_group(alert_receive_channel)
|
2023-04-26 15:55:08 -06:00
|
|
|
make_custom_webhook(organization=organization, trigger_type=Webhook.TRIGGER_ALERT_GROUP_CREATED)
|
|
|
|
|
make_custom_webhook(
|
|
|
|
|
organization=organization, trigger_type=Webhook.TRIGGER_ALERT_GROUP_CREATED, is_webhook_enabled=False
|
|
|
|
|
)
|
2023-04-18 13:03:33 -06:00
|
|
|
|
|
|
|
|
with patch("apps.webhooks.tasks.trigger_webhook.execute_webhook.apply_async") as mock_execute:
|
2023-04-26 15:55:08 -06:00
|
|
|
send_webhook_event(Webhook.TRIGGER_ALERT_GROUP_CREATED, alert_group.pk, organization_id=organization.pk)
|
2023-04-18 13:03:33 -06:00
|
|
|
mock_execute.assert_called_once()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_execute_webhook_integration_filter_not_matching(
|
|
|
|
|
make_organization, make_team, make_alert_receive_channel, make_alert_group, make_custom_webhook
|
|
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
alert_receive_channel = make_alert_receive_channel(organization)
|
|
|
|
|
alert_group = make_alert_group(alert_receive_channel)
|
|
|
|
|
webhook = make_custom_webhook(
|
2023-04-26 15:55:08 -06:00
|
|
|
organization=organization,
|
|
|
|
|
trigger_type=Webhook.TRIGGER_ALERT_GROUP_CREATED,
|
|
|
|
|
integration_filter=["does-not-match"],
|
2023-04-18 13:03:33 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
with patch("apps.webhooks.models.webhook.requests") as mock_requests:
|
|
|
|
|
execute_webhook(webhook.pk, alert_group.pk, None, None)
|
|
|
|
|
|
|
|
|
|
assert not mock_requests.post.called
|
|
|
|
|
# check log should exist but have no status code
|
|
|
|
|
assert (
|
|
|
|
|
webhook.responses.count() == 1
|
|
|
|
|
and webhook.responses.first().status_code is None
|
|
|
|
|
and webhook.responses.first().request_trigger == NOT_FROM_SELECTED_INTEGRATION
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_execute_webhook_integration_filter_matching(
|
|
|
|
|
make_organization, make_team, make_alert_receive_channel, make_alert_group, make_custom_webhook
|
|
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
alert_receive_channel = make_alert_receive_channel(organization, public_primary_key="test-integration-1")
|
|
|
|
|
alert_group = make_alert_group(alert_receive_channel)
|
|
|
|
|
webhook = make_custom_webhook(
|
|
|
|
|
organization=organization,
|
2023-04-26 15:55:08 -06:00
|
|
|
trigger_type=Webhook.TRIGGER_ALERT_GROUP_CREATED,
|
2023-04-18 13:03:33 -06:00
|
|
|
integration_filter=["test-integration-1"],
|
|
|
|
|
# Check we get past integration filter but exit early to keep test simple
|
|
|
|
|
trigger_template="False",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
with patch("apps.webhooks.models.webhook.requests") as mock_requests:
|
|
|
|
|
execute_webhook(webhook.pk, alert_group.pk, None, None)
|
|
|
|
|
|
|
|
|
|
assert not mock_requests.post.called
|
|
|
|
|
# check log should exist but have no status code
|
|
|
|
|
assert (
|
|
|
|
|
webhook.responses.count() == 1
|
|
|
|
|
and webhook.responses.first().status_code is None
|
|
|
|
|
# Matches evaluated trigger_template
|
|
|
|
|
and webhook.responses.first().request_trigger == "False"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2023-03-13 18:19:22 -03:00
|
|
|
@pytest.mark.django_db
|
2023-03-14 14:21:46 -03:00
|
|
|
def test_execute_webhook_ok(
|
|
|
|
|
make_organization, make_user_for_organization, make_alert_receive_channel, make_alert_group, make_custom_webhook
|
|
|
|
|
):
|
2023-03-13 18:19:22 -03:00
|
|
|
organization = make_organization()
|
2023-03-14 14:21:46 -03:00
|
|
|
user = make_user_for_organization(organization)
|
|
|
|
|
alert_receive_channel = make_alert_receive_channel(organization)
|
|
|
|
|
alert_group = make_alert_group(
|
|
|
|
|
alert_receive_channel, acknowledged_at=timezone.now(), acknowledged=True, acknowledged_by=user.pk
|
|
|
|
|
)
|
2023-03-13 18:19:22 -03:00
|
|
|
webhook = make_custom_webhook(
|
|
|
|
|
organization=organization,
|
2023-03-14 14:21:46 -03:00
|
|
|
url="https://something/{{ alert_group_id }}/",
|
2023-03-13 18:19:22 -03:00
|
|
|
http_method="POST",
|
|
|
|
|
trigger_type=Webhook.TRIGGER_ACKNOWLEDGE,
|
2023-03-14 14:21:46 -03:00
|
|
|
trigger_template="{{{{ alert_group.integration_id == '{}' }}}}".format(
|
|
|
|
|
alert_receive_channel.public_primary_key
|
|
|
|
|
),
|
|
|
|
|
headers='{"some-header": "{{ alert_group_id }}"}',
|
|
|
|
|
data='{"value": "{{ alert_group_id }}"}',
|
2023-03-13 18:19:22 -03:00
|
|
|
forward_all=False,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
mock_response = MockResponse()
|
|
|
|
|
with patch("apps.webhooks.utils.socket.gethostbyname") as mock_gethostbyname:
|
|
|
|
|
mock_gethostbyname.return_value = "8.8.8.8"
|
|
|
|
|
with patch("apps.webhooks.models.webhook.requests") as mock_requests:
|
|
|
|
|
mock_requests.post.return_value = mock_response
|
2023-04-05 09:03:55 -03:00
|
|
|
execute_webhook(webhook.pk, alert_group.pk, user.pk, None)
|
2023-03-13 18:19:22 -03:00
|
|
|
|
|
|
|
|
assert mock_requests.post.called
|
|
|
|
|
expected_call = call(
|
2023-03-14 14:21:46 -03:00
|
|
|
"https://something/{}/".format(alert_group.public_primary_key),
|
2023-03-13 18:19:22 -03:00
|
|
|
timeout=10,
|
2023-03-14 14:21:46 -03:00
|
|
|
headers={"some-header": alert_group.public_primary_key},
|
|
|
|
|
json={"value": alert_group.public_primary_key},
|
2023-03-13 18:19:22 -03:00
|
|
|
)
|
|
|
|
|
assert mock_requests.post.call_args == expected_call
|
|
|
|
|
# check logs
|
2023-03-21 10:43:37 -03:00
|
|
|
log = webhook.responses.all()[0]
|
|
|
|
|
assert log.status_code == 200
|
|
|
|
|
assert log.content == json.dumps(mock_response.json())
|
|
|
|
|
assert log.request_data == json.dumps({"value": alert_group.public_primary_key})
|
|
|
|
|
assert log.request_headers == json.dumps({"some-header": alert_group.public_primary_key})
|
|
|
|
|
assert log.url == "https://something/{}/".format(alert_group.public_primary_key)
|
2023-04-05 09:03:55 -03:00
|
|
|
# check log record
|
|
|
|
|
log_record = alert_group.log_records.last()
|
|
|
|
|
assert log_record.type == AlertGroupLogRecord.TYPE_CUSTOM_BUTTON_TRIGGERED
|
|
|
|
|
expected_info = {
|
|
|
|
|
"trigger": "acknowledge",
|
|
|
|
|
"webhook_id": webhook.public_primary_key,
|
|
|
|
|
"webhook_name": webhook.name,
|
|
|
|
|
}
|
|
|
|
|
assert log_record.step_specific_info == expected_info
|
|
|
|
|
assert log_record.escalation_policy is None
|
|
|
|
|
assert log_record.escalation_policy_step is None
|
|
|
|
|
assert log_record.rendered_log_line_action() == f"outgoing webhook `{webhook.name}` triggered by acknowledge"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_execute_webhook_via_escalation_ok(
|
|
|
|
|
make_organization,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_alert_receive_channel,
|
|
|
|
|
make_alert_group,
|
|
|
|
|
make_custom_webhook,
|
|
|
|
|
make_escalation_chain,
|
|
|
|
|
make_escalation_policy,
|
|
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
user = make_user_for_organization(organization)
|
|
|
|
|
alert_receive_channel = make_alert_receive_channel(organization)
|
|
|
|
|
alert_group = make_alert_group(
|
|
|
|
|
alert_receive_channel, acknowledged_at=timezone.now(), acknowledged=True, acknowledged_by=user.pk
|
|
|
|
|
)
|
|
|
|
|
webhook = make_custom_webhook(
|
|
|
|
|
organization=organization,
|
|
|
|
|
url="https://something/{{ alert_group_id }}/",
|
|
|
|
|
http_method="POST",
|
|
|
|
|
trigger_type=Webhook.TRIGGER_ESCALATION_STEP,
|
|
|
|
|
trigger_template="{{{{ alert_group.integration_id == '{}' }}}}".format(
|
|
|
|
|
alert_receive_channel.public_primary_key
|
|
|
|
|
),
|
|
|
|
|
headers='{"some-header": "{{ alert_group_id }}"}',
|
|
|
|
|
data='{"value": "{{ alert_group_id }}"}',
|
|
|
|
|
forward_all=False,
|
|
|
|
|
)
|
|
|
|
|
escalation_chain = make_escalation_chain(organization)
|
|
|
|
|
escalation_policy = make_escalation_policy(
|
|
|
|
|
escalation_chain=escalation_chain,
|
|
|
|
|
escalation_policy_step=EscalationPolicy.STEP_TRIGGER_CUSTOM_WEBHOOK,
|
|
|
|
|
custom_webhook=webhook,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
mock_response = MockResponse()
|
|
|
|
|
with patch("apps.webhooks.utils.socket.gethostbyname") as mock_gethostbyname:
|
|
|
|
|
mock_gethostbyname.return_value = "8.8.8.8"
|
|
|
|
|
with patch("apps.webhooks.models.webhook.requests") as mock_requests:
|
|
|
|
|
mock_requests.post.return_value = mock_response
|
|
|
|
|
execute_webhook(webhook.pk, alert_group.pk, user.pk, escalation_policy.pk)
|
|
|
|
|
|
|
|
|
|
assert mock_requests.post.called
|
|
|
|
|
# check log record
|
|
|
|
|
log_record = alert_group.log_records.last()
|
|
|
|
|
assert log_record.type == AlertGroupLogRecord.TYPE_CUSTOM_BUTTON_TRIGGERED
|
|
|
|
|
expected_info = {
|
|
|
|
|
"trigger": "escalation",
|
|
|
|
|
"webhook_id": webhook.public_primary_key,
|
|
|
|
|
"webhook_name": webhook.name,
|
|
|
|
|
}
|
|
|
|
|
assert log_record.step_specific_info == expected_info
|
|
|
|
|
assert log_record.escalation_policy == escalation_policy
|
|
|
|
|
assert log_record.escalation_policy_step == EscalationPolicy.STEP_TRIGGER_CUSTOM_WEBHOOK
|
|
|
|
|
assert log_record.rendered_log_line_action() == f"outgoing webhook `{webhook.name}` triggered by escalation"
|
2023-03-21 10:43:37 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_execute_webhook_ok_forward_all(
|
2023-04-06 14:52:23 -03:00
|
|
|
make_organization,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_alert_receive_channel,
|
|
|
|
|
make_alert_group,
|
|
|
|
|
make_user_notification_policy_log_record,
|
|
|
|
|
make_custom_webhook,
|
2023-03-21 10:43:37 -03:00
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
user = make_user_for_organization(organization)
|
2023-04-06 14:52:23 -03:00
|
|
|
notified_user = make_user_for_organization(organization)
|
|
|
|
|
other_user = make_user_for_organization(organization)
|
2023-03-21 10:43:37 -03:00
|
|
|
alert_receive_channel = make_alert_receive_channel(organization)
|
|
|
|
|
alert_group = make_alert_group(
|
|
|
|
|
alert_receive_channel, acknowledged_at=timezone.now(), acknowledged=True, acknowledged_by=user.pk
|
|
|
|
|
)
|
2023-04-06 14:52:23 -03:00
|
|
|
for i in range(3):
|
|
|
|
|
make_user_notification_policy_log_record(
|
|
|
|
|
author=notified_user,
|
|
|
|
|
alert_group=alert_group,
|
|
|
|
|
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_SUCCESS,
|
|
|
|
|
)
|
|
|
|
|
make_user_notification_policy_log_record(
|
|
|
|
|
author=other_user,
|
|
|
|
|
alert_group=alert_group,
|
|
|
|
|
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED,
|
|
|
|
|
)
|
2023-03-21 10:43:37 -03:00
|
|
|
webhook = make_custom_webhook(
|
|
|
|
|
organization=organization,
|
|
|
|
|
url="https://something/{{ alert_group_id }}/",
|
|
|
|
|
http_method="POST",
|
|
|
|
|
trigger_type=Webhook.TRIGGER_ACKNOWLEDGE,
|
|
|
|
|
forward_all=True,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
mock_response = MockResponse()
|
|
|
|
|
with patch("apps.webhooks.utils.socket.gethostbyname") as mock_gethostbyname:
|
|
|
|
|
mock_gethostbyname.return_value = "8.8.8.8"
|
|
|
|
|
with patch("apps.webhooks.models.webhook.requests") as mock_requests:
|
|
|
|
|
mock_requests.post.return_value = mock_response
|
2023-04-05 09:03:55 -03:00
|
|
|
execute_webhook(webhook.pk, alert_group.pk, user.pk, None)
|
2023-03-21 10:43:37 -03:00
|
|
|
|
|
|
|
|
assert mock_requests.post.called
|
2023-03-14 14:21:46 -03:00
|
|
|
expected_data = {
|
|
|
|
|
"event": {
|
2023-03-21 10:43:37 -03:00
|
|
|
"type": "acknowledge",
|
2023-03-14 14:21:46 -03:00
|
|
|
"time": alert_group.acknowledged_at.isoformat(),
|
|
|
|
|
},
|
2023-04-06 14:52:23 -03:00
|
|
|
"user": {
|
|
|
|
|
"id": user.public_primary_key,
|
|
|
|
|
"username": user.username,
|
|
|
|
|
"email": user.email,
|
|
|
|
|
},
|
|
|
|
|
"integration": {
|
|
|
|
|
"id": alert_receive_channel.public_primary_key,
|
|
|
|
|
"type": alert_receive_channel.integration,
|
|
|
|
|
"name": alert_receive_channel.short_name,
|
|
|
|
|
"team": None,
|
|
|
|
|
},
|
|
|
|
|
"notified_users": [
|
|
|
|
|
{
|
|
|
|
|
"id": notified_user.public_primary_key,
|
|
|
|
|
"username": notified_user.username,
|
|
|
|
|
"email": notified_user.email,
|
|
|
|
|
}
|
|
|
|
|
],
|
2023-03-14 14:21:46 -03:00
|
|
|
"alert_group": IncidentSerializer(alert_group).data,
|
|
|
|
|
"alert_group_id": alert_group.public_primary_key,
|
|
|
|
|
"alert_payload": "",
|
2023-04-20 10:13:48 -06:00
|
|
|
"users_to_be_notified": [],
|
2023-03-14 14:21:46 -03:00
|
|
|
}
|
2023-03-21 10:43:37 -03:00
|
|
|
expected_call = call(
|
|
|
|
|
"https://something/{}/".format(alert_group.public_primary_key),
|
|
|
|
|
timeout=10,
|
|
|
|
|
headers={},
|
|
|
|
|
json=expected_data,
|
|
|
|
|
)
|
|
|
|
|
assert mock_requests.post.call_args == expected_call
|
|
|
|
|
# check logs
|
|
|
|
|
log = webhook.responses.all()[0]
|
|
|
|
|
assert log.status_code == 200
|
|
|
|
|
assert log.content == json.dumps(mock_response.json())
|
|
|
|
|
assert json.loads(log.request_data) == expected_data
|
2023-03-14 14:21:46 -03:00
|
|
|
assert log.url == "https://something/{}/".format(alert_group.public_primary_key)
|
2023-03-13 18:19:22 -03:00
|
|
|
|
|
|
|
|
|
2023-03-21 10:43:37 -03:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_execute_webhook_using_responses_data(
|
|
|
|
|
make_organization,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_alert_receive_channel,
|
|
|
|
|
make_alert_group,
|
|
|
|
|
make_custom_webhook,
|
|
|
|
|
make_webhook_response,
|
|
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
user = make_user_for_organization(organization)
|
|
|
|
|
alert_receive_channel = make_alert_receive_channel(organization)
|
|
|
|
|
alert_group = make_alert_group(
|
|
|
|
|
alert_receive_channel, acknowledged_at=timezone.now(), acknowledged=True, acknowledged_by=user.pk
|
|
|
|
|
)
|
|
|
|
|
webhook = make_custom_webhook(
|
|
|
|
|
organization=organization,
|
2023-04-13 12:52:29 -06:00
|
|
|
url='https://something/{{ responses["response-1"].id }}/',
|
2023-03-21 10:43:37 -03:00
|
|
|
http_method="POST",
|
|
|
|
|
trigger_type=Webhook.TRIGGER_RESOLVE,
|
2023-04-13 12:52:29 -06:00
|
|
|
data='{"value": "{{ responses["response-2"].status }}"}',
|
2023-03-21 10:43:37 -03:00
|
|
|
forward_all=False,
|
|
|
|
|
)
|
2023-04-13 12:52:29 -06:00
|
|
|
|
2023-03-21 10:43:37 -03:00
|
|
|
# add previous webhook responses for the related alert group
|
|
|
|
|
make_webhook_response(
|
2023-04-13 12:52:29 -06:00
|
|
|
alert_group=alert_group,
|
|
|
|
|
webhook=make_custom_webhook(
|
|
|
|
|
organization=organization,
|
|
|
|
|
public_primary_key="response-1",
|
|
|
|
|
),
|
2023-04-26 15:55:08 -06:00
|
|
|
trigger_type=Webhook.TRIGGER_ALERT_GROUP_CREATED,
|
2023-04-14 14:30:32 -03:00
|
|
|
status_code=200,
|
2023-04-13 12:52:29 -06:00
|
|
|
content=json.dumps({"id": "third-party-id"}),
|
2023-03-21 10:43:37 -03:00
|
|
|
)
|
|
|
|
|
make_webhook_response(
|
|
|
|
|
alert_group=alert_group,
|
2023-04-13 12:52:29 -06:00
|
|
|
webhook=make_custom_webhook(
|
|
|
|
|
organization=organization,
|
|
|
|
|
public_primary_key="response-2",
|
|
|
|
|
),
|
2023-03-21 10:43:37 -03:00
|
|
|
trigger_type=Webhook.TRIGGER_ACKNOWLEDGE,
|
2023-04-14 14:30:32 -03:00
|
|
|
status_code=200,
|
2023-03-21 10:43:37 -03:00
|
|
|
content=json.dumps({"id": "third-party-id", "status": "updated"}),
|
|
|
|
|
)
|
2023-04-14 14:30:32 -03:00
|
|
|
# webhook wasn't executed because of some error, there is no content or status_code
|
|
|
|
|
make_webhook_response(
|
|
|
|
|
alert_group=alert_group,
|
|
|
|
|
webhook=make_custom_webhook(
|
|
|
|
|
organization=organization,
|
|
|
|
|
public_primary_key="response-3",
|
|
|
|
|
),
|
|
|
|
|
trigger_type=Webhook.TRIGGER_SILENCE,
|
|
|
|
|
content=None,
|
|
|
|
|
status_code=None,
|
|
|
|
|
)
|
2023-03-21 10:43:37 -03:00
|
|
|
|
|
|
|
|
mock_response = MockResponse()
|
|
|
|
|
with patch("apps.webhooks.utils.socket.gethostbyname") as mock_gethostbyname:
|
|
|
|
|
mock_gethostbyname.return_value = "8.8.8.8"
|
|
|
|
|
with patch("apps.webhooks.models.webhook.requests") as mock_requests:
|
|
|
|
|
mock_requests.post.return_value = mock_response
|
2023-04-05 09:03:55 -03:00
|
|
|
execute_webhook(webhook.pk, alert_group.pk, user.pk, None)
|
2023-03-21 10:43:37 -03:00
|
|
|
|
|
|
|
|
assert mock_requests.post.called
|
|
|
|
|
expected_data = {"value": "updated"}
|
|
|
|
|
expected_call = call(
|
|
|
|
|
"https://something/third-party-id/",
|
|
|
|
|
timeout=10,
|
|
|
|
|
headers={},
|
|
|
|
|
json=expected_data,
|
|
|
|
|
)
|
|
|
|
|
assert mock_requests.post.call_args == expected_call
|
|
|
|
|
# check logs
|
|
|
|
|
log = webhook.responses.all()[0]
|
|
|
|
|
assert log.status_code == 200
|
|
|
|
|
assert log.content == json.dumps(mock_response.json())
|
|
|
|
|
assert json.loads(log.request_data) == expected_data
|
|
|
|
|
assert log.url == "https://something/third-party-id/"
|
|
|
|
|
|
|
|
|
|
|
2023-03-13 18:19:22 -03:00
|
|
|
@pytest.mark.django_db
|
2023-03-14 14:21:46 -03:00
|
|
|
def test_execute_webhook_trigger_false(
|
|
|
|
|
make_organization, make_alert_receive_channel, make_alert_group, make_custom_webhook
|
|
|
|
|
):
|
2023-03-13 18:19:22 -03:00
|
|
|
organization = make_organization()
|
2023-03-14 14:21:46 -03:00
|
|
|
alert_receive_channel = make_alert_receive_channel(organization)
|
|
|
|
|
alert_group = make_alert_group(alert_receive_channel, acknowledged_at=timezone.now(), acknowledged=True)
|
2023-03-13 18:19:22 -03:00
|
|
|
webhook = make_custom_webhook(
|
|
|
|
|
organization=organization,
|
|
|
|
|
url="https://something/{{ alert_id }}/",
|
|
|
|
|
http_method="POST",
|
|
|
|
|
trigger_type=Webhook.TRIGGER_ACKNOWLEDGE,
|
|
|
|
|
trigger_template="{{ integration_id == 'the-integration' }}",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
with patch("apps.webhooks.models.webhook.requests") as mock_requests:
|
2023-04-05 09:03:55 -03:00
|
|
|
execute_webhook(webhook.pk, alert_group.pk, None, None)
|
2023-03-13 18:19:22 -03:00
|
|
|
|
|
|
|
|
assert not mock_requests.post.called
|
2023-04-13 12:52:29 -06:00
|
|
|
# check log should exist but have no status
|
|
|
|
|
assert webhook.responses.count() == 1 and webhook.responses.first().status_code is None
|
2023-03-13 18:19:22 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"field_name,value,log_field_name,expected_error",
|
|
|
|
|
[
|
|
|
|
|
(
|
|
|
|
|
"url",
|
2023-03-14 14:21:46 -03:00
|
|
|
"https://myserver/{{ }}/triggered",
|
2023-03-13 18:19:22 -03:00
|
|
|
"url",
|
2023-03-14 14:21:46 -03:00
|
|
|
"URL - Template Error: Expected an expression, got 'end of print statement'",
|
2023-03-13 18:19:22 -03:00
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"trigger_template",
|
|
|
|
|
"{{ }}",
|
2023-03-21 10:43:37 -03:00
|
|
|
"request_trigger",
|
2023-03-13 18:19:22 -03:00
|
|
|
"Trigger - Template Error: Expected an expression, got 'end of print statement'",
|
|
|
|
|
),
|
2023-03-21 10:43:37 -03:00
|
|
|
("headers", '"{{foo|invalid}}"', "request_headers", "Headers - Template Error: No filter named 'invalid'."),
|
|
|
|
|
(
|
|
|
|
|
"data",
|
|
|
|
|
"{{ }}",
|
|
|
|
|
"request_data",
|
|
|
|
|
"Data - Template Error: Expected an expression, got 'end of print statement'",
|
|
|
|
|
),
|
2023-03-13 18:19:22 -03:00
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_execute_webhook_errors(
|
2023-03-14 14:21:46 -03:00
|
|
|
make_organization,
|
|
|
|
|
make_alert_receive_channel,
|
|
|
|
|
make_alert_group,
|
|
|
|
|
make_custom_webhook,
|
|
|
|
|
field_name,
|
|
|
|
|
value,
|
|
|
|
|
log_field_name,
|
|
|
|
|
expected_error,
|
2023-03-13 18:19:22 -03:00
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
2023-03-14 14:21:46 -03:00
|
|
|
alert_receive_channel = make_alert_receive_channel(organization)
|
|
|
|
|
alert_group = make_alert_group(alert_receive_channel, resolved_at=timezone.now(), resolved=True)
|
|
|
|
|
|
2023-03-13 18:19:22 -03:00
|
|
|
extra_kwargs = {field_name: value}
|
|
|
|
|
if "url" not in extra_kwargs:
|
|
|
|
|
extra_kwargs["url"] = "https://something.cool/"
|
|
|
|
|
webhook = make_custom_webhook(
|
|
|
|
|
organization=organization,
|
|
|
|
|
http_method="POST",
|
2023-03-14 14:21:46 -03:00
|
|
|
trigger_type=Webhook.TRIGGER_RESOLVE,
|
2023-03-13 18:19:22 -03:00
|
|
|
forward_all=False,
|
|
|
|
|
**extra_kwargs,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
with patch("apps.webhooks.utils.socket.gethostbyname") as mock_gethostbyname:
|
|
|
|
|
# make it a valid URL when resolving name
|
|
|
|
|
mock_gethostbyname.return_value = "8.8.8.8"
|
|
|
|
|
with patch("apps.webhooks.models.webhook.requests") as mock_requests:
|
2023-04-05 09:03:55 -03:00
|
|
|
execute_webhook(webhook.pk, alert_group.pk, None, None)
|
2023-03-13 18:19:22 -03:00
|
|
|
|
|
|
|
|
assert not mock_requests.post.called
|
2023-03-21 10:43:37 -03:00
|
|
|
log = webhook.responses.all()[0]
|
|
|
|
|
assert log.status_code is None
|
|
|
|
|
assert log.content is None
|
2023-03-13 18:19:22 -03:00
|
|
|
error = getattr(log, log_field_name)
|
|
|
|
|
assert error == expected_error
|
2023-04-05 09:03:55 -03:00
|
|
|
# check log record
|
|
|
|
|
log_record = alert_group.log_records.last()
|
|
|
|
|
assert log_record.type == AlertGroupLogRecord.ERROR_ESCALATION_TRIGGER_CUSTOM_WEBHOOK_ERROR
|
|
|
|
|
expected_info = {
|
|
|
|
|
"trigger": "resolve",
|
|
|
|
|
"webhook_id": webhook.public_primary_key,
|
|
|
|
|
"webhook_name": webhook.name,
|
|
|
|
|
}
|
|
|
|
|
assert log_record.step_specific_info == expected_info
|
|
|
|
|
assert log_record.reason == expected_error
|
|
|
|
|
assert (
|
|
|
|
|
log_record.rendered_log_line_action() == f"skipped resolve outgoing webhook `{webhook.name}`: {expected_error}"
|
|
|
|
|
)
|
2023-04-26 15:55:08 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_response_content_limit(
|
|
|
|
|
make_organization, make_user_for_organization, make_alert_receive_channel, make_alert_group, make_custom_webhook
|
|
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
user = make_user_for_organization(organization)
|
|
|
|
|
alert_receive_channel = make_alert_receive_channel(organization)
|
|
|
|
|
alert_group = make_alert_group(
|
|
|
|
|
alert_receive_channel, acknowledged_at=timezone.now(), acknowledged=True, acknowledged_by=user.pk
|
|
|
|
|
)
|
|
|
|
|
webhook = make_custom_webhook(
|
|
|
|
|
organization=organization,
|
|
|
|
|
url="https://test/",
|
|
|
|
|
http_method="POST",
|
|
|
|
|
trigger_type=Webhook.TRIGGER_ACKNOWLEDGE,
|
|
|
|
|
forward_all=False,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
mock_response = MockResponse(content_length=100000)
|
|
|
|
|
with patch("apps.webhooks.utils.socket.gethostbyname") as mock_gethostbyname:
|
|
|
|
|
mock_gethostbyname.return_value = "8.8.8.8"
|
|
|
|
|
with patch("apps.webhooks.models.webhook.requests") as mock_requests:
|
|
|
|
|
mock_requests.post.return_value = mock_response
|
|
|
|
|
execute_webhook(webhook.pk, alert_group.pk, user.pk, None)
|
|
|
|
|
|
|
|
|
|
assert mock_requests.post.called
|
|
|
|
|
expected_call = call(
|
|
|
|
|
"https://test/",
|
|
|
|
|
timeout=10,
|
|
|
|
|
headers={},
|
|
|
|
|
)
|
|
|
|
|
assert mock_requests.post.call_args == expected_call
|
|
|
|
|
# check logs
|
|
|
|
|
log = webhook.responses.all()[0]
|
|
|
|
|
assert log.status_code == 200
|
|
|
|
|
assert log.content == f"Response content exceeds {WEBHOOK_RESPONSE_LIMIT} character limit"
|
|
|
|
|
assert log.url == "https://test/"
|