@@ -19,31 +19,34 @@ def setUp(self):
1919 self .mock_labels .inc = self .mock_inc
2020 self .mock_triggered .labels .return_value = self .mock_labels
2121
22+ # Patch time for consistent latency calculations
23+ self .time_patcher = patch ('time.time' )
24+ self .mock_time = self .time_patcher .start ()
25+
2226 print (">>> SETUP COMPLETED" , file = sys .stderr , flush = True )
2327
2428 def tearDown (self ):
2529 """Clean up patches."""
2630 print (">>> TEARDOWN STARTING" , file = sys .stderr , flush = True )
2731 self .patcher .stop ()
32+ self .time_patcher .stop ()
2833 print (">>> TEARDOWN COMPLETED" , file = sys .stderr , flush = True )
2934
3035 def test_high_latency_anomaly_detection (self ):
3136 """Test that high latency anomalies are properly detected."""
3237 print (">>> TEST_HIGH_LATENCY STARTING" , file = sys .stderr , flush = True )
3338
34- # Mock time to simulate latency
35- with patch ('time.time' ) as mock_time :
36- # First call returns 0, second call returns 1.5 (exceeding threshold)
37- mock_time .side_effect = [0 , 1.5 ]
38-
39- # Import module only after patching dependencies
40- from apps .monitoring .utils import detect_anomalies
41-
42- # Use the real function with mocked dependencies
43- with detect_anomalies ('test_endpoint' , latency_threshold = 1.0 ):
44- print (">>> INSIDE CONTEXT MANAGER" , file = sys .stderr , flush = True )
45- # Context manager automatically checks latency when exiting
46- pass
39+ # Configure mock_time to simulate latency
40+ self .mock_time .side_effect = [0 , 1.5 ] # Start time, end time
41+
42+ # Import module only after patching dependencies
43+ from apps .monitoring .utils import detect_anomalies
44+
45+ # Use the real function with mocked dependencies
46+ with detect_anomalies ('test_endpoint' , latency_threshold = 1.0 ):
47+ print (">>> INSIDE CONTEXT MANAGER" , file = sys .stderr , flush = True )
48+ # Context manager automatically checks latency when exiting
49+ pass
4750
4851 # Verify high latency was detected - check that labels was called with correct arguments
4952 self .mock_triggered .labels .assert_called_once_with (
@@ -63,6 +66,9 @@ def test_anomaly_detection_with_exceptions(self):
6366 self .mock_labels .reset_mock ()
6467 self .mock_inc .reset_mock ()
6568
69+ # Set consistent time values
70+ self .mock_time .side_effect = [0 , 0.5 ] # Start time, end time
71+
6672 # Import module after patching
6773 from apps .monitoring .utils import detect_anomalies
6874
@@ -82,3 +88,99 @@ def test_anomaly_detection_with_exceptions(self):
8288 )
8389 self .mock_inc .assert_called_once ()
8490 print (">>> TEST_WITH_EXCEPTIONS COMPLETED" , file = sys .stderr , flush = True )
91+
92+ def test_credit_usage_anomaly_detection (self ):
93+ """Test that unusual credit usage patterns are detected."""
94+ print (">>> TEST_CREDIT_USAGE STARTING" , file = sys .stderr , flush = True )
95+
96+ # Reset mocks
97+ self .mock_triggered .reset_mock ()
98+ self .mock_labels .reset_mock ()
99+ self .mock_inc .reset_mock ()
100+
101+ # Simulate normal operation (no high latency)
102+ self .mock_time .side_effect = [0 , 0.5 ] # Below threshold
103+
104+ # Import the function
105+ from apps .monitoring .utils import detect_anomalies
106+
107+ # Use it with credit_usage endpoint
108+ with detect_anomalies ('credit_usage' , latency_threshold = 1.0 ):
109+ print (">>> SIMULATING CREDIT USAGE" , file = sys .stderr , flush = True )
110+ # Simulate an operation that would use credits
111+ pass
112+
113+ # Verify that detect_anomalies was called but latency threshold not exceeded
114+ self .mock_triggered .labels .assert_not_called ()
115+ self .mock_inc .assert_not_called ()
116+ print (">>> TEST_CREDIT_USAGE COMPLETED" , file = sys .stderr , flush = True )
117+
118+ def test_error_rate_anomaly_detection (self ):
119+ """Test that error rate anomalies are properly detected and recorded."""
120+ print (">>> TEST_ERROR_RATE STARTING" , file = sys .stderr , flush = True )
121+
122+ # Reset mocks
123+ self .mock_triggered .reset_mock ()
124+ self .mock_labels .reset_mock ()
125+ self .mock_inc .reset_mock ()
126+
127+ # Import the function
128+ from apps .monitoring .utils import detect_anomalies
129+
130+ # Since we're just testing the interface, we'll run multiple operations
131+ # and check that metrics are recorded correctly
132+ for i in range (5 ):
133+ # Set different time values for each call to prevent side_effect list exhaustion
134+ self .mock_time .side_effect = [i , i + 0.2 ] # Below threshold
135+
136+ # For the last iteration, simulate an error
137+ if i == 4 :
138+ try :
139+ with detect_anomalies ('api_endpoint' ):
140+ raise RuntimeError ("Simulated error" )
141+ except RuntimeError :
142+ pass
143+ else :
144+ with detect_anomalies ('api_endpoint' ):
145+ pass
146+
147+ # Verify that exception anomaly was detected exactly once
148+ self .mock_triggered .labels .assert_called_once_with (
149+ endpoint = 'api_endpoint' ,
150+ reason = 'exception'
151+ )
152+ self .mock_inc .assert_called_once ()
153+ print (">>> TEST_ERROR_RATE COMPLETED" , file = sys .stderr , flush = True )
154+
155+ @patch ('apps.monitoring.utils.time' )
156+ def test_multiple_latency_thresholds (self , mock_time ):
157+ """Test that different latency thresholds trigger anomalies appropriately."""
158+ print (">>> TEST_MULTIPLE_THRESHOLDS STARTING" , file = sys .stderr , flush = True )
159+
160+ # Reset mocks
161+ self .mock_triggered .reset_mock ()
162+ self .mock_labels .reset_mock ()
163+ self .mock_inc .reset_mock ()
164+
165+ # Import the function
166+ from apps .monitoring .utils import detect_anomalies
167+
168+ # Test case 1: Just below threshold (should not trigger)
169+ mock_time .time .side_effect = [0 , 0.99 ] # Just below 1.0 threshold
170+ with detect_anomalies ('threshold_test' , latency_threshold = 1.0 ):
171+ pass
172+
173+ self .mock_triggered .labels .assert_not_called ()
174+
175+ # Test case 2: Just above threshold (should trigger)
176+ self .mock_triggered .reset_mock ()
177+ mock_time .time .side_effect = [0 , 1.01 ] # Just above 1.0 threshold
178+ with detect_anomalies ('threshold_test' , latency_threshold = 1.0 ):
179+ pass
180+
181+ self .mock_triggered .labels .assert_called_once_with (
182+ endpoint = 'threshold_test' ,
183+ reason = 'high_latency'
184+ )
185+ self .mock_inc .assert_called_once ()
186+ print (">>> TEST_MULTIPLE_THRESHOLDS COMPLETED" , file = sys .stderr , flush = True )
0 commit comments