def test_environment(self): group = self.group environment = Environment.get_or_create(group.project, "production") from sentry.api.serializers.models.group import tsdb with mock.patch("sentry.api.serializers.models.group.tsdb.get_range", side_effect=tsdb.get_range) as get_range: serialize( [group], serializer=StreamGroupSerializer( environment_func=lambda: environment, stats_period="14d"), ) assert get_range.call_count == 1 for args, kwargs in get_range.call_args_list: assert kwargs["environment_ids"] == [environment.id] def get_invalid_environment(): raise Environment.DoesNotExist() with mock.patch("sentry.api.serializers.models.group.tsdb.make_series", side_effect=tsdb.make_series) as make_series: serialize( [group], serializer=StreamGroupSerializer( environment_func=get_invalid_environment, stats_period="14d"), ) assert make_series.call_count == 1
def mocked_attachment_cache(request, mock_client): class RbCluster(object): def get_routing_client(self): return mock_client if request.param == "rb": with mock.patch("sentry.cache.redis.get_cluster_from_options", return_value=(RbCluster(), {})) as cluster_get: attachment_cache = import_string( "sentry.attachments.redis.RbAttachmentCache")(hosts=[]) cluster_get.assert_any_call("SENTRY_CACHE_OPTIONS", {"hosts": []}) assert isinstance(attachment_cache.inner, RbCache) elif request.param == "rediscluster": with mock.patch("sentry.utils.redis.redis_clusters.get", return_value=mock_client) as cluster_get: attachment_cache = import_string( "sentry.attachments.redis.RedisClusterAttachmentCache")() cluster_get.assert_any_call("rc-short") assert isinstance(attachment_cache.inner, RedisClusterCache) else: assert False assert attachment_cache.inner.client is mock_client yield attachment_cache
def test_iterates_pages(self): snapshot_calls = [0] def exploding_create_snapshot(*args, **kwargs): if snapshot_calls[0] < 1: snapshot_calls[0] += 1 raise Exception("bad snapshot") return create_incident_snapshot(*args, **kwargs) incident = self.create_incident( title="incident", status=IncidentStatus.CLOSED.value, date_started=datetime(2020, 5, 1), date_closed=datetime(2020, 5, 5), ) PendingIncidentSnapshot.objects.create(incident=incident, target_run_date=timezone.now()) other_incident = self.create_incident( title="incident", status=IncidentStatus.CLOSED.value, date_started=datetime(2020, 5, 1), date_closed=datetime(2020, 5, 5), ) failing = PendingIncidentSnapshot.objects.create( incident=other_incident, target_run_date=timezone.now() ) with patch("sentry.incidents.tasks.INCIDENT_SNAPSHOT_BATCH_SIZE", new=1), patch( "sentry.incidents.logic.create_incident_snapshot", ) as mock_create_snapshot: mock_create_snapshot.side_effect = exploding_create_snapshot with self.tasks(): process_pending_incident_snapshots() assert list(PendingIncidentSnapshot.objects.all()) == [failing]
def test_environment(self): group = self.group environment = Environment.get_or_create(group.project, "production") with mock.patch( "sentry.api.serializers.models.group.snuba_tsdb.get_range", side_effect=snuba_tsdb.get_range, ) as get_range: serialize( [group], serializer=StreamGroupSerializerSnuba( environment_ids=[environment.id], stats_period="14d"), ) assert get_range.call_count == 1 for args, kwargs in get_range.call_args_list: assert kwargs["environment_ids"] == [environment.id] with mock.patch( "sentry.api.serializers.models.group.snuba_tsdb.get_range", side_effect=snuba_tsdb.get_range, ) as get_range: serialize( [group], serializer=StreamGroupSerializerSnuba(environment_ids=None, stats_period="14d"), ) assert get_range.call_count == 1 for args, kwargs in get_range.call_args_list: assert kwargs["environment_ids"] is None
def test_project_get_option_does_not_reload(default_project, task_runner, monkeypatch): ProjectOption.objects._option_cache.clear() with task_runner(): with patch("sentry.models.projectoption.cache.get", return_value=None): with patch( "sentry.models.projectoption.schedule_update_config_cache" ) as update_config_cache: default_project.get_option( "sentry:relay_pii_config", '{"applications": {"$string": ["@creditcard:mask"]}}') update_config_cache.assert_not_called() # noqa
def test_assign_use_email_api(self, mock_sync_group_assignee_inbound): org = self.organization integration = Integration.objects.create( provider="jira", name="Example Jira", metadata={ "oauth_client_id": "oauth-client-id", "shared_secret": "a-super-secret-key-from-atlassian", "base_url": "https://example.atlassian.net", "domain_name": "example.atlassian.net", }, ) integration.add_organization(org, self.user) path = reverse("sentry-extensions-jira-issue-updated") responses.add( responses.GET, "https://example.atlassian.net/rest/api/3/user/email", json={"accountId": "deadbeef123", "email": self.user.email}, match_querystring=False, ) with patch( "sentry.integrations.jira.webhooks.get_integration_from_jwt", return_value=integration ): data = StubService.get_stub_data("jira", "edit_issue_assignee_payload.json") data["issue"]["fields"]["assignee"]["emailAddress"] = "" resp = self.client.post(path, data=data, HTTP_AUTHORIZATION="JWT anexampletoken") assert resp.status_code == 200 assert mock_sync_group_assignee_inbound.called assert len(responses.calls) == 1
def test_creating_too_many_project_thresholds_raises_error(self): ProjectTransactionThresholdOverride.objects.create( project=self.project, organization=self.project.organization, threshold=300, metric=TransactionMetric.DURATION.value, transaction="fire", ) MAX_TRANSACTION_THRESHOLDS_PER_PROJECT = 1 with mock.patch( "sentry.api.endpoints.project_transaction_threshold_override.MAX_TRANSACTION_THRESHOLDS_PER_PROJECT", MAX_TRANSACTION_THRESHOLDS_PER_PROJECT, ): with self.feature(self.feature_name): response = self.client.post( self.url, data={ "transaction": self.data["transaction"], "project": [self.project.id], "metric": "duration", "threshold": "600", }, ) assert response.status_code == 400 assert response.data == { "non_field_errors": ["At most 1 configured transaction thresholds per project."] }
def test_multiple_environments(self): group = self.create_group() self.login_as(user=self.user) environment = Environment.get_or_create(group.project, "production") environment2 = Environment.get_or_create(group.project, "staging") url = u"/api/0/issues/{}/?enable_snuba=1".format(group.id) from sentry.api.endpoints.group_details import tsdb with mock.patch("sentry.api.endpoints.group_details.tsdb.get_range", side_effect=tsdb.get_range) as get_range: response = self.client.get( "%s&environment=production&environment=staging" % (url, ), format="json") assert response.status_code == 200 assert get_range.call_count == 2 for args, kwargs in get_range.call_args_list: assert kwargs["environment_ids"] == [ environment.id, environment2.id ] response = self.client.get("%s&environment=invalid" % (url, ), format="json") assert response.status_code == 404
def BurstTaskRunner(): """ A fixture for queueing up Celery tasks and working them off in bursts. The main interesting property is that one can run tasks at a later point in the future, testing "concurrency" without actually spawning any kind of worker. """ queue = [] def apply_async(self, args=(), kwargs=(), countdown=None): queue.append((self, args, kwargs)) def work(max_jobs=None): jobs = 0 while queue and (max_jobs is None or max_jobs > jobs): self, args, kwargs = queue.pop(0) with patch("celery.app.task.Task.apply_async", apply_async): self(*args, **kwargs) jobs += 1 if queue: raise RuntimeError("Could not empty queue, last task items: %s" % repr(queue)) with patch("celery.app.task.Task.apply_async", apply_async): yield work
def Feature(names): """ Control whether a feature is enabled. A single feature may be conveniently enabled with >>> with Feature('feature-1'): >>> # Executes with feature-1 enabled More advanced enabling / disabling can be done using a dict >>> with Feature({'feature-1': True, 'feature-2': False}): >>> # Executes with feature-1 enabled and feature-2 disabled The following two invocations are equivalent: >>> with Feature(['feature-1', 'feature-2']): >>> # execute with both features enabled >>> with Feature({'feature-1': True, 'feature-2': True}): >>> # execute with both features enabled """ if isinstance(names, str): names = {names: True} elif not isinstance(names, collections.Mapping): names = {k: True for k in names} with patch("sentry.features.has") as features_has: features_has.side_effect = lambda x, *a, **k: names.get(x, False) yield
def test_workitem_change_assignee(self): work_item_id = 31 external_issue = ExternalIssue.objects.create( organization_id=self.organization.id, integration_id=self.model.id, key=work_item_id) with patch( "sentry.integrations.vsts.webhooks.sync_group_assignee_inbound" ) as mock: resp = self.client.post( absolute_uri("/extensions/vsts/issue-updated/"), data=WORK_ITEM_UPDATED, HTTP_SHARED_SECRET=self.shared_secret, ) assert resp.status_code == 200 external_issue = ExternalIssue.objects.get(id=external_issue.id) assert mock.call_count == 1 args = mock.call_args[1] assert args["integration"].__class__ == Integration assert args["email"] == "*****@*****.**" assert args["external_issue_key"] == work_item_id assert args["assign"] is True
def test_traceparent_header_wsgi(self): # Assert that posting something to store will not create another # (transaction) event under any circumstances. # # We use Werkzeug's test client because Django's test client bypasses a # lot of request handling code that we want to test implicitly (such as # all our WSGI middlewares and the entire Django instrumentation by # sentry-sdk). # # XXX(markus): Ideally methods such as `_postWithHeader` would always # call the WSGI application => swap out Django's test client with e.g. # Werkzeug's. client = WerkzeugClient(application) calls = [] def new_disable_transaction_events(): with configure_scope() as scope: assert scope.span.sampled assert scope.span.transaction disable_transaction_events() assert not scope.span.sampled calls.append(1) events = [] auth = get_auth_header("_postWithWerkzeug/0.0.0", self.projectkey.public_key, self.projectkey.secret_key, "7") with mock.patch("sentry.web.api.disable_transaction_events", new_disable_transaction_events): with self.tasks(): with Hub( Client( transport=events.append, integrations=[ CeleryIntegration(), DjangoIntegration() ], )): app_iter, status, headers = client.post( reverse("sentry-api-store"), data=b'{"message": "hello"}', headers={ "x-sentry-auth": auth, "sentry-trace": "1", "content-type": "application/octet-stream", }, environ_base={"REMOTE_ADDR": "127.0.0.1"}, ) body = "".join(app_iter) assert status == "200 OK", body assert set( (e.get("type"), e.get("transaction")) for e in events) == {("transaction", "rule_processor_apply")} assert calls == [1]
def test_first_last_only_one_tagstore(self): self.login_as(user=self.user) event = self.store_event( data={ "release": "1.0", "timestamp": iso_format(before_now(days=3)) }, project_id=self.project.id, ) self.store_event( data={ "release": "1.1", "timestamp": iso_format(before_now(minutes=3)) }, project_id=self.project.id, ) group = event.group url = f"/api/0/issues/{group.id}/" with mock.patch( "sentry.api.endpoints.group_details.tagstore.get_release_tags" ) as get_release_tags: response = self.client.get(url, format="json") assert response.status_code == 200 assert get_release_tags.call_count == 1
def test_calculated_limit(self): with patch("sentry.tsdb.snuba.snuba") as snuba: # 24h test rollup = 3600 end = self.now start = end + timedelta(days=-1, seconds=rollup) self.db.get_data(TSDBModel.group, [1, 2, 3, 4, 5], start, end, rollup=rollup) assert snuba.query.call_args[1]["limit"] == 120 # 14 day test rollup = 86400 start = end + timedelta(days=-14, seconds=rollup) self.db.get_data(TSDBModel.group, [1, 2, 3, 4, 5], start, end, rollup=rollup) assert snuba.query.call_args[1]["limit"] == 70 # 1h test rollup = 3600 end = self.now start = end + timedelta(hours=-1, seconds=rollup) self.db.get_data(TSDBModel.group, [1, 2, 3, 4, 5], start, end, rollup=rollup) assert snuba.query.call_args[1]["limit"] == 5
def test_delete_disabled_no_commits(self, mock_delete_repository, mock_get_transaction_id): mock_get_transaction_id.return_value = "1" self.login_as(user=self.user) org = self.create_organization(owner=self.user, name="baz") repo = Repository.objects.create( name="example", external_id="abc12345", organization_id=org.id, status=ObjectStatus.DISABLED, ) url = reverse("sentry-api-0-organization-repository-details", args=[org.slug, repo.id]) with patch("sentry.db.mixin.uuid4", new=self.get_mock_uuid()): response = self.client.delete(url) assert response.status_code == 202, (response.status_code, response.content) repo = Repository.objects.get(id=repo.id) assert repo.status == ObjectStatus.PENDING_DELETION mock_delete_repository.apply_async.assert_called_with( kwargs={"object_id": repo.id, "transaction_id": "1", "actor_id": self.user.id}, countdown=0, ) self.assert_rename_pending_delete(response, repo, "abc12345")
def test_too_large_for_cache(self): # make the cache fail domain_key = http.get_domain_key("http://example.com") original_get = cache.get def cache_get(key): if key == domain_key: return original_get(key) with patch("sentry.utils.cache.cache.get", side_effect=cache_get): responses.add( responses.GET, "http://example.com", body=b"Stuff", content_type="application/json; charset=utf-8", ) with pytest.raises(http.CannotFetch) as exc: fetch_file("http://example.com") assert exc.value.data["type"] == EventError.TOO_LARGE_FOR_CACHE assert cache.get(domain_key) == { "type": "too_large_for_cache", "url": "http://example.com", }
def test_simple_status_sync_inbound(self, mock_sync_status_inbound): org = self.organization integration = Integration.objects.create(provider="jira", name="Example Jira") integration.add_organization(org, self.user) path = reverse("sentry-extensions-jira-issue-updated") with patch( "sentry.integrations.jira.webhooks.get_integration_from_jwt", return_value=integration ) as mock_get_integration_from_jwt: data = StubService.get_stub_data("jira", "edit_issue_status_payload.json") resp = self.client.post(path, data=data, HTTP_AUTHORIZATION="JWT anexampletoken") assert resp.status_code == 200 mock_get_integration_from_jwt.assert_called_with( "anexampletoken", "/extensions/jira/issue-updated/", "jira", {}, method="POST" ) mock_sync_status_inbound.assert_called_with( "APP-123", { "changelog": { "from": "10101", "field": "status", "fromString": "Done", "to": "3", "toString": "In Progress", "fieldtype": "jira", "fieldId": "status", }, "issue": { "fields": {"project": {"id": "10000", "key": "APP"}}, "key": "APP-123", }, }, )
def test_rename_on_pending_deletion(self): with patch("sentry.db.mixin.uuid4", new=self.mock_uuid4): self.repository.rename_on_pending_deletion() repo = Repository.objects.get(id=self.repository.id) assert repo.name == "1234567" assert repo.external_id == "1234567" self.assert_organization_option(repo)
def test_fails_validation(self): """ Test that the absence of dynamic_form_fields in the action fails validation """ with mock.patch( "sentry.integrations.jira.integration.JiraIntegration.get_client", self.get_client ): # Create a new Rule response = self.client.post( reverse( "sentry-api-0-project-rules", kwargs={ "organization_slug": self.organization.slug, "project_slug": self.project.slug, }, ), format="json", data={ "name": "hello world", "environment": None, "actionMatch": "any", "frequency": 5, "actions": [ { "id": "sentry.integrations.jira.notify_action.JiraCreateTicketAction", "integration": self.integration.id, "issuetype": "1", "name": "Create a Jira ticket in the Jira Cloud account", "project": "10000", } ], "conditions": [], }, ) assert response.status_code == 400
def test_filter_fails(self): # setup a simple alert rule with 1 condition and 1 filter that doesn't pass self.event = self.store_event(data={}, project_id=self.project.id) filter_data = { "id": "tests.sentry.rules.test_processor.MockFilterFalse" } Rule.objects.filter(project=self.event.project).delete() self.rule = Rule.objects.create( project=self.event.project, data={ "conditions": [EVERY_EVENT_COND_DATA, filter_data], "actions": [EMAIL_ACTION_DATA], }, ) # patch the rule registry to contain the mocked rules with patch("sentry.rules.processor.rules", init_registry()): rp = RuleProcessor( self.event, is_new=True, is_regression=True, is_new_group_environment=True, has_reappeared=True, ) results = list(rp.apply()) assert len(results) == 0
def test_get_option_with_project(self): with mock.patch("sentry.models.ProjectOption.objects.get_value") as get_value: project = mock.Mock() result = get_option("key", project) self.assertEquals(result, get_value.return_value) get_value.assert_called_once_with(project, "key", None)
def test_set_option_with_project(self): with mock.patch( "sentry.models.ProjectOption.objects.set_value") as set_value: project = mock.Mock() set_option("key", "value", project) set_value.assert_called_once_with(project, "key", "value")
def test_attachment_outcomes(self): manager = EventManager(make_event(message="foo"), project=self.project) manager.normalize() a1 = CachedAttachment(name="a1", data=b"hello") a2 = CachedAttachment(name="a2", data=b"limited", rate_limited=True) a3 = CachedAttachment(name="a3", data=b"world") cache_key = cache_key_for_event(manager.get_data()) attachment_cache.set(cache_key, attachments=[a1, a2, a3]) mock_track_outcome = mock.Mock() with mock.patch("sentry.event_manager.track_outcome", mock_track_outcome): with self.feature("organizations:event-attachments"): manager.save(1, cache_key=cache_key) assert mock_track_outcome.call_count == 3 for o in mock_track_outcome.mock_calls: assert o.kwargs["outcome"] == Outcome.ACCEPTED for o in mock_track_outcome.mock_calls[:2]: assert o.kwargs["category"] == DataCategory.ATTACHMENT assert o.kwargs["quantity"] == 5 final = mock_track_outcome.mock_calls[2] assert final.kwargs["category"] == DataCategory.DEFAULT
def store_event(self, *args, **kwargs): with mock.patch("sentry.eventstream.insert", self.snuba_eventstream.insert): stored_event = Factories.store_event(*args, **kwargs) stored_group = stored_event.group if stored_group is not None: self.store_group(stored_group) return stored_event
def test_status_unmigratable_disabled_integration(self): self.url = self.url + "?status=unmigratable" integration = Integration.objects.create(provider="github", status=ObjectStatus.DISABLED) OrganizationIntegration.objects.create(integration_id=integration.id, organization_id=self.org.id) unmigratable_repo = Repository.objects.create( name="NotConnected/foo", organization_id=self.org.id) with patch( "sentry.integrations.github.GitHubIntegration.get_unmigratable_repositories" ) as f: f.return_value = [unmigratable_repo] response = self.client.get(self.url, format="json") assert response.status_code == 200 # Shouldn't return the above "unmigratable repo" since the # Integration is disabled. assert len(response.data) == 0 # Shouldn't even make the request to get repos assert not f.called
def setUp(self): patcher = patch( "django.utils.timezone.now", return_value=(datetime(2013, 5, 18, 15, 13, 58, 132928, tzinfo=timezone.utc)), ) patcher.start() self.addCleanup(patcher.stop) super().setUp()
def test_ticket_rules(self): with mock.patch( "sentry.integrations.jira.integration.JiraIntegration.get_client", self.get_client): # Create a new Rule response = self.client.post( reverse( "sentry-api-0-project-rules", kwargs={ "organization_slug": self.organization.slug, "project_slug": self.project.slug, }, ), format="json", data={ "name": "hello world", "environment": None, "actionMatch": "any", "frequency": 5, "actions": [{ "id": "sentry.integrations.jira.notify_action.JiraCreateTicketAction", "issuetype": "1", "jira_integration": self.integration.id, "name": "Create a Jira ticket in the Jira Cloud account", "project": "10000", }], "conditions": [], }, ) assert response.status_code == 200 # Get the rule from DB rule_object = Rule.objects.get(id=response.data["id"]) event = self.get_event() # Trigger its `after` self.trigger(event, rule_object) # assert ticket created in DB key = self.get_key(event) external_issue_count = len(ExternalIssue.objects.filter(key=key)) assert external_issue_count == 1 # assert ticket created on jira data = self.installation.get_issue(key) assert event.message in data["description"] # Trigger its `after` _again_ self.trigger(event, rule_object) # assert new ticket NOT created in DB assert ExternalIssue.objects.count() == external_issue_count
def assert_setup_flow(self): responses.reset() responses.add( responses.POST, u"https://smba.trafficmanager.net/amer/v3/conversations/%s/activities" % team_id, json={}, ) with patch("time.time") as mock_time: mock_time.return_value = self.start_time # token mock access_json = {"expires_in": 86399, "access_token": "my_token"} responses.add( responses.POST, "https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token", json=access_json, ) params = {"signed_params": sign(**self.pipeline_state)} self.pipeline.bind_state(self.provider.key, self.pipeline_state) resp = self.client.get(self.setup_path, params) body = responses.calls[0].request.body assert body == urlencode({ "client_id": "msteams-client-id", "client_secret": "msteams-client-secret", "grant_type": "client_credentials", "scope": "https://api.botframework.com/.default", }) assert resp.status_code == 200 self.assertDialogSuccess(resp) integration = Integration.objects.get(provider=self.provider.key) assert integration.external_id == team_id assert integration.name == "my_team" assert integration.metadata == { "access_token": "my_token", "service_url": "https://smba.trafficmanager.net/amer/", "expires_at": self.start_time + 86399 - 60 * 5, } assert OrganizationIntegration.objects.get( integration=integration, organization=self.organization) integration_url = u"organizations/{}/rules/".format( self.organization.slug) assert integration_url in responses.calls[1].request.body.decode( "utf-8") assert self.organization.name in responses.calls[ 1].request.body.decode("utf-8")
def initialize(self, live_server): self.project.update_option("sentry:builtin_symbol_sources", []) new_prefix = live_server.url with patch("sentry.auth.system.is_internal_ip", return_value=True), self.options( {"system.url-prefix": new_prefix}): # Run test case: yield
def accept_auth(self, **kargs): saml_response = self.load_fixture("saml2_auth_response.xml") saml_response = base64.b64encode(saml_response) # Disable validation of the SAML2 mock response is_valid = "onelogin.saml2.response.OneLogin_Saml2_Response.is_valid" with mock.patch(is_valid, return_value=True): return self.client.post(self.acs_path, {"SAMLResponse": saml_response}, **kargs)