|
@@ -1,11 +1,12 @@
|
|
|
from datetime import datetime
|
|
|
+from typing import Any, Mapping
|
|
|
from unittest.mock import patch
|
|
|
|
|
|
import responses
|
|
|
-from django.urls import reverse
|
|
|
from freezegun import freeze_time
|
|
|
from pytz import UTC
|
|
|
|
|
|
+from sentry.integrations.slack.utils.channel import strip_channel_name
|
|
|
from sentry.models import (
|
|
|
Environment,
|
|
|
Integration,
|
|
@@ -14,110 +15,104 @@ from sentry.models import (
|
|
|
RuleActivityType,
|
|
|
RuleFireHistory,
|
|
|
RuleStatus,
|
|
|
- SentryAppComponent,
|
|
|
+ User,
|
|
|
)
|
|
|
from sentry.testutils import APITestCase
|
|
|
+from sentry.testutils.helpers import install_slack
|
|
|
from sentry.utils import json
|
|
|
|
|
|
|
|
|
-class ProjectRuleDetailsTest(APITestCase):
|
|
|
- endpoint = "sentry-api-0-project-rule-details"
|
|
|
-
|
|
|
- def test_simple(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
+def assert_rule_from_payload(rule: Rule, payload: Mapping[str, Any]) -> None:
|
|
|
+ """
|
|
|
+ Helper function to assert every field on a Rule was modified correctly from the incoming payload
|
|
|
+ """
|
|
|
+ rule.refresh_from_db()
|
|
|
+ assert rule.label == payload.get("name")
|
|
|
|
|
|
- team = self.create_team()
|
|
|
- project1 = self.create_project(teams=[team], name="foo", fire_project_created=True)
|
|
|
- self.create_project(teams=[team], name="bar", fire_project_created=True)
|
|
|
-
|
|
|
- rule = project1.rule_set.all()[0]
|
|
|
+ owner_id = payload.get("owner")
|
|
|
+ if owner_id:
|
|
|
+ assert rule.owner == User.objects.get(id=owner_id).actor
|
|
|
+ else:
|
|
|
+ assert rule.owner is None
|
|
|
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project1.organization.slug,
|
|
|
- "project_slug": project1.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
+ environment = payload.get("environment")
|
|
|
+ if environment:
|
|
|
+ assert (
|
|
|
+ rule.environment_id
|
|
|
+ == Environment.objects.get(projects=rule.project, name=environment).id
|
|
|
)
|
|
|
- response = self.client.get(url, format="json")
|
|
|
-
|
|
|
- assert response.status_code == 200, response.content
|
|
|
- assert response.data["id"] == str(rule.id)
|
|
|
- assert response.data["environment"] is None
|
|
|
-
|
|
|
- def test_non_existing_rule(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
-
|
|
|
- team = self.create_team()
|
|
|
- project1 = self.create_project(teams=[team], name="foo", fire_project_created=True)
|
|
|
- self.create_project(teams=[team], name="bar", fire_project_created=True)
|
|
|
-
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project1.organization.slug,
|
|
|
- "project_slug": project1.slug,
|
|
|
- "rule_id": 12345,
|
|
|
- },
|
|
|
+ else:
|
|
|
+ assert rule.environment_id is None
|
|
|
+ assert rule.data["action_match"] == payload.get("actionMatch")
|
|
|
+ assert rule.data["filter_match"] == payload.get("filterMatch")
|
|
|
+ # For actions/conditions/filters, payload might only have a portion of the rule data so we use
|
|
|
+ # any(a.items() <= b.items()) to check if the payload dict is a subset of the rule.data dict
|
|
|
+ # E.g. payload["actions"] = [{"name": "Test1"}], rule.data["actions"] = [{"name": "Test1", "id": 1}]
|
|
|
+ for payload_action in payload.get("actions", []):
|
|
|
+ # The Slack payload will contain '#channel' or '@user', but we save 'channel' or 'user' on the Rule
|
|
|
+ if (
|
|
|
+ payload_action["id"]
|
|
|
+ == "sentry.integrations.slack.notify_action.SlackNotifyServiceAction"
|
|
|
+ ):
|
|
|
+ payload_action["channel"] = strip_channel_name(payload_action["channel"])
|
|
|
+ assert any(
|
|
|
+ payload_action.items() <= rule_action.items() for rule_action in rule.data["actions"]
|
|
|
)
|
|
|
- response = self.client.get(url, format="json")
|
|
|
-
|
|
|
- assert response.status_code == 404
|
|
|
-
|
|
|
- def test_with_environment(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
-
|
|
|
- team = self.create_team()
|
|
|
- project1 = self.create_project(teams=[team], name="foo", fire_project_created=True)
|
|
|
- self.create_project(teams=[team], name="bar", fire_project_created=True)
|
|
|
-
|
|
|
- rule = project1.rule_set.all()[0]
|
|
|
- rule.update(environment_id=Environment.get_or_create(rule.project, "production").id)
|
|
|
-
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project1.organization.slug,
|
|
|
- "project_slug": project1.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
+ payload_conditions = payload.get("conditions", []) + payload.get("filters", [])
|
|
|
+ for payload_condition in payload_conditions:
|
|
|
+ assert any(
|
|
|
+ payload_condition.items() <= rule_condition.items()
|
|
|
+ for rule_condition in rule.data["conditions"]
|
|
|
)
|
|
|
- response = self.client.get(url, format="json")
|
|
|
+ assert RuleActivity.objects.filter(rule=rule, type=RuleActivityType.UPDATED.value).exists()
|
|
|
|
|
|
- assert response.status_code == 200, response.content
|
|
|
- assert response.data["id"] == str(rule.id)
|
|
|
- assert response.data["environment"] == "production"
|
|
|
|
|
|
- def test_with_null_environment(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
+class ProjectRuleDetailsBaseTestCase(APITestCase):
|
|
|
+ endpoint = "sentry-api-0-project-rule-details"
|
|
|
|
|
|
- team = self.create_team()
|
|
|
- project1 = self.create_project(teams=[team], name="foo", fire_project_created=True)
|
|
|
- self.create_project(teams=[team], name="bar", fire_project_created=True)
|
|
|
+ def setUp(self):
|
|
|
+ self.rule = self.create_project_rule(project=self.project)
|
|
|
+ self.environment = self.create_environment(self.project, name="production")
|
|
|
+ self.slack_integration = install_slack(organization=self.organization)
|
|
|
+ self.jira_integration = Integration.objects.create(
|
|
|
+ provider="jira", name="Jira", external_id="jira:1"
|
|
|
+ )
|
|
|
+ self.jira_integration.add_organization(self.organization, self.user)
|
|
|
+ self.sentry_app = self.create_sentry_app(
|
|
|
+ name="Pied Piper",
|
|
|
+ organization=self.organization,
|
|
|
+ schema={"elements": [self.create_alert_rule_action_schema()]},
|
|
|
+ )
|
|
|
+ self.sentry_app_installation = self.create_sentry_app_installation(
|
|
|
+ slug=self.sentry_app.slug, organization=self.organization
|
|
|
+ )
|
|
|
+ self.sentry_app_settings_payload = [
|
|
|
+ {"name": "title", "value": "Team Rocket"},
|
|
|
+ {"name": "summary", "value": "We're blasting off again."},
|
|
|
+ ]
|
|
|
+ self.login_as(self.user)
|
|
|
|
|
|
- rule = project1.rule_set.all()[0]
|
|
|
- rule.update(environment_id=None)
|
|
|
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project1.organization.slug,
|
|
|
- "project_slug": project1.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
+class ProjectRuleDetailsTest(ProjectRuleDetailsBaseTestCase):
|
|
|
+ def test_simple(self):
|
|
|
+ response = self.get_success_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=200
|
|
|
)
|
|
|
- response = self.client.get(url, format="json")
|
|
|
-
|
|
|
- assert response.status_code == 200, response.content
|
|
|
- assert response.data["id"] == str(rule.id)
|
|
|
+ assert response.data["id"] == str(self.rule.id)
|
|
|
assert response.data["environment"] is None
|
|
|
|
|
|
- def test_with_filters(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
+ def test_non_existing_rule(self):
|
|
|
+ self.get_error_response(self.organization.slug, self.project.slug, 12345, status_code=404)
|
|
|
|
|
|
- project = self.create_project()
|
|
|
+ def test_with_environment(self):
|
|
|
+ self.rule.update(environment_id=self.environment.id)
|
|
|
+ response = self.get_success_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=200
|
|
|
+ )
|
|
|
+ assert response.data["id"] == str(self.rule.id)
|
|
|
+ assert response.data["environment"] == self.environment.name
|
|
|
|
|
|
+ def test_with_filters(self):
|
|
|
conditions = [
|
|
|
{"id": "sentry.rules.conditions.every_event.EveryEventCondition"},
|
|
|
{"id": "sentry.rules.filters.issue_occurrences.IssueOccurrencesFilter", "value": 10},
|
|
@@ -130,21 +125,12 @@ class ProjectRuleDetailsTest(APITestCase):
|
|
|
"action_match": "all",
|
|
|
"frequency": 30,
|
|
|
}
|
|
|
+ self.rule.update(data=data)
|
|
|
|
|
|
- rule = Rule.objects.create(project=project, label="foo", data=data)
|
|
|
-
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project.organization.slug,
|
|
|
- "project_slug": project.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
+ response = self.get_success_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=200
|
|
|
)
|
|
|
- response = self.client.get(url, format="json")
|
|
|
-
|
|
|
- assert response.status_code == 200, response.content
|
|
|
- assert response.data["id"] == str(rule.id)
|
|
|
+ assert response.data["id"] == str(self.rule.id)
|
|
|
|
|
|
# ensure that conditions and filters are split up correctly
|
|
|
assert len(response.data["conditions"]) == 1
|
|
@@ -154,19 +140,6 @@ class ProjectRuleDetailsTest(APITestCase):
|
|
|
|
|
|
@responses.activate
|
|
|
def test_with_unresponsive_sentryapp(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
-
|
|
|
- self.sentry_app = self.create_sentry_app(
|
|
|
- organization=self.organization,
|
|
|
- published=True,
|
|
|
- verify_install=False,
|
|
|
- name="Super Awesome App",
|
|
|
- schema={"elements": [self.create_alert_rule_action_schema()]},
|
|
|
- )
|
|
|
- self.installation = self.create_sentry_app_installation(
|
|
|
- slug=self.sentry_app.slug, organization=self.organization, user=self.user
|
|
|
- )
|
|
|
-
|
|
|
conditions = [
|
|
|
{"id": "sentry.rules.conditions.every_event.EveryEventCondition"},
|
|
|
{"id": "sentry.rules.filters.issue_occurrences.IssueOccurrencesFilter", "value": 10},
|
|
@@ -175,7 +148,7 @@ class ProjectRuleDetailsTest(APITestCase):
|
|
|
actions = [
|
|
|
{
|
|
|
"id": "sentry.rules.actions.notify_event_sentry_app.NotifyEventSentryAppAction",
|
|
|
- "sentryAppInstallationUuid": self.installation.uuid,
|
|
|
+ "sentryAppInstallationUuid": self.sentry_app_installation.uuid,
|
|
|
"settings": [
|
|
|
{"name": "title", "value": "An alert"},
|
|
|
{"summary": "Something happened here..."},
|
|
@@ -191,63 +164,47 @@ class ProjectRuleDetailsTest(APITestCase):
|
|
|
"action_match": "all",
|
|
|
"frequency": 30,
|
|
|
}
|
|
|
+ self.rule.update(data=data)
|
|
|
|
|
|
- rule = Rule.objects.create(project=self.project, label="foo", data=data)
|
|
|
-
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": self.project.organization.slug,
|
|
|
- "project_slug": self.project.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
- )
|
|
|
responses.add(responses.GET, "http://example.com/sentry/members", json={}, status=404)
|
|
|
-
|
|
|
- response = self.client.get(url, format="json")
|
|
|
+ response = self.get_success_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=200
|
|
|
+ )
|
|
|
assert len(responses.calls) == 1
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
# Returns errors while fetching
|
|
|
assert len(response.data["errors"]) == 1
|
|
|
- assert response.data["errors"][0] == {
|
|
|
- "detail": "Could not fetch details from Super Awesome App"
|
|
|
- }
|
|
|
+ assert self.sentry_app.name in response.data["errors"][0]["detail"]
|
|
|
|
|
|
# Disables the SentryApp
|
|
|
- assert response.data["actions"][0]["sentryAppInstallationUuid"] == self.installation.uuid
|
|
|
+ assert (
|
|
|
+ response.data["actions"][0]["sentryAppInstallationUuid"]
|
|
|
+ == self.sentry_app_installation.uuid
|
|
|
+ )
|
|
|
assert response.data["actions"][0]["disabled"] is True
|
|
|
|
|
|
@freeze_time()
|
|
|
def test_last_triggered(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
- rule = self.create_project_rule()
|
|
|
- resp = self.get_success_response(
|
|
|
- self.organization.slug, self.project.slug, rule.id, expand=["lastTriggered"]
|
|
|
+ response = self.get_success_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, expand=["lastTriggered"]
|
|
|
)
|
|
|
- assert resp.data["lastTriggered"] is None
|
|
|
- RuleFireHistory.objects.create(project=self.project, rule=rule, group=self.group)
|
|
|
- resp = self.get_success_response(
|
|
|
- self.organization.slug, self.project.slug, rule.id, expand=["lastTriggered"]
|
|
|
+ assert response.data["lastTriggered"] is None
|
|
|
+ RuleFireHistory.objects.create(project=self.project, rule=self.rule, group=self.group)
|
|
|
+ response = self.get_success_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, expand=["lastTriggered"]
|
|
|
)
|
|
|
- assert resp.data["lastTriggered"] == datetime.now().replace(tzinfo=UTC)
|
|
|
+ assert response.data["lastTriggered"] == datetime.now().replace(tzinfo=UTC)
|
|
|
|
|
|
def test_with_jira_action_error(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
- self.integration = Integration.objects.create(
|
|
|
- provider="jira", name="Jira", external_id="jira:1"
|
|
|
- )
|
|
|
- self.integration.add_organization(self.organization, self.user)
|
|
|
-
|
|
|
conditions = [
|
|
|
{"id": "sentry.rules.conditions.every_event.EveryEventCondition"},
|
|
|
{"id": "sentry.rules.filters.issue_occurrences.IssueOccurrencesFilter", "value": 10},
|
|
|
]
|
|
|
-
|
|
|
actions = [
|
|
|
{
|
|
|
"id": "sentry.integrations.jira.notify_action.JiraCreateTicketAction",
|
|
|
- "integration": self.integration.id,
|
|
|
+ "integration": self.jira_integration.id,
|
|
|
"customfield_epic_link": "EPIC-3",
|
|
|
"customfield_severity": "Medium",
|
|
|
"dynamic_form_fields": [
|
|
@@ -261,7 +218,7 @@ class ProjectRuleDetailsTest(APITestCase):
|
|
|
"name": "customfield_epic_link",
|
|
|
"required": False,
|
|
|
"type": "select",
|
|
|
- "url": f"/extensions/jira/search/{self.organization.slug}/{self.integration.id}/",
|
|
|
+ "url": f"/extensions/jira/search/{self.organization.slug}/{self.jira_integration.id}/",
|
|
|
},
|
|
|
{
|
|
|
"choices": [
|
|
@@ -286,21 +243,11 @@ class ProjectRuleDetailsTest(APITestCase):
|
|
|
"frequency": 30,
|
|
|
}
|
|
|
|
|
|
- rule = Rule.objects.create(project=self.project, label="foo", data=data)
|
|
|
+ self.rule.update(data=data)
|
|
|
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": self.project.organization.slug,
|
|
|
- "project_slug": self.project.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
+ response = self.get_success_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=200
|
|
|
)
|
|
|
-
|
|
|
- response = self.client.get(url, format="json")
|
|
|
-
|
|
|
- assert response.status_code == 200
|
|
|
-
|
|
|
# Expect that the choices get filtered to match the API: Array<string, string>
|
|
|
assert response.data["actions"][0].get("dynamic_form_fields")[0].get("choices") == [
|
|
|
["EPIC-1", "Citizen Knope"],
|
|
@@ -308,15 +255,11 @@ class ProjectRuleDetailsTest(APITestCase):
|
|
|
]
|
|
|
|
|
|
|
|
|
-class UpdateProjectRuleTest(APITestCase):
|
|
|
+class UpdateProjectRuleTest(ProjectRuleDetailsBaseTestCase):
|
|
|
+ method = "PUT"
|
|
|
+
|
|
|
@patch("sentry.signals.alert_rule_edited.send_robust")
|
|
|
def test_simple(self, send_robust):
|
|
|
- self.login_as(user=self.user)
|
|
|
-
|
|
|
- project = self.create_project()
|
|
|
-
|
|
|
- rule = Rule.objects.create(project=project, label="foo")
|
|
|
-
|
|
|
conditions = [
|
|
|
{
|
|
|
"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition",
|
|
@@ -325,52 +268,22 @@ class UpdateProjectRuleTest(APITestCase):
|
|
|
"value": "bar",
|
|
|
}
|
|
|
]
|
|
|
-
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project.organization.slug,
|
|
|
- "project_slug": project.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
- )
|
|
|
- response = self.client.put(
|
|
|
- url,
|
|
|
- data={
|
|
|
- "name": "hello world",
|
|
|
- "owner": self.user.id,
|
|
|
- "actionMatch": "any",
|
|
|
- "filterMatch": "any",
|
|
|
- "actions": [{"id": "sentry.rules.actions.notify_event.NotifyEventAction"}],
|
|
|
- "conditions": conditions,
|
|
|
- },
|
|
|
- format="json",
|
|
|
+ payload = {
|
|
|
+ "name": "hello world",
|
|
|
+ "owner": self.user.id,
|
|
|
+ "actionMatch": "any",
|
|
|
+ "filterMatch": "any",
|
|
|
+ "actions": [{"id": "sentry.rules.actions.notify_event.NotifyEventAction"}],
|
|
|
+ "conditions": conditions,
|
|
|
+ }
|
|
|
+ response = self.get_success_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=200, **payload
|
|
|
)
|
|
|
-
|
|
|
- assert response.status_code == 200, response.content
|
|
|
- assert response.data["id"] == str(rule.id)
|
|
|
-
|
|
|
- rule = Rule.objects.get(id=rule.id)
|
|
|
- assert rule.label == "hello world"
|
|
|
- assert rule.owner == self.user.actor
|
|
|
- assert rule.environment_id is None
|
|
|
- assert rule.data["action_match"] == "any"
|
|
|
- assert rule.data["filter_match"] == "any"
|
|
|
- assert rule.data["actions"] == [
|
|
|
- {"id": "sentry.rules.actions.notify_event.NotifyEventAction"}
|
|
|
- ]
|
|
|
- assert rule.data["conditions"] == conditions
|
|
|
-
|
|
|
- assert RuleActivity.objects.filter(rule=rule, type=RuleActivityType.UPDATED.value).exists()
|
|
|
+ assert response.data["id"] == str(self.rule.id)
|
|
|
+ assert_rule_from_payload(self.rule, payload)
|
|
|
assert send_robust.called
|
|
|
|
|
|
def test_no_owner(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
-
|
|
|
- project = self.create_project()
|
|
|
-
|
|
|
- rule = Rule.objects.create(project=project, label="foo")
|
|
|
-
|
|
|
conditions = [
|
|
|
{
|
|
|
"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition",
|
|
@@ -379,220 +292,107 @@ class UpdateProjectRuleTest(APITestCase):
|
|
|
"value": "bar",
|
|
|
}
|
|
|
]
|
|
|
-
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project.organization.slug,
|
|
|
- "project_slug": project.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
- )
|
|
|
- response = self.client.put(
|
|
|
- url,
|
|
|
- data={
|
|
|
- "name": "hello world",
|
|
|
- "owner": None,
|
|
|
- "actionMatch": "any",
|
|
|
- "filterMatch": "any",
|
|
|
- "actions": [{"id": "sentry.rules.actions.notify_event.NotifyEventAction"}],
|
|
|
- "conditions": conditions,
|
|
|
- },
|
|
|
- format="json",
|
|
|
+ payload = {
|
|
|
+ "name": "hello world",
|
|
|
+ "owner": None,
|
|
|
+ "actionMatch": "any",
|
|
|
+ "filterMatch": "any",
|
|
|
+ "actions": [{"id": "sentry.rules.actions.notify_event.NotifyEventAction"}],
|
|
|
+ "conditions": conditions,
|
|
|
+ }
|
|
|
+ response = self.get_success_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=200, **payload
|
|
|
)
|
|
|
-
|
|
|
- assert response.status_code == 200, response.content
|
|
|
- assert response.data["id"] == str(rule.id)
|
|
|
-
|
|
|
- rule = Rule.objects.get(id=rule.id)
|
|
|
- assert rule.label == "hello world"
|
|
|
- assert rule.owner is None
|
|
|
- assert rule.environment_id is None
|
|
|
- assert rule.data["action_match"] == "any"
|
|
|
- assert rule.data["filter_match"] == "any"
|
|
|
- assert rule.data["actions"] == [
|
|
|
- {"id": "sentry.rules.actions.notify_event.NotifyEventAction"}
|
|
|
- ]
|
|
|
- assert rule.data["conditions"] == conditions
|
|
|
- assert RuleActivity.objects.filter(rule=rule, type=RuleActivityType.UPDATED.value).exists()
|
|
|
+ assert response.data["id"] == str(self.rule.id)
|
|
|
+ assert_rule_from_payload(self.rule, payload)
|
|
|
|
|
|
def test_update_name(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
-
|
|
|
- project = self.create_project()
|
|
|
-
|
|
|
- rule = Rule.objects.create(project=project, label="foo")
|
|
|
-
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project.organization.slug,
|
|
|
- "project_slug": project.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
- )
|
|
|
+ conditions = [
|
|
|
+ {
|
|
|
+ "interval": "1h",
|
|
|
+ "id": "sentry.rules.conditions.event_frequency.EventFrequencyCondition",
|
|
|
+ "value": 666,
|
|
|
+ "name": "The issue is seen more than 30 times in 1m",
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ actions = [
|
|
|
+ {
|
|
|
+ "id": "sentry.rules.actions.notify_event.NotifyEventAction",
|
|
|
+ "name": "Send a notification (for all legacy integrations)",
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ payload = {
|
|
|
+ "name": "test",
|
|
|
+ "environment": None,
|
|
|
+ "actionMatch": "all",
|
|
|
+ "filterMatch": "all",
|
|
|
+ "frequency": 30,
|
|
|
+ "conditions": conditions,
|
|
|
+ "actions": actions,
|
|
|
+ }
|
|
|
|
|
|
- response = self.client.put(
|
|
|
- url,
|
|
|
- data={
|
|
|
- "environment": None,
|
|
|
- "actionMatch": "all",
|
|
|
- "filterMatch": "all",
|
|
|
- "frequency": 30,
|
|
|
- "name": "test",
|
|
|
- "conditions": [
|
|
|
- {
|
|
|
- "interval": "1h",
|
|
|
- "id": "sentry.rules.conditions.event_frequency.EventFrequencyCondition",
|
|
|
- "value": 666,
|
|
|
- "name": "The issue is seen more than 30 times in 1m",
|
|
|
- }
|
|
|
- ],
|
|
|
- "id": rule.id,
|
|
|
- "actions": [
|
|
|
- {
|
|
|
- "id": "sentry.rules.actions.notify_event.NotifyEventAction",
|
|
|
- "name": "Send a notification (for all legacy integrations)",
|
|
|
- }
|
|
|
- ],
|
|
|
- "dateCreated": "2018-04-24T23:37:21.246Z",
|
|
|
- },
|
|
|
- format="json",
|
|
|
+ response = self.get_success_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=200, **payload
|
|
|
)
|
|
|
-
|
|
|
- assert response.status_code == 200, response.content
|
|
|
assert (
|
|
|
response.data["conditions"][0]["name"] == "The issue is seen more than 666 times in 1h"
|
|
|
)
|
|
|
-
|
|
|
- assert RuleActivity.objects.filter(rule=rule, type=RuleActivityType.UPDATED.value).exists()
|
|
|
+ assert_rule_from_payload(self.rule, payload)
|
|
|
|
|
|
def test_with_environment(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
-
|
|
|
- project = self.create_project()
|
|
|
-
|
|
|
- Environment.get_or_create(project, "production")
|
|
|
-
|
|
|
- rule = Rule.objects.create(project=project, label="foo")
|
|
|
-
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project.organization.slug,
|
|
|
- "project_slug": project.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
- )
|
|
|
- response = self.client.put(
|
|
|
- url,
|
|
|
- data={
|
|
|
- "name": "hello world",
|
|
|
- "environment": "production",
|
|
|
- "actionMatch": "any",
|
|
|
- "filterMatch": "any",
|
|
|
- "actions": [{"id": "sentry.rules.actions.notify_event.NotifyEventAction"}],
|
|
|
- "conditions": [
|
|
|
- {"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition"}
|
|
|
- ],
|
|
|
- },
|
|
|
- format="json",
|
|
|
+ payload = {
|
|
|
+ "name": "hello world",
|
|
|
+ "environment": self.environment.name,
|
|
|
+ "actionMatch": "any",
|
|
|
+ "filterMatch": "any",
|
|
|
+ "actions": [{"id": "sentry.rules.actions.notify_event.NotifyEventAction"}],
|
|
|
+ "conditions": [
|
|
|
+ {"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition"}
|
|
|
+ ],
|
|
|
+ }
|
|
|
+ response = self.get_success_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=200, **payload
|
|
|
)
|
|
|
-
|
|
|
- assert response.status_code == 200, response.content
|
|
|
- assert response.data["id"] == str(rule.id)
|
|
|
- assert response.data["environment"] == "production"
|
|
|
-
|
|
|
- rule = Rule.objects.get(id=rule.id)
|
|
|
- assert rule.label == "hello world"
|
|
|
- assert rule.environment_id == Environment.get_or_create(rule.project, "production").id
|
|
|
+ assert response.data["id"] == str(self.rule.id)
|
|
|
+ assert response.data["environment"] == self.environment.name
|
|
|
+ assert_rule_from_payload(self.rule, payload)
|
|
|
|
|
|
def test_with_null_environment(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
-
|
|
|
- project = self.create_project()
|
|
|
-
|
|
|
- rule = Rule.objects.create(
|
|
|
- project=project,
|
|
|
- environment_id=Environment.get_or_create(project, "production").id,
|
|
|
- label="foo",
|
|
|
- )
|
|
|
+ self.rule.update(environment_id=self.environment.id)
|
|
|
+
|
|
|
+ payload = {
|
|
|
+ "name": "hello world",
|
|
|
+ "environment": None,
|
|
|
+ "actionMatch": "any",
|
|
|
+ "filterMatch": "any",
|
|
|
+ "actions": [{"id": "sentry.rules.actions.notify_event.NotifyEventAction"}],
|
|
|
+ "conditions": [
|
|
|
+ {"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition"}
|
|
|
+ ],
|
|
|
+ }
|
|
|
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project.organization.slug,
|
|
|
- "project_slug": project.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
+ response = self.get_success_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=200, **payload
|
|
|
)
|
|
|
- response = self.client.put(
|
|
|
- url,
|
|
|
- data={
|
|
|
- "name": "hello world",
|
|
|
- "environment": None,
|
|
|
- "actionMatch": "any",
|
|
|
- "filterMatch": "any",
|
|
|
- "actions": [{"id": "sentry.rules.actions.notify_event.NotifyEventAction"}],
|
|
|
- "conditions": [
|
|
|
- {"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition"}
|
|
|
- ],
|
|
|
- },
|
|
|
- format="json",
|
|
|
- )
|
|
|
-
|
|
|
- assert response.status_code == 200, response.content
|
|
|
- assert response.data["id"] == str(rule.id)
|
|
|
+ assert response.data["id"] == str(self.rule.id)
|
|
|
assert response.data["environment"] is None
|
|
|
-
|
|
|
- rule = Rule.objects.get(id=rule.id)
|
|
|
- assert rule.label == "hello world"
|
|
|
- assert rule.environment_id is None
|
|
|
+ assert_rule_from_payload(self.rule, payload)
|
|
|
|
|
|
@responses.activate
|
|
|
def test_update_channel_slack(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
-
|
|
|
- project = self.create_project()
|
|
|
- integration = Integration.objects.create(
|
|
|
- provider="slack",
|
|
|
- name="Awesome Team",
|
|
|
- external_id="TXXXXXXX1",
|
|
|
- metadata={
|
|
|
- "access_token": "xoxp-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx",
|
|
|
- "installation_type": "born_as_bot",
|
|
|
- },
|
|
|
- )
|
|
|
- integration.add_organization(project.organization, self.user)
|
|
|
-
|
|
|
conditions = [{"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition"}]
|
|
|
-
|
|
|
actions = [
|
|
|
{
|
|
|
"channel_id": "old_channel_id",
|
|
|
- "workspace": integration.id,
|
|
|
+ "workspace": str(self.slack_integration.id),
|
|
|
"id": "sentry.integrations.slack.notify_action.SlackNotifyServiceAction",
|
|
|
"channel": "#old_channel_name",
|
|
|
}
|
|
|
]
|
|
|
-
|
|
|
- rule = Rule.objects.create(
|
|
|
- project=project,
|
|
|
- data={"conditions": [conditions], "actions": [actions]},
|
|
|
- )
|
|
|
+ self.rule.update(data={"conditions": conditions, "actions": actions})
|
|
|
|
|
|
actions[0]["channel"] = "#new_channel_name"
|
|
|
actions[0]["channel_id"] = "new_channel_id"
|
|
|
-
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project.organization.slug,
|
|
|
- "project_slug": project.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
- )
|
|
|
-
|
|
|
channels = {
|
|
|
"ok": "true",
|
|
|
"channels": [
|
|
@@ -616,63 +416,31 @@ class UpdateProjectRuleTest(APITestCase):
|
|
|
body=json.dumps({"ok": channels["ok"], "channel": channels["channels"][1]}),
|
|
|
)
|
|
|
|
|
|
- response = self.client.put(
|
|
|
- url,
|
|
|
- data={
|
|
|
- "name": "#new_channel_name",
|
|
|
- "actionMatch": "any",
|
|
|
- "filterMatch": "any",
|
|
|
- "actions": actions,
|
|
|
- "conditions": conditions,
|
|
|
- "frequency": 30,
|
|
|
- },
|
|
|
- format="json",
|
|
|
+ payload = {
|
|
|
+ "name": "#new_channel_name",
|
|
|
+ "actionMatch": "any",
|
|
|
+ "filterMatch": "any",
|
|
|
+ "actions": actions,
|
|
|
+ "conditions": conditions,
|
|
|
+ "frequency": 30,
|
|
|
+ }
|
|
|
+ self.get_success_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=200, **payload
|
|
|
)
|
|
|
-
|
|
|
- assert response.status_code == 200, response.content
|
|
|
- rule = Rule.objects.get(id=response.data["id"])
|
|
|
- assert rule.label == "#new_channel_name"
|
|
|
- assert rule.data["actions"][0]["channel_id"] == "new_channel_id"
|
|
|
+ assert_rule_from_payload(self.rule, payload)
|
|
|
|
|
|
@responses.activate
|
|
|
def test_update_channel_slack_workspace_fail(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
-
|
|
|
- project = self.create_project()
|
|
|
- integration = Integration.objects.create(
|
|
|
- provider="slack",
|
|
|
- name="Awesome Team",
|
|
|
- external_id="TXXXXXXX1",
|
|
|
- metadata={"access_token": "xoxp-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx"},
|
|
|
- )
|
|
|
- integration.add_organization(project.organization, self.user)
|
|
|
-
|
|
|
conditions = [{"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition"}]
|
|
|
-
|
|
|
actions = [
|
|
|
{
|
|
|
"channel_id": "old_channel_id",
|
|
|
- "workspace": integration.id,
|
|
|
+ "workspace": str(self.slack_integration.id),
|
|
|
"id": "sentry.integrations.slack.notify_action.SlackNotifyServiceAction",
|
|
|
"channel": "#old_channel_name",
|
|
|
}
|
|
|
]
|
|
|
-
|
|
|
- rule = Rule.objects.create(
|
|
|
- project=project,
|
|
|
- data={"conditions": [conditions], "actions": [actions]},
|
|
|
- )
|
|
|
-
|
|
|
- actions[0]["channel"] = "#new_channel_name"
|
|
|
-
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project.organization.slug,
|
|
|
- "project_slug": project.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
- )
|
|
|
+ self.rule.update(data={"conditions": conditions, "actions": actions})
|
|
|
|
|
|
channels = {
|
|
|
"ok": "true",
|
|
@@ -696,380 +464,208 @@ class UpdateProjectRuleTest(APITestCase):
|
|
|
body=json.dumps({"ok": channels["ok"], "channel": channels["channels"][0]}),
|
|
|
)
|
|
|
|
|
|
- response = self.client.put(
|
|
|
- url,
|
|
|
- data={
|
|
|
- "name": "#new_channel_name",
|
|
|
- "actionMatch": "any",
|
|
|
- "filterMatch": "any",
|
|
|
- "actions": actions,
|
|
|
- "conditions": conditions,
|
|
|
- "frequency": 30,
|
|
|
- },
|
|
|
- format="json",
|
|
|
+ actions[0]["channel"] = "#new_channel_name"
|
|
|
+ payload = {
|
|
|
+ "name": "#new_channel_name",
|
|
|
+ "actionMatch": "any",
|
|
|
+ "filterMatch": "any",
|
|
|
+ "actions": actions,
|
|
|
+ "conditions": conditions,
|
|
|
+ "frequency": 30,
|
|
|
+ }
|
|
|
+ self.get_error_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=400, **payload
|
|
|
)
|
|
|
|
|
|
- assert response.status_code == 400, response.content
|
|
|
-
|
|
|
@responses.activate
|
|
|
def test_slack_channel_id_saved(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
-
|
|
|
- project = self.create_project()
|
|
|
-
|
|
|
- rule = Rule.objects.create(
|
|
|
- project=project,
|
|
|
- environment_id=Environment.get_or_create(project, "production").id,
|
|
|
- label="foo",
|
|
|
- )
|
|
|
- integration = Integration.objects.create(
|
|
|
- provider="slack",
|
|
|
- name="Awesome Team",
|
|
|
- external_id="TXXXXXXX1",
|
|
|
- metadata={"access_token": "xoxp-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx"},
|
|
|
- )
|
|
|
- integration.add_organization(project.organization, self.user)
|
|
|
-
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project.organization.slug,
|
|
|
- "project_slug": project.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
- )
|
|
|
+ channel_id = "CSVK0921"
|
|
|
responses.add(
|
|
|
method=responses.GET,
|
|
|
url="https://slack.com/api/conversations.info",
|
|
|
status=200,
|
|
|
content_type="application/json",
|
|
|
body=json.dumps(
|
|
|
- {"ok": "true", "channel": {"name": "team-team-team", "id": "CSVK0921"}}
|
|
|
+ {"ok": "true", "channel": {"name": "team-team-team", "id": channel_id}}
|
|
|
),
|
|
|
)
|
|
|
- response = self.client.put(
|
|
|
- url,
|
|
|
- data={
|
|
|
- "name": "hello world",
|
|
|
- "environment": None,
|
|
|
- "actionMatch": "any",
|
|
|
- "actions": [
|
|
|
- {
|
|
|
- "id": "sentry.integrations.slack.notify_action.SlackNotifyServiceAction",
|
|
|
- "name": "Send a notification to the funinthesun Slack workspace to #team-team-team and show tags [] in notification",
|
|
|
- "workspace": integration.id,
|
|
|
- "channel": "#team-team-team",
|
|
|
- "channel_id": "CSVK0921",
|
|
|
- }
|
|
|
- ],
|
|
|
- "conditions": [
|
|
|
- {"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition"}
|
|
|
- ],
|
|
|
- },
|
|
|
- format="json",
|
|
|
+ payload = {
|
|
|
+ "name": "hello world",
|
|
|
+ "environment": None,
|
|
|
+ "actionMatch": "any",
|
|
|
+ "actions": [
|
|
|
+ {
|
|
|
+ "id": "sentry.integrations.slack.notify_action.SlackNotifyServiceAction",
|
|
|
+ "name": "Send a notification to the funinthesun Slack workspace to #team-team-team and show tags [] in notification",
|
|
|
+ "workspace": str(self.slack_integration.id),
|
|
|
+ "channel": "#team-team-team",
|
|
|
+ "channel_id": channel_id,
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "conditions": [
|
|
|
+ {"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition"}
|
|
|
+ ],
|
|
|
+ }
|
|
|
+ response = self.get_success_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=200, **payload
|
|
|
)
|
|
|
-
|
|
|
- assert response.status_code == 200, response.content
|
|
|
- assert response.data["id"] == str(rule.id)
|
|
|
- assert response.data["actions"][0]["channel_id"] == "CSVK0921"
|
|
|
+ assert response.data["id"] == str(self.rule.id)
|
|
|
+ assert response.data["actions"][0]["channel_id"] == channel_id
|
|
|
|
|
|
def test_invalid_rule_node_type(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
-
|
|
|
- project = self.create_project()
|
|
|
-
|
|
|
- rule = Rule.objects.create(project=project, label="foo")
|
|
|
-
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project.organization.slug,
|
|
|
- "project_slug": project.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
- )
|
|
|
- response = self.client.put(
|
|
|
- url,
|
|
|
- data={
|
|
|
- "name": "hello world",
|
|
|
- "actionMatch": "any",
|
|
|
- "filterMatch": "any",
|
|
|
- "conditions": [{"id": "sentry.rules.actions.notify_event.NotifyEventAction"}],
|
|
|
- "actions": [],
|
|
|
- },
|
|
|
- format="json",
|
|
|
+ payload = {
|
|
|
+ "name": "hello world",
|
|
|
+ "actionMatch": "any",
|
|
|
+ "filterMatch": "any",
|
|
|
+ "conditions": [{"id": "sentry.rules.actions.notify_event.NotifyEventAction"}],
|
|
|
+ "actions": [],
|
|
|
+ }
|
|
|
+ self.get_error_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=400, **payload
|
|
|
)
|
|
|
|
|
|
- assert response.status_code == 400, response.content
|
|
|
-
|
|
|
def test_invalid_rule_node(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
-
|
|
|
- project = self.create_project()
|
|
|
-
|
|
|
- rule = Rule.objects.create(project=project, label="foo")
|
|
|
-
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project.organization.slug,
|
|
|
- "project_slug": project.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
- )
|
|
|
- response = self.client.put(
|
|
|
- url,
|
|
|
- data={
|
|
|
- "name": "hello world",
|
|
|
- "actionMatch": "any",
|
|
|
- "filterMatch": "any",
|
|
|
- "conditions": [{"id": "sentry.rules.actions.notify_event.NotifyEventAction"}],
|
|
|
- "actions": [{"id": "foo"}],
|
|
|
- },
|
|
|
- format="json",
|
|
|
+ payload = {
|
|
|
+ "name": "hello world",
|
|
|
+ "actionMatch": "any",
|
|
|
+ "filterMatch": "any",
|
|
|
+ "conditions": [{"id": "sentry.rules.actions.notify_event.NotifyEventAction"}],
|
|
|
+ "actions": [{"id": "foo"}],
|
|
|
+ }
|
|
|
+ self.get_error_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=400, **payload
|
|
|
)
|
|
|
|
|
|
- assert response.status_code == 400, response.content
|
|
|
-
|
|
|
def test_rule_form_not_valid(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
-
|
|
|
- project = self.create_project()
|
|
|
-
|
|
|
- rule = Rule.objects.create(project=project, label="foo")
|
|
|
-
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project.organization.slug,
|
|
|
- "project_slug": project.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
- )
|
|
|
- response = self.client.put(
|
|
|
- url,
|
|
|
- data={
|
|
|
- "name": "hello world",
|
|
|
- "actionMatch": "any",
|
|
|
- "filterMatch": "any",
|
|
|
- "conditions": [{"id": "sentry.rules.conditions.tagged_event.TaggedEventCondition"}],
|
|
|
- "actions": [],
|
|
|
- },
|
|
|
- format="json",
|
|
|
+ payload = {
|
|
|
+ "name": "hello world",
|
|
|
+ "actionMatch": "any",
|
|
|
+ "filterMatch": "any",
|
|
|
+ "conditions": [{"id": "sentry.rules.conditions.tagged_event.TaggedEventCondition"}],
|
|
|
+ "actions": [],
|
|
|
+ }
|
|
|
+ self.get_error_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=400, **payload
|
|
|
)
|
|
|
|
|
|
- assert response.status_code == 400, response.content
|
|
|
-
|
|
|
def test_rule_form_owner_perms(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
-
|
|
|
- project = self.create_project()
|
|
|
-
|
|
|
- rule = Rule.objects.create(project=project, label="foo")
|
|
|
-
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project.organization.slug,
|
|
|
- "project_slug": project.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
- )
|
|
|
- other_user = self.create_user()
|
|
|
- response = self.client.put(
|
|
|
- url,
|
|
|
- data={
|
|
|
- "name": "hello world",
|
|
|
- "actionMatch": "any",
|
|
|
- "filterMatch": "any",
|
|
|
- "conditions": [{"id": "sentry.rules.conditions.tagged_event.TaggedEventCondition"}],
|
|
|
- "actions": [],
|
|
|
- "owner": other_user.actor.get_actor_identifier(),
|
|
|
- },
|
|
|
- format="json",
|
|
|
+ new_user = self.create_user()
|
|
|
+ payload = {
|
|
|
+ "name": "hello world",
|
|
|
+ "actionMatch": "any",
|
|
|
+ "filterMatch": "any",
|
|
|
+ "conditions": [{"id": "sentry.rules.conditions.tagged_event.TaggedEventCondition"}],
|
|
|
+ "actions": [],
|
|
|
+ "owner": new_user.actor.get_actor_identifier(),
|
|
|
+ }
|
|
|
+ response = self.get_error_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=400, **payload
|
|
|
)
|
|
|
-
|
|
|
- assert response.status_code == 400, response.content
|
|
|
assert str(response.data["owner"][0]) == "User is not a member of this organization"
|
|
|
|
|
|
def test_rule_form_missing_action(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
-
|
|
|
- project = self.create_project()
|
|
|
-
|
|
|
- rule = Rule.objects.create(project=project, label="foo")
|
|
|
-
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project.organization.slug,
|
|
|
- "project_slug": project.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
- )
|
|
|
- response = self.client.put(
|
|
|
- url,
|
|
|
- data={
|
|
|
- "name": "hello world",
|
|
|
- "actionMatch": "any",
|
|
|
- "filterMatch": "any",
|
|
|
- "action": [],
|
|
|
- "conditions": [{"id": "sentry.rules.conditions.tagged_event.TaggedEventCondition"}],
|
|
|
- },
|
|
|
- format="json",
|
|
|
+ payload = {
|
|
|
+ "name": "hello world",
|
|
|
+ "actionMatch": "any",
|
|
|
+ "filterMatch": "any",
|
|
|
+ "action": [],
|
|
|
+ "conditions": [{"id": "sentry.rules.conditions.tagged_event.TaggedEventCondition"}],
|
|
|
+ }
|
|
|
+ self.get_error_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=400, **payload
|
|
|
)
|
|
|
|
|
|
- assert response.status_code == 400, response.content
|
|
|
-
|
|
|
def test_update_filters(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
-
|
|
|
- project = self.create_project()
|
|
|
-
|
|
|
- rule = Rule.objects.create(project=project, label="foo")
|
|
|
-
|
|
|
conditions = [{"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition"}]
|
|
|
filters = [
|
|
|
{"id": "sentry.rules.filters.issue_occurrences.IssueOccurrencesFilter", "value": 10}
|
|
|
]
|
|
|
-
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project.organization.slug,
|
|
|
- "project_slug": project.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
- )
|
|
|
- response = self.client.put(
|
|
|
- url,
|
|
|
- data={
|
|
|
- "name": "hello world",
|
|
|
- "actionMatch": "any",
|
|
|
- "filterMatch": "any",
|
|
|
- "actions": [{"id": "sentry.rules.actions.notify_event.NotifyEventAction"}],
|
|
|
- "conditions": conditions,
|
|
|
- "filters": filters,
|
|
|
- },
|
|
|
- format="json",
|
|
|
+ payload = {
|
|
|
+ "name": "hello world",
|
|
|
+ "actionMatch": "any",
|
|
|
+ "filterMatch": "any",
|
|
|
+ "actions": [{"id": "sentry.rules.actions.notify_event.NotifyEventAction"}],
|
|
|
+ "conditions": conditions,
|
|
|
+ "filters": filters,
|
|
|
+ }
|
|
|
+ response = self.get_success_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=200, **payload
|
|
|
)
|
|
|
+ assert response.data["id"] == str(self.rule.id)
|
|
|
|
|
|
- assert response.status_code == 200, response.content
|
|
|
- assert response.data["id"] == str(rule.id)
|
|
|
+ assert_rule_from_payload(self.rule, payload)
|
|
|
|
|
|
- rule = Rule.objects.get(id=rule.id)
|
|
|
- assert rule.label == "hello world"
|
|
|
- assert rule.environment_id is None
|
|
|
- assert rule.data["action_match"] == "any"
|
|
|
- assert rule.data["filter_match"] == "any"
|
|
|
- assert rule.data["actions"] == [
|
|
|
- {"id": "sentry.rules.actions.notify_event.NotifyEventAction"}
|
|
|
+ @responses.activate
|
|
|
+ def test_update_sentry_app_action_success(self):
|
|
|
+ responses.add(
|
|
|
+ method=responses.POST,
|
|
|
+ url="https://example.com/sentry/alert-rule",
|
|
|
+ status=202,
|
|
|
+ )
|
|
|
+ actions = [
|
|
|
+ {
|
|
|
+ "id": "sentry.rules.actions.notify_event_sentry_app.NotifyEventSentryAppAction",
|
|
|
+ "settings": self.sentry_app_settings_payload,
|
|
|
+ "sentryAppInstallationUuid": self.sentry_app_installation.uuid,
|
|
|
+ "hasSchemaFormConfig": True,
|
|
|
+ },
|
|
|
]
|
|
|
- assert rule.data["conditions"] == conditions + filters
|
|
|
-
|
|
|
- assert RuleActivity.objects.filter(rule=rule, type=RuleActivityType.UPDATED.value).exists()
|
|
|
-
|
|
|
- @patch("sentry.mediators.alert_rule_actions.AlertRuleActionCreator.run")
|
|
|
- def test_update_alert_rule_action(self, mock_alert_rule_action_creator):
|
|
|
- """
|
|
|
- Ensures that Sentry Apps with schema forms (UI components)
|
|
|
- receive a payload when an alert rule is updated with them.
|
|
|
- """
|
|
|
- self.login_as(user=self.user)
|
|
|
-
|
|
|
- project = self.create_project()
|
|
|
-
|
|
|
- rule = Rule.objects.create(project=project, label="my super cool rule")
|
|
|
|
|
|
- sentry_app = self.create_sentry_app(
|
|
|
- name="Pied Piper",
|
|
|
- organization=project.organization,
|
|
|
- schema={"elements": [self.create_alert_rule_action_schema()]},
|
|
|
- )
|
|
|
- install = self.create_sentry_app_installation(
|
|
|
- slug="pied-piper", organization=project.organization
|
|
|
+ payload = {
|
|
|
+ "name": "my super cool rule",
|
|
|
+ "actionMatch": "any",
|
|
|
+ "filterMatch": "any",
|
|
|
+ "actions": actions,
|
|
|
+ "conditions": [],
|
|
|
+ "filters": [],
|
|
|
+ }
|
|
|
+ self.get_success_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=200, **payload
|
|
|
)
|
|
|
+ assert_rule_from_payload(self.rule, payload)
|
|
|
+ assert len(responses.calls) == 1
|
|
|
|
|
|
- sentry_app_component = SentryAppComponent.objects.get(
|
|
|
- sentry_app=sentry_app, type="alert-rule-action"
|
|
|
+ @responses.activate
|
|
|
+ def test_update_sentry_app_action_failure(self):
|
|
|
+ error_message = "Something is totally broken :'("
|
|
|
+ responses.add(
|
|
|
+ method=responses.POST,
|
|
|
+ url="https://example.com/sentry/alert-rule",
|
|
|
+ status=500,
|
|
|
+ json={"message": error_message},
|
|
|
)
|
|
|
-
|
|
|
actions = [
|
|
|
{
|
|
|
"id": "sentry.rules.actions.notify_event_sentry_app.NotifyEventSentryAppAction",
|
|
|
- "settings": [
|
|
|
- {"name": "title", "value": "Team Rocket"},
|
|
|
- {"name": "summary", "value": "We're blasting off again."},
|
|
|
- ],
|
|
|
- "sentryAppInstallationUuid": install.uuid,
|
|
|
+ "settings": self.sentry_app_settings_payload,
|
|
|
+ "sentryAppInstallationUuid": self.sentry_app_installation.uuid,
|
|
|
"hasSchemaFormConfig": True,
|
|
|
},
|
|
|
]
|
|
|
-
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project.organization.slug,
|
|
|
- "project_slug": project.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
- )
|
|
|
- with patch(
|
|
|
- "sentry.mediators.sentry_app_components.Preparer.run", return_value=sentry_app_component
|
|
|
- ):
|
|
|
- response = self.client.put(
|
|
|
- url,
|
|
|
- data={
|
|
|
- "name": "my super cool rule",
|
|
|
- "actionMatch": "any",
|
|
|
- "filterMatch": "any",
|
|
|
- "actions": actions,
|
|
|
- "conditions": [],
|
|
|
- "filters": [],
|
|
|
- },
|
|
|
- format="json",
|
|
|
- )
|
|
|
-
|
|
|
- assert response.status_code == 200, response.content
|
|
|
- assert response.data["id"] == str(rule.id)
|
|
|
-
|
|
|
- rule = Rule.objects.get(id=rule.id)
|
|
|
- assert rule.data["actions"] == actions
|
|
|
-
|
|
|
- kwargs = {
|
|
|
- "install": install,
|
|
|
- "fields": actions[0].get("settings"),
|
|
|
+ payload = {
|
|
|
+ "name": "my super cool rule",
|
|
|
+ "actionMatch": "any",
|
|
|
+ "filterMatch": "any",
|
|
|
+ "actions": actions,
|
|
|
+ "conditions": [],
|
|
|
+ "filters": [],
|
|
|
}
|
|
|
-
|
|
|
- call_kwargs = mock_alert_rule_action_creator.call_args[1]
|
|
|
-
|
|
|
- assert call_kwargs["install"].id == kwargs["install"].id
|
|
|
- assert call_kwargs["fields"] == kwargs["fields"]
|
|
|
-
|
|
|
- assert RuleActivity.objects.filter(rule=rule, type=RuleActivityType.UPDATED.value).exists()
|
|
|
-
|
|
|
-
|
|
|
-class DeleteProjectRuleTest(APITestCase):
|
|
|
- def test_simple(self):
|
|
|
- self.login_as(user=self.user)
|
|
|
-
|
|
|
- project = self.create_project()
|
|
|
-
|
|
|
- rule = Rule.objects.create(project=project, label="foo")
|
|
|
-
|
|
|
- url = reverse(
|
|
|
- "sentry-api-0-project-rule-details",
|
|
|
- kwargs={
|
|
|
- "organization_slug": project.organization.slug,
|
|
|
- "project_slug": project.slug,
|
|
|
- "rule_id": rule.id,
|
|
|
- },
|
|
|
+ response = self.get_error_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=400, **payload
|
|
|
)
|
|
|
- response = self.client.delete(url)
|
|
|
+ assert len(responses.calls) == 1
|
|
|
+ assert error_message in response.json().get("actions")[0]
|
|
|
|
|
|
- assert response.status_code == 202, response.content
|
|
|
|
|
|
- rule = Rule.objects.get(id=rule.id)
|
|
|
- assert rule.status == RuleStatus.PENDING_DELETION
|
|
|
+class DeleteProjectRuleTest(ProjectRuleDetailsBaseTestCase):
|
|
|
+ method = "DELETE"
|
|
|
|
|
|
- assert RuleActivity.objects.filter(rule=rule, type=RuleActivityType.DELETED.value).exists()
|
|
|
+ def test_simple(self):
|
|
|
+ self.get_success_response(
|
|
|
+ self.organization.slug, self.project.slug, self.rule.id, status_code=202
|
|
|
+ )
|
|
|
+ self.rule.refresh_from_db()
|
|
|
+ assert self.rule.status == RuleStatus.PENDING_DELETION
|
|
|
+ assert RuleActivity.objects.filter(
|
|
|
+ rule=self.rule, type=RuleActivityType.DELETED.value
|
|
|
+ ).exists()
|