2
2
from unittest import mock
3
3
4
4
import orjson
5
+ import pytest
5
6
from urllib3 .response import HTTPResponse
6
7
8
+ from sentry .incidents .handlers .condition .anomaly_detection_handler import DetectorError
7
9
from sentry .incidents .utils .types import MetricDetectorUpdate
8
10
from sentry .seer .anomaly_detection .types import (
9
11
AnomalyDetectionSeasonality ,
10
12
AnomalyDetectionSensitivity ,
11
13
AnomalyDetectionThresholdType ,
12
14
AnomalyType ,
15
+ DataSourceType ,
13
16
DetectAnomaliesResponse ,
14
17
)
15
18
from sentry .snuba .subscriptions import create_snuba_subscription
@@ -82,7 +85,7 @@ def test_passes(self, mock_seer_request):
82
85
@mock .patch (
83
86
"sentry.seer.anomaly_detection.get_anomaly_data.SEER_ANOMALY_DETECTION_CONNECTION_POOL.urlopen"
84
87
)
85
- def test_does_not_pass (self , mock_seer_request ):
88
+ def test_passes_medium (self , mock_seer_request ):
86
89
seer_return_value : DetectAnomaliesResponse = {
87
90
"success" : True ,
88
91
"timeseries" : [
@@ -97,4 +100,73 @@ def test_does_not_pass(self, mock_seer_request):
97
100
],
98
101
}
99
102
mock_seer_request .return_value = HTTPResponse (orjson .dumps (seer_return_value ), status = 200 )
100
- self .assert_does_not_pass (self .dc , self .data_packet )
103
+ assert self .dc .evaluate_value (self .data_packet ) == DetectorPriorityLevel .MEDIUM
104
+
105
+ @mock .patch (
106
+ "sentry.seer.anomaly_detection.get_anomaly_data.SEER_ANOMALY_DETECTION_CONNECTION_POOL.urlopen"
107
+ )
108
+ @mock .patch ("sentry.seer.anomaly_detection.get_anomaly_data.logger" )
109
+ def test_seer_call_timeout_error (self , mock_logger , mock_seer_request ):
110
+ from urllib3 .exceptions import TimeoutError
111
+
112
+ mock_seer_request .side_effect = TimeoutError
113
+ timeout_extra = {
114
+ "subscription_id" : self .subscription .id ,
115
+ "organization_id" : self .organization .id ,
116
+ "project_id" : self .project .id ,
117
+ "source_id" : self .subscription .id ,
118
+ "source_type" : DataSourceType .SNUBA_QUERY_SUBSCRIPTION ,
119
+ "dataset" : self .subscription .snuba_query .dataset ,
120
+ }
121
+ with pytest .raises (DetectorError ):
122
+ self .dc .evaluate_value (self .data_packet )
123
+ mock_logger .warning .assert_called_with (
124
+ "Timeout error when hitting anomaly detection endpoint" , extra = timeout_extra
125
+ )
126
+
127
+ @mock .patch (
128
+ "sentry.seer.anomaly_detection.get_anomaly_data.SEER_ANOMALY_DETECTION_CONNECTION_POOL.urlopen"
129
+ )
130
+ @mock .patch ("sentry.seer.anomaly_detection.get_anomaly_data.logger" )
131
+ def test_seer_call_empty_list (self , mock_logger , mock_seer_request ):
132
+ seer_return_value : DetectAnomaliesResponse = {"success" : True , "timeseries" : []}
133
+ mock_seer_request .return_value = HTTPResponse (orjson .dumps (seer_return_value ), status = 200 )
134
+ with pytest .raises (DetectorError ):
135
+ self .dc .evaluate_value (self .data_packet )
136
+ assert mock_logger .warning .call_args [0 ] == (
137
+ "Seer anomaly detection response returned no potential anomalies" ,
138
+ )
139
+
140
+ @mock .patch (
141
+ "sentry.seer.anomaly_detection.get_anomaly_data.SEER_ANOMALY_DETECTION_CONNECTION_POOL.urlopen"
142
+ )
143
+ @mock .patch ("sentry.seer.anomaly_detection.get_anomaly_data.logger" )
144
+ def test_seer_call_bad_status (self , mock_logger , mock_seer_request ):
145
+ mock_seer_request .return_value = HTTPResponse (status = 403 )
146
+ extra = {
147
+ "subscription_id" : self .subscription .id ,
148
+ "organization_id" : self .organization .id ,
149
+ "project_id" : self .project .id ,
150
+ "source_id" : self .subscription .id ,
151
+ "source_type" : DataSourceType .SNUBA_QUERY_SUBSCRIPTION ,
152
+ "dataset" : self .subscription .snuba_query .dataset ,
153
+ "response_data" : None ,
154
+ }
155
+ with pytest .raises (DetectorError ):
156
+ self .dc .evaluate_value (self .data_packet )
157
+ mock_logger .error .assert_called_with (
158
+ "Error when hitting Seer detect anomalies endpoint" , extra = extra
159
+ )
160
+
161
+ @mock .patch (
162
+ "sentry.seer.anomaly_detection.get_anomaly_data.SEER_ANOMALY_DETECTION_CONNECTION_POOL.urlopen"
163
+ )
164
+ @mock .patch ("sentry.seer.anomaly_detection.get_anomaly_data.logger" )
165
+ def test_seer_call_failed_parse (self , mock_logger , mock_seer_request ):
166
+ # XXX: coercing a response into something that will fail to parse
167
+ mock_seer_request .return_value = HTTPResponse (None , status = 200 ) # type: ignore[arg-type]
168
+ with pytest .raises (DetectorError ):
169
+ self .dc .evaluate_value (self .data_packet )
170
+ mock_logger .exception .assert_called_with (
171
+ "Failed to parse Seer anomaly detection response" , extra = mock .ANY
172
+ )
0 commit comments