|
@@ -1,5 +1,6 @@
|
|
|
from collections import namedtuple
|
|
|
-from unittest.mock import patch
|
|
|
+from copy import deepcopy
|
|
|
+from unittest.mock import MagicMock, patch
|
|
|
|
|
|
import pytest
|
|
|
from celery import Task
|
|
@@ -44,20 +45,77 @@ def raiseException():
|
|
|
raise Exception
|
|
|
|
|
|
|
|
|
+class RequestMock:
|
|
|
+ def __init__(self):
|
|
|
+ self.body = "blah blah"
|
|
|
+
|
|
|
+
|
|
|
+class ResponseMock:
|
|
|
+ def __init__(self, headers, content, text, ok, status_code, request):
|
|
|
+ self.headers = headers
|
|
|
+ self.content = content
|
|
|
+ self.text = text
|
|
|
+ self.ok = ok
|
|
|
+ self.status_code = status_code
|
|
|
+ self.request = request
|
|
|
+
|
|
|
+ def get(self, request=None, content=None):
|
|
|
+ if request:
|
|
|
+ return self.request
|
|
|
+ if content:
|
|
|
+ return self.content
|
|
|
+ return None
|
|
|
+
|
|
|
+
|
|
|
+headers = {"Sentry-Hook-Error": "d5111da2c28645c5889d072017e3445d", "Sentry-Hook-Project": "1"}
|
|
|
+
|
|
|
+mock_fail_resp = ResponseMock(
|
|
|
+ headers=headers,
|
|
|
+ content={},
|
|
|
+ text="",
|
|
|
+ ok=False,
|
|
|
+ status_code=400,
|
|
|
+ request=RequestMock(),
|
|
|
+)
|
|
|
+
|
|
|
+mock_fail_html_resp = deepcopy(mock_fail_resp)
|
|
|
+mock_fail_html_resp.content = "a bunch of garbage HTML"
|
|
|
+
|
|
|
+MockFailureHTMLContentResponseInstance = MagicMock(
|
|
|
+ status_code=mock_fail_html_resp.status_code,
|
|
|
+ headers=mock_fail_html_resp.headers,
|
|
|
+ content=mock_fail_html_resp.content,
|
|
|
+ request=mock_fail_html_resp.request,
|
|
|
+)
|
|
|
+MockFailureHTMLContentResponseInstance.__iter__.return_value = []
|
|
|
+
|
|
|
+mock_fail_json_resp = deepcopy(mock_fail_resp)
|
|
|
+mock_fail_json_resp.content = '{"error": "bad request"}'
|
|
|
+
|
|
|
+MockFailureJSONContentResponseInstance = MagicMock(
|
|
|
+ status_code=mock_fail_json_resp.status_code,
|
|
|
+ headers=mock_fail_html_resp.headers,
|
|
|
+ content=mock_fail_json_resp.content,
|
|
|
+ request=mock_fail_json_resp.request,
|
|
|
+)
|
|
|
+MockFailureJSONContentResponseInstance.__iter__.return_value = []
|
|
|
+
|
|
|
+MockResponseWithHeadersInstance = MagicMock(
|
|
|
+ status_code=mock_fail_resp.status_code, headers=mock_fail_resp.headers
|
|
|
+)
|
|
|
+MockResponseWithHeadersInstance.__iter__.return_value = []
|
|
|
+
|
|
|
+MockFailureResponseInstance = MagicMock(
|
|
|
+ status_code=mock_fail_resp.status_code, headers=mock_fail_resp.headers
|
|
|
+)
|
|
|
+MockFailureResponseInstance.__iter__.return_value = []
|
|
|
+
|
|
|
+
|
|
|
MockResponse = namedtuple(
|
|
|
"MockResponse", ["headers", "content", "text", "ok", "status_code", "raise_for_status"]
|
|
|
)
|
|
|
MockResponseInstance = MockResponse({}, {}, "", True, 200, raiseStatusFalse)
|
|
|
-MockFailureResponseInstance = MockResponse({}, {}, "", False, 400, raiseStatusTrue)
|
|
|
MockResponse404 = MockResponse({}, {}, "", False, 404, raiseException)
|
|
|
-MockResponseWithHeadersInstance = MockResponse(
|
|
|
- {"Sentry-Hook-Error": "d5111da2c28645c5889d072017e3445d", "Sentry-Hook-Project": "1"},
|
|
|
- {},
|
|
|
- "",
|
|
|
- False,
|
|
|
- 400,
|
|
|
- raiseStatusTrue,
|
|
|
-)
|
|
|
|
|
|
|
|
|
class TestSendAlertEvent(TestCase):
|
|
@@ -558,6 +616,52 @@ class TestWebhookRequests(TestCase):
|
|
|
assert first_request["event_type"] == "issue.assigned"
|
|
|
assert first_request["organization_id"] == self.install.organization.id
|
|
|
|
|
|
+ @patch(
|
|
|
+ "sentry.utils.sentry_apps.webhooks.safe_urlopen",
|
|
|
+ return_value=MockFailureHTMLContentResponseInstance,
|
|
|
+ )
|
|
|
+ def test_saves_error_if_webhook_request_with_html_content_fails(self, safe_urlopen):
|
|
|
+ data = {"issue": serialize(self.issue)}
|
|
|
+
|
|
|
+ with self.assertRaises(ClientError):
|
|
|
+ send_webhooks(
|
|
|
+ installation=self.install, event="issue.assigned", data=data, actor=self.user
|
|
|
+ )
|
|
|
+
|
|
|
+ requests = self.buffer.get_requests()
|
|
|
+ requests_count = len(requests)
|
|
|
+ first_request = requests[0]
|
|
|
+
|
|
|
+ assert safe_urlopen.called
|
|
|
+ assert requests_count == 1
|
|
|
+ assert first_request["response_code"] == 400
|
|
|
+ assert first_request["event_type"] == "issue.assigned"
|
|
|
+ assert first_request["organization_id"] == self.install.organization.id
|
|
|
+ assert first_request["response_body"] == mock_fail_html_resp.content
|
|
|
+
|
|
|
+ @patch(
|
|
|
+ "sentry.utils.sentry_apps.webhooks.safe_urlopen",
|
|
|
+ return_value=MockFailureJSONContentResponseInstance,
|
|
|
+ )
|
|
|
+ def test_saves_error_if_webhook_request_with_json_content_fails(self, safe_urlopen):
|
|
|
+ data = {"issue": serialize(self.issue)}
|
|
|
+
|
|
|
+ with self.assertRaises(ClientError):
|
|
|
+ send_webhooks(
|
|
|
+ installation=self.install, event="issue.assigned", data=data, actor=self.user
|
|
|
+ )
|
|
|
+
|
|
|
+ requests = self.buffer.get_requests()
|
|
|
+ requests_count = len(requests)
|
|
|
+ first_request = requests[0]
|
|
|
+
|
|
|
+ assert safe_urlopen.called
|
|
|
+ assert requests_count == 1
|
|
|
+ assert first_request["response_code"] == 400
|
|
|
+ assert first_request["event_type"] == "issue.assigned"
|
|
|
+ assert first_request["organization_id"] == self.install.organization.id
|
|
|
+ assert json.loads(first_request["response_body"]) == mock_fail_json_resp.content
|
|
|
+
|
|
|
@patch("sentry.utils.sentry_apps.webhooks.safe_urlopen", return_value=MockResponseInstance)
|
|
|
def test_saves_request_if_webhook_request_succeeds(self, safe_urlopen):
|
|
|
data = {"issue": serialize(self.issue)}
|