def test_alert_rule_resolved_invalid(self): self.run_fail_validation_test( {"resolve_threshold": 500}, { "nonFieldErrors": [ "critical alert threshold must be above resolution threshold" ] }, ) base_params = self.valid_params.copy() base_params["resolve_threshold"] = 0.5 base_params["triggers"].pop() base_params["triggers"][0]["alertThreshold"] = 0.3 serializer = AlertRuleSerializer(context=self.context, data=base_params) assert not serializer.is_valid() assert serializer.errors == { "nonFieldErrors": ["critical alert threshold must be above resolution threshold"] }
def test_comparison_delta_above(self): params = self.valid_params.copy() params["comparison_delta"] = 60 params["resolveThreshold"] = 10 params["triggers"][0]["alertThreshold"] = 50 params["triggers"][1]["alertThreshold"] = 40 serializer = AlertRuleSerializer(context=self.context, data=params, partial=True) assert serializer.is_valid(), serializer.errors alert_rule = serializer.save() assert alert_rule.comparison_delta == 60 * 60 assert alert_rule.resolve_threshold == 110 triggers = { trigger.label: trigger for trigger in alert_rule.alertruletrigger_set.all() } assert triggers["critical"].alert_threshold == 150 assert triggers["warning"].alert_threshold == 140 assert alert_rule.snuba_query.resolution == DEFAULT_CMP_ALERT_RULE_RESOLUTION * 60
def post(self, request, project): """ Create an alert rule """ if not features.has('organizations:incidents', project.organization, actor=request.user): raise ResourceDoesNotExist serializer = AlertRuleSerializer( context={'project': project}, data=request.data, ) if serializer.is_valid(): alert_rule = serializer.save() return Response(serialize(alert_rule, request.user), status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def test_invalid_slack_channel(self): # We had an error where an invalid slack channel was spitting out unclear # error for the user, and CREATING THE RULE. So the next save (after fixing slack action) # says "Name already in use". This test makes sure that is not happening anymore. # We save a rule with an invalid slack, make sure we get back a useful error # and that the rule is not created. base_params = self.valid_params.copy() base_params["name"] = "Aun1qu3n4m3" integration = Integration.objects.create( external_id="1", provider="slack", metadata={ "access_token": "xoxp-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx" }, ) integration.add_organization(self.organization, self.user) base_params["triggers"][0]["actions"].append({ "type": AlertRuleTriggerAction.get_registered_type( AlertRuleTriggerAction.Type.SLACK).slug, "targetType": action_target_type_to_string[ AlertRuleTriggerAction.TargetType.SPECIFIC], "targetIdentifier": "123", "integration": six.text_type(integration.id), }) serializer = AlertRuleSerializer(context=self.context, data=base_params) assert serializer.is_valid() with self.assertRaises(serializers.ValidationError): serializer.save() # Make sure the rule was not created. assert len(list(AlertRule.objects.filter(name="Aun1qu3n4m3"))) == 0 # Make sure the action was not created. alert_rule_trigger_actions = list( AlertRuleTriggerAction.objects.filter(integration=integration)) assert len(alert_rule_trigger_actions) == 0
def test_aggregate(self): self.run_fail_validation_test( {"aggregate": "what()"}, {"aggregate": ["Invalid Metric: what() is not a valid function"]}, ) self.run_fail_validation_test( {"aggregate": "what"}, { "nonFieldErrors": [ "Invalid Metric: Please pass a valid function for aggregation" ] }, ) self.run_fail_validation_test( {"aggregate": "123"}, { "nonFieldErrors": [ "Invalid Metric: Please pass a valid function for aggregation" ] }, ) self.run_fail_validation_test( {"aggregate": "count_unique(123, hello)"}, { "aggregate": [ "Invalid Metric: count_unique(123, hello): expected 1 arguments" ] }, ) self.run_fail_validation_test( {"aggregate": "max()"}, {"aggregate": ["Invalid Metric: max(): expected 1 arguments"]}) aggregate = "count_unique(tags[sentry:user])" base_params = self.valid_params.copy() base_params["aggregate"] = aggregate serializer = AlertRuleSerializer(context=self.context, data=base_params) assert serializer.is_valid(), serializer.errors alert_rule = serializer.save() assert alert_rule.snuba_query.aggregate == aggregate
def test_alert_rule_threshold_resolve_only(self): resolve_threshold = 10 payload = { "name": "hello_im_a_test", "time_window": 10, "query": "level:error", "aggregate": "count()", "thresholdType": 0, "resolveThreshold": 10, "threshold_period": 1, "projects": [self.project.slug], "triggers": [{ "label": "critical", "alertThreshold": 98, "actions": [{ "type": "email", "targetType": "team", "targetIdentifier": self.team.id }], }], } serializer = AlertRuleSerializer(context=self.context, data=payload, partial=True) assert serializer.is_valid(), serializer.errors assert serializer.validated_data[ "threshold_type"] == AlertRuleThresholdType.ABOVE assert serializer.validated_data[ "resolve_threshold"] == resolve_threshold
def post(self, request, project): """ Create an alert rule """ if not features.has("organizations:incidents", project.organization, actor=request.user): raise ResourceDoesNotExist data = deepcopy(request.data) data["projects"] = [project.slug] serializer = AlertRuleSerializer( context={"organization": project.organization, "access": request.access}, data=data ) if serializer.is_valid(): alert_rule = serializer.save() alert_rule_created.send_robust( user=request.user, project=project, rule=alert_rule, rule_type="metric", sender=self ) return Response(serialize(alert_rule, request.user), status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def test_invalid_team_with_channel_timeout(self, mock_get_channel_id): self.setup_slack_integration() other_org = self.create_organization() new_team = self.create_team(organization=other_org) trigger = { "label": "critical", "alertThreshold": 200, "actions": [ { "type": "slack", "targetIdentifier": "my-channel", "targetType": "specific", "integration": self.integration.id, }, { "type": "email", "targetType": "team", "targetIdentifier": new_team.id }, ], } base_params = self.valid_params.copy() base_params.update({"triggers": [trigger]}) serializer = AlertRuleSerializer(context=self.context, data=base_params) # error is raised during save assert serializer.is_valid() with self.assertRaises(serializers.ValidationError) as err: serializer.save() assert err.exception.detail == { "nonFieldErrors": ["Team does not exist"] } mock_get_channel_id.assert_called_with(self.integration, "my-channel", 10)
def post(self, request: Request, organization) -> Response: """ Create an alert rule """ if not features.has( "organizations:incidents", organization, actor=request.user): raise ResourceDoesNotExist serializer = AlertRuleSerializer( context={ "organization": organization, "access": request.access, "user": request.user }, data=request.data, ) if serializer.is_valid(): alert_rule = serializer.save() return Response(serialize(alert_rule, request.user), status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def run_fail_validation_test(self, params, errors): base_params = self.valid_params.copy() base_params.update(params) serializer = AlertRuleSerializer(context=self.context, data=base_params) assert not serializer.is_valid() assert serializer.errors == errors
def test_boundary(self): payload = { "name": "hello_im_a_test", "time_window": 10, "query": "level:error", "threshold_type": 0, "aggregation": 0, "threshold_period": 1, "projects": [self.project.slug], "triggers": [{ "label": "critical", "alertThreshold": 1, "resolveThreshold": 2, "thresholdType": AlertRuleThresholdType.ABOVE.value, "actions": [{ "type": "email", "targetType": "team", "targetIdentifier": self.team.id }], }], } serializer = AlertRuleSerializer(context=self.context, data=payload, partial=True) assert serializer.is_valid(), serializer.errors # Now do a two trigger test: payload["triggers"].append({ "label": "warning", "alertThreshold": 0, "resolveThreshold": 1, "thresholdType": AlertRuleThresholdType.ABOVE.value, "actions": [ { "type": "email", "targetType": "team", "targetIdentifier": self.team.id }, { "type": "email", "targetType": "user", "targetIdentifier": self.user.id }, ], }) serializer = AlertRuleSerializer(context=self.context, data=payload, partial=True) assert serializer.is_valid(), serializer.errors
def find_channel_id_for_alert_rule(organization_id, uuid, data, alert_rule_id=None, user_id=None): redis_rule_status = RedisRuleStatus(uuid) try: organization = Organization.objects.get(id=organization_id) except Organization.DoesNotExist: redis_rule_status.set_value("failed") return user = None if user_id: try: user = User.objects.get(id=user_id) except User.DoesNotExist: pass alert_rule = None if alert_rule_id: try: alert_rule = AlertRule.objects.get(organization_id=organization_id, id=alert_rule_id) except AlertRule.DoesNotExist: redis_rule_status.set_value("failed") return try: mapped_ids = get_slack_channel_ids(organization, user, data) except (serializers.ValidationError, ChannelLookupTimeoutError, InvalidTriggerActionError) as e: # channel doesn't exist error or validation error logger.info( "get_slack_channel_ids.failed", extra={ "exception": e, }, ) redis_rule_status.set_value("failed") return for trigger in data["triggers"]: for action in trigger["actions"]: if action["type"] == "slack": if action["targetIdentifier"] in mapped_ids: action["input_channel_id"] = mapped_ids[ action["targetIdentifier"]] else: # We can early exit because we couldn't map this action's slack channel name to a slack id # This is a fail safe, but I think we shouldn't really hit this. redis_rule_status.set_value("failed") return # we use SystemAccess here because we can't pass the access instance from the request into the task # this means at this point we won't raise any validation errors associated with permissions # however, we should only be calling this task after we tried saving the alert rule first # which will catch those kinds of validation errors serializer = AlertRuleSerializer( context={ "organization": organization, "access": SystemAccess(), "user": user, "use_async_lookup": True, "validate_channel_id": False, }, data=data, instance=alert_rule, ) if serializer.is_valid(): try: alert_rule = serializer.save() redis_rule_status.set_value("success", alert_rule.id) return # we can still get a validation error for the channel not existing except (serializers.ValidationError, ChannelLookupTimeoutError): # channel doesn't exist error or validation error redis_rule_status.set_value("failed") return # some other error redis_rule_status.set_value("failed") return
def test_transaction_dataset(self): serializer = AlertRuleSerializer(context=self.context, data=self.valid_transaction_params) assert serializer.is_valid(), serializer.errors alert_rule = serializer.save() assert alert_rule.snuba_query.dataset == QueryDatasets.TRANSACTIONS.value assert alert_rule.snuba_query.aggregate == "count()"