|
@@ -1,108 +1,110 @@
|
|
|
from copy import deepcopy
|
|
|
-from unittest import mock
|
|
|
-from unittest.mock import MagicMock
|
|
|
|
|
|
-from django.http import HttpResponse
|
|
|
-from django.test import RequestFactory, override_settings
|
|
|
+import responses
|
|
|
+from django.http import HttpRequest, HttpResponse
|
|
|
+from django.test import RequestFactory
|
|
|
from django.urls import reverse
|
|
|
|
|
|
from fixtures.vsts import WORK_ITEM_UNASSIGNED, WORK_ITEM_UPDATED, WORK_ITEM_UPDATED_STATUS
|
|
|
from sentry.middleware.integrations.classifications import IntegrationClassification
|
|
|
from sentry.middleware.integrations.parsers.vsts import VstsRequestParser
|
|
|
-from sentry.models.integrations.integration import Integration
|
|
|
-from sentry.models.outbox import ControlOutbox, WebhookProviderIdentifier
|
|
|
-from sentry.silo.base import SiloMode
|
|
|
+from sentry.models.outbox import WebhookProviderIdentifier
|
|
|
from sentry.testutils.cases import TestCase
|
|
|
-from sentry.testutils.outbox import assert_webhook_outboxes
|
|
|
-from sentry.testutils.silo import control_silo_test
|
|
|
-from sentry.types.region import Region, RegionCategory
|
|
|
+from sentry.testutils.outbox import assert_no_webhook_outboxes, assert_webhook_outboxes
|
|
|
+from sentry.testutils.silo import control_silo_test, create_test_regions
|
|
|
|
|
|
|
|
|
-@control_silo_test
|
|
|
+@control_silo_test(regions=create_test_regions("us"))
|
|
|
class VstsRequestParserTest(TestCase):
|
|
|
- get_response = MagicMock(return_value=HttpResponse(content=b"no-error", status=200))
|
|
|
factory = RequestFactory()
|
|
|
+ shared_secret = "1234567890"
|
|
|
path = f"{IntegrationClassification.integration_prefix}vsts/issue-updated/"
|
|
|
- region = Region("us", 1, "https://us.testserver", RegionCategory.MULTI_TENANT)
|
|
|
|
|
|
def setUp(self):
|
|
|
super().setUp()
|
|
|
- self.shared_secret = "1234567890"
|
|
|
-
|
|
|
- def set_workitem_state(self, old_value, new_value):
|
|
|
- work_item = deepcopy(WORK_ITEM_UPDATED_STATUS)
|
|
|
- state = work_item["resource"]["fields"]["System.State"]
|
|
|
-
|
|
|
- if old_value is None:
|
|
|
- del state["oldValue"]
|
|
|
- else:
|
|
|
- state["oldValue"] = old_value
|
|
|
- state["newValue"] = new_value
|
|
|
+ self.user = self.create_user()
|
|
|
+ self.organization = self.create_organization(owner=self.user)
|
|
|
+ account_id = WORK_ITEM_UPDATED["resourceContainers"]["collection"]["id"]
|
|
|
+ self.integration = self.create_integration(
|
|
|
+ organization=self.organization,
|
|
|
+ external_id=account_id,
|
|
|
+ provider="vsts",
|
|
|
+ name="vsts_name",
|
|
|
+ metadata={
|
|
|
+ "domain_name": "https://instance.visualstudio.com/",
|
|
|
+ "subscription": {"id": 1234, "secret": self.shared_secret},
|
|
|
+ },
|
|
|
+ )
|
|
|
|
|
|
- return work_item
|
|
|
+ def get_response(self, request: HttpRequest) -> HttpResponse:
|
|
|
+ return HttpResponse(status=200, content="passthrough")
|
|
|
|
|
|
- @override_settings(SILO_MODE=SiloMode.CONTROL)
|
|
|
- def test_routing_properly(self):
|
|
|
+ @responses.activate
|
|
|
+ def test_routing_work_item_webhook(self):
|
|
|
+ # No integration found for request...
|
|
|
+ data = deepcopy(WORK_ITEM_UPDATED)
|
|
|
+ data["resourceContainers"]["collection"]["id"] = "non-existant"
|
|
|
request = self.factory.post(
|
|
|
self.path,
|
|
|
- json=WORK_ITEM_UPDATED,
|
|
|
+ data=data,
|
|
|
+ content_type="application/json",
|
|
|
HTTP_SHARED_SECRET=self.shared_secret,
|
|
|
)
|
|
|
parser = VstsRequestParser(request=request, response_handler=self.get_response)
|
|
|
|
|
|
- # No regions identified
|
|
|
- with mock.patch.object(
|
|
|
- parser, "get_response_from_control_silo"
|
|
|
- ) as get_response_from_control_silo, mock.patch.object(
|
|
|
- parser, "get_regions_from_organizations", return_value=[]
|
|
|
- ):
|
|
|
- parser.get_response()
|
|
|
- assert get_response_from_control_silo.called
|
|
|
+ response = parser.get_response()
|
|
|
+ assert isinstance(response, HttpResponse)
|
|
|
+ assert response.status_code == 200
|
|
|
+ assert response.content == b"passthrough"
|
|
|
+ assert len(responses.calls) == 0
|
|
|
+ assert_no_webhook_outboxes()
|
|
|
|
|
|
# Regions found
|
|
|
- with mock.patch.object(
|
|
|
- parser, "get_response_from_outbox_creation"
|
|
|
- ) as get_response_from_outbox_creation, mock.patch.object(
|
|
|
- parser, "get_regions_from_organizations", return_value=[self.region]
|
|
|
- ):
|
|
|
- parser.get_response()
|
|
|
- assert get_response_from_outbox_creation.called
|
|
|
-
|
|
|
- # Non-webhook urls
|
|
|
- with mock.patch.object(
|
|
|
- parser, "get_response_from_outbox_creation"
|
|
|
- ) as get_response_from_outbox_creation, mock.patch.object(
|
|
|
- parser, "get_response_from_control_silo"
|
|
|
- ) as get_response_from_control_silo:
|
|
|
- parser.request = self.factory.get(
|
|
|
- reverse("vsts-extension-configuration"), data={"targetId": "1", "targetName": "foo"}
|
|
|
- )
|
|
|
- parser.get_response()
|
|
|
- assert get_response_from_control_silo.called
|
|
|
- assert not get_response_from_outbox_creation.called
|
|
|
-
|
|
|
- parser.request = self.factory.get(
|
|
|
- reverse(
|
|
|
- "sentry-extensions-vsts-search",
|
|
|
- kwargs={"organization_slug": "albertos-apples", "integration_id": 1234},
|
|
|
- ),
|
|
|
- )
|
|
|
- parser.get_response()
|
|
|
- assert get_response_from_control_silo.called
|
|
|
+ request = self.factory.post(
|
|
|
+ self.path,
|
|
|
+ data=WORK_ITEM_UPDATED,
|
|
|
+ content_type="application/json",
|
|
|
+ HTTP_SHARED_SECRET=self.shared_secret,
|
|
|
+ )
|
|
|
+ parser = VstsRequestParser(request=request, response_handler=self.get_response)
|
|
|
+ response = parser.get_response()
|
|
|
+ assert isinstance(response, HttpResponse)
|
|
|
+ assert response.status_code == 202
|
|
|
+ assert_webhook_outboxes(
|
|
|
+ factory_request=request,
|
|
|
+ webhook_identifier=WebhookProviderIdentifier.VSTS,
|
|
|
+ region_names=["us"],
|
|
|
+ )
|
|
|
|
|
|
- @override_settings(SILO_MODE=SiloMode.CONTROL)
|
|
|
- def test_get_integration_from_request(self):
|
|
|
- account_id = "80ded3e8-3cd3-43b1-9f96-52032624aa3a"
|
|
|
- expected_integration = Integration.objects.create(
|
|
|
- provider="vsts",
|
|
|
- external_id=account_id,
|
|
|
- name="vsts_name",
|
|
|
- metadata={
|
|
|
- "domain_name": "https://instance.visualstudio.com/",
|
|
|
- "subscription": {"id": 1234, "secret": self.shared_secret},
|
|
|
- },
|
|
|
+ @responses.activate
|
|
|
+ def test_routing_control_paths(self):
|
|
|
+ config_request = self.factory.get(
|
|
|
+ reverse("vsts-extension-configuration"),
|
|
|
+ data={"targetId": "1", "targetName": "foo"},
|
|
|
+ )
|
|
|
+ parser = VstsRequestParser(request=config_request, response_handler=self.get_response)
|
|
|
+ response = parser.get_response()
|
|
|
+ assert isinstance(response, HttpResponse)
|
|
|
+ assert response.status_code == 200
|
|
|
+ assert response.content == b"passthrough"
|
|
|
+ assert len(responses.calls) == 0
|
|
|
+ assert_no_webhook_outboxes()
|
|
|
+
|
|
|
+ search_request = self.factory.get(
|
|
|
+ reverse(
|
|
|
+ "sentry-extensions-vsts-search",
|
|
|
+ kwargs={"organization_slug": "albertos-apples", "integration_id": 1234},
|
|
|
+ ),
|
|
|
)
|
|
|
+ parser = VstsRequestParser(request=search_request, response_handler=self.get_response)
|
|
|
+ response = parser.get_response()
|
|
|
+ assert isinstance(response, HttpResponse)
|
|
|
+ assert response.status_code == 200
|
|
|
+ assert response.content == b"passthrough"
|
|
|
+ assert len(responses.calls) == 0
|
|
|
+ assert_no_webhook_outboxes()
|
|
|
|
|
|
+ def test_get_integration_from_request(self):
|
|
|
region_silo_payloads = [WORK_ITEM_UNASSIGNED, WORK_ITEM_UPDATED, WORK_ITEM_UPDATED_STATUS]
|
|
|
|
|
|
for payload in region_silo_payloads:
|
|
@@ -114,7 +116,7 @@ class VstsRequestParserTest(TestCase):
|
|
|
)
|
|
|
parser = VstsRequestParser(request=request, response_handler=self.get_response)
|
|
|
integration = parser.get_integration_from_request()
|
|
|
- assert integration == expected_integration
|
|
|
+ assert integration == self.integration
|
|
|
|
|
|
# Invalid payload or content-type
|
|
|
request = self.factory.post(
|
|
@@ -127,22 +129,19 @@ class VstsRequestParserTest(TestCase):
|
|
|
integration = parser.get_integration_from_request()
|
|
|
assert integration is None
|
|
|
|
|
|
- @override_settings(SILO_MODE=SiloMode.CONTROL)
|
|
|
def test_webhook_outbox_creation(self):
|
|
|
request = self.factory.post(
|
|
|
self.path,
|
|
|
- json=WORK_ITEM_UPDATED,
|
|
|
+ data=WORK_ITEM_UPDATED,
|
|
|
+ content_type="application/json",
|
|
|
HTTP_SHARED_SECRET=self.shared_secret,
|
|
|
)
|
|
|
parser = VstsRequestParser(request=request, response_handler=self.get_response)
|
|
|
|
|
|
- assert ControlOutbox.objects.count() == 0
|
|
|
- with mock.patch.object(
|
|
|
- parser, "get_regions_from_organizations", return_value=[self.region]
|
|
|
- ):
|
|
|
- parser.get_response()
|
|
|
- assert_webhook_outboxes(
|
|
|
- factory_request=request,
|
|
|
- webhook_identifier=WebhookProviderIdentifier.VSTS,
|
|
|
- region_names=[self.region.name],
|
|
|
- )
|
|
|
+ assert_no_webhook_outboxes()
|
|
|
+ parser.get_response()
|
|
|
+ assert_webhook_outboxes(
|
|
|
+ factory_request=request,
|
|
|
+ webhook_identifier=WebhookProviderIdentifier.VSTS,
|
|
|
+ region_names=["us"],
|
|
|
+ )
|