-
Notifications
You must be signed in to change notification settings - Fork 711
Expand file tree
/
Copy pathtest_event_utils.py
More file actions
451 lines (368 loc) · 19.6 KB
/
Copy pathtest_event_utils.py
File metadata and controls
451 lines (368 loc) · 19.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
"""Unit tests for event_utils module."""
import logging
import sys
import os
from unittest.mock import Mock, patch
import pytest
# Mock external dependencies at module level
sys.modules['azure'] = Mock()
sys.modules['azure.ai'] = Mock()
sys.modules['azure.ai.projects'] = Mock()
sys.modules['azure.ai.projects.aio'] = Mock()
sys.modules['azure.monitor'] = Mock()
sys.modules['azure.monitor.events'] = Mock()
sys.modules['azure.monitor.events.extension'] = Mock()
sys.modules['azure.core'] = Mock()
sys.modules['azure.core.exceptions'] = Mock()
sys.modules['azure.identity'] = Mock()
sys.modules['azure.identity.aio'] = Mock()
sys.modules['azure.cosmos'] = Mock()
sys.modules['azure.cosmos.aio'] = Mock()
sys.modules['azure.keyvault'] = Mock()
sys.modules['azure.keyvault.secrets'] = Mock()
sys.modules['azure.keyvault.secrets.aio'] = Mock()
# Add the backend directory to the Python path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', 'backend'))
# Set required environment variables for testing
os.environ.setdefault('APPLICATIONINSIGHTS_CONNECTION_STRING', 'test_connection_string')
os.environ.setdefault('APP_ENV', 'dev')
os.environ.setdefault('AZURE_OPENAI_ENDPOINT', 'https://test.openai.azure.com/')
os.environ.setdefault('AZURE_OPENAI_API_KEY', 'test_key')
os.environ.setdefault('AZURE_OPENAI_DEPLOYMENT_NAME', 'test_deployment')
os.environ.setdefault('AZURE_AI_SUBSCRIPTION_ID', 'test_subscription_id')
os.environ.setdefault('AZURE_AI_RESOURCE_GROUP', 'test_resource_group')
os.environ.setdefault('AZURE_AI_PROJECT_NAME', 'test_project_name')
os.environ.setdefault('AZURE_AI_AGENT_ENDPOINT', 'https://test.agent.azure.com/')
os.environ.setdefault('COSMOSDB_ENDPOINT', 'https://test.documents.azure.com:443/')
os.environ.setdefault('COSMOSDB_DATABASE', 'test_database')
os.environ.setdefault('COSMOSDB_CONTAINER', 'test_container')
os.environ.setdefault('AZURE_CLIENT_ID', 'test_client_id')
os.environ.setdefault('AZURE_TENANT_ID', 'test_tenant_id')
from backend.common.utils.event_utils import track_event_if_configured
class TestTrackEventIfConfigured:
"""Test track_event_if_configured function."""
def setup_method(self):
"""Setup for each test method."""
# Clear any cached logging handlers
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
def teardown_method(self):
"""Cleanup after each test method."""
# Clear any cached logging handlers
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
@patch('backend.common.utils.event_utils.track_event')
@patch('backend.common.utils.event_utils.config')
def test_track_event_with_valid_configuration(self, mock_config, mock_track_event):
"""Test track_event_if_configured with valid Application Insights configuration."""
# Setup
mock_config.APPLICATIONINSIGHTS_CONNECTION_STRING = "InstrumentationKey=test-key;IngestionEndpoint=https://test.com/"
event_name = "test_event"
event_data = {"key1": "value1", "key2": "value2"}
# Execute
track_event_if_configured(event_name, event_data)
# Verify
mock_track_event.assert_called_once_with(event_name, event_data)
@patch('backend.common.utils.event_utils.track_event')
@patch('backend.common.utils.event_utils.config')
@patch('backend.common.utils.event_utils.logging')
def test_track_event_with_no_configuration(self, mock_logging, mock_config, mock_track_event):
"""Test track_event_if_configured when Application Insights is not configured."""
# Setup
mock_config.APPLICATIONINSIGHTS_CONNECTION_STRING = None
event_name = "test_event"
event_data = {"key1": "value1"}
# Execute
track_event_if_configured(event_name, event_data)
# Verify
mock_track_event.assert_not_called()
mock_logging.warning.assert_called_once_with(
f"Skipping track_event for {event_name} as Application Insights is not configured"
)
@patch('backend.common.utils.event_utils.track_event')
@patch('backend.common.utils.event_utils.config')
@patch('backend.common.utils.event_utils.logging')
def test_track_event_with_empty_configuration(self, mock_logging, mock_config, mock_track_event):
"""Test track_event_if_configured with empty connection string."""
# Setup
mock_config.APPLICATIONINSIGHTS_CONNECTION_STRING = ""
event_name = "test_event"
event_data = {"key1": "value1"}
# Execute
track_event_if_configured(event_name, event_data)
# Verify
mock_track_event.assert_not_called()
mock_logging.warning.assert_called_once_with(
f"Skipping track_event for {event_name} as Application Insights is not configured"
)
@patch('backend.common.utils.event_utils.track_event')
@patch('backend.common.utils.event_utils.config')
@patch('backend.common.utils.event_utils.logging')
def test_track_event_handles_attribute_error(self, mock_logging, mock_config, mock_track_event):
"""Test track_event_if_configured handles AttributeError (ProxyLogger error)."""
# Setup
mock_config.APPLICATIONINSIGHTS_CONNECTION_STRING = "valid_connection_string"
mock_track_event.side_effect = AttributeError("'ProxyLogger' object has no attribute 'resource'")
event_name = "test_event"
event_data = {"key1": "value1"}
# Execute
track_event_if_configured(event_name, event_data)
# Verify
mock_track_event.assert_called_once_with(event_name, event_data)
mock_logging.warning.assert_called_once_with(
"ProxyLogger error in track_event: 'ProxyLogger' object has no attribute 'resource'"
)
@patch('backend.common.utils.event_utils.track_event')
@patch('backend.common.utils.event_utils.config')
@patch('backend.common.utils.event_utils.logging')
def test_track_event_handles_generic_exception(self, mock_logging, mock_config, mock_track_event):
"""Test track_event_if_configured handles generic exceptions."""
# Setup
mock_config.APPLICATIONINSIGHTS_CONNECTION_STRING = "valid_connection_string"
mock_track_event.side_effect = RuntimeError("Unexpected error occurred")
event_name = "test_event"
event_data = {"key1": "value1"}
# Execute
track_event_if_configured(event_name, event_data)
# Verify
mock_track_event.assert_called_once_with(event_name, event_data)
mock_logging.warning.assert_called_once_with(
"Error in track_event: Unexpected error occurred"
)
@patch('backend.common.utils.event_utils.track_event')
@patch('backend.common.utils.event_utils.config')
def test_track_event_with_complex_event_data(self, mock_config, mock_track_event):
"""Test track_event_if_configured with complex event data structures."""
# Setup
mock_config.APPLICATIONINSIGHTS_CONNECTION_STRING = "valid_connection_string"
event_name = "complex_event"
event_data = {
"string_value": "test",
"number_value": 42,
"boolean_value": True,
"list_value": [1, 2, 3],
"dict_value": {"nested_key": "nested_value"},
"null_value": None
}
# Execute
track_event_if_configured(event_name, event_data)
# Verify
mock_track_event.assert_called_once_with(event_name, event_data)
@patch('backend.common.utils.event_utils.track_event')
@patch('backend.common.utils.event_utils.config')
def test_track_event_with_empty_event_data(self, mock_config, mock_track_event):
"""Test track_event_if_configured with empty event data."""
# Setup
mock_config.APPLICATIONINSIGHTS_CONNECTION_STRING = "valid_connection_string"
event_name = "empty_data_event"
event_data = {}
# Execute
track_event_if_configured(event_name, event_data)
# Verify
mock_track_event.assert_called_once_with(event_name, event_data)
@patch('backend.common.utils.event_utils.track_event')
@patch('backend.common.utils.event_utils.config')
def test_track_event_with_special_characters_in_name(self, mock_config, mock_track_event):
"""Test track_event_if_configured with special characters in event name."""
# Setup
mock_config.APPLICATIONINSIGHTS_CONNECTION_STRING = "valid_connection_string"
event_name = "test-event_with.special@characters123"
event_data = {"test": "data"}
# Execute
track_event_if_configured(event_name, event_data)
# Verify
mock_track_event.assert_called_once_with(event_name, event_data)
@patch('backend.common.utils.event_utils.track_event')
@patch('backend.common.utils.event_utils.config')
@patch('backend.common.utils.event_utils.logging')
def test_track_event_multiple_calls_with_mixed_scenarios(self, mock_logging, mock_config, mock_track_event):
"""Test track_event_if_configured with multiple calls having different scenarios."""
# Setup
mock_config.APPLICATIONINSIGHTS_CONNECTION_STRING = "valid_connection_string"
# First call - successful
track_event_if_configured("event1", {"data": "test1"})
# Second call - with AttributeError
mock_track_event.side_effect = AttributeError("ProxyLogger error")
track_event_if_configured("event2", {"data": "test2"})
# Third call - reset and successful again
mock_track_event.side_effect = None
track_event_if_configured("event3", {"data": "test3"})
# Verify
assert mock_track_event.call_count == 3
mock_logging.warning.assert_called_once_with("ProxyLogger error in track_event: ProxyLogger error")
class TestEventUtilsIntegration:
"""Test event_utils integration scenarios."""
def setup_method(self):
"""Setup for each test method."""
# Clear any cached logging handlers
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
def teardown_method(self):
"""Cleanup after each test method."""
# Clear any cached logging handlers
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
@patch('backend.common.utils.event_utils.track_event')
def test_track_event_with_real_config_module(self, mock_track_event):
"""Test track_event_if_configured with real config module (mocked at track_event level)."""
# Note: config is already loaded from the real module due to our imports
# We just need to ensure track_event is mocked to avoid actual Azure calls
event_name = "integration_test_event"
event_data = {"integration": "test", "timestamp": "2025-12-08"}
# Execute
track_event_if_configured(event_name, event_data)
# Since we have APPLICATIONINSIGHTS_CONNECTION_STRING set in environment,
# track_event should be called
mock_track_event.assert_called_once_with(event_name, event_data)
@patch('backend.common.utils.event_utils.track_event')
@patch('backend.common.utils.event_utils.config')
def test_track_event_preserves_original_event_data(self, mock_config, mock_track_event):
"""Test that track_event_if_configured preserves original event data."""
# Setup
mock_config.APPLICATIONINSIGHTS_CONNECTION_STRING = "valid_connection_string"
original_event_data = {"mutable": ["list"], "dict": {"key": "value"}}
event_data_copy = original_event_data.copy()
# Execute
track_event_if_configured("test_event", original_event_data)
# Verify original data is unchanged
assert original_event_data == event_data_copy
mock_track_event.assert_called_once_with("test_event", original_event_data)
@patch('backend.common.utils.event_utils.track_event')
@patch('backend.common.utils.event_utils.config')
@patch('backend.common.utils.event_utils.logging')
def test_logging_behavior_with_different_log_levels(self, mock_logging, mock_config, mock_track_event):
"""Test that warnings are logged at the correct level."""
# Setup - no configuration
mock_config.APPLICATIONINSIGHTS_CONNECTION_STRING = None
# Execute
track_event_if_configured("test_event", {"data": "test"})
# Verify warning level is used
mock_logging.warning.assert_called_once()
# Verify other log levels are not called
assert not hasattr(mock_logging, 'info') or not mock_logging.info.called
assert not hasattr(mock_logging, 'error') or not mock_logging.error.called
class TestEventUtilsErrorScenarios:
"""Test error scenarios and edge cases for event_utils."""
def setup_method(self):
"""Setup for each test method."""
# Clear any cached logging handlers
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
def teardown_method(self):
"""Cleanup after each test method."""
# Clear any cached logging handlers
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
@patch('backend.common.utils.event_utils.track_event')
@patch('backend.common.utils.event_utils.config')
@patch('backend.common.utils.event_utils.logging')
def test_track_event_with_various_attribute_errors(self, mock_logging, mock_config, mock_track_event):
"""Test track_event_if_configured with various AttributeError scenarios."""
# Setup
mock_config.APPLICATIONINSIGHTS_CONNECTION_STRING = "valid_connection_string"
# Test different AttributeError messages
attribute_errors = [
"'ProxyLogger' object has no attribute 'resource'",
"'Logger' object has no attribute 'some_method'",
"module 'azure' has no attribute 'monitor'"
]
for error_msg in attribute_errors:
mock_track_event.side_effect = AttributeError(error_msg)
track_event_if_configured("test_event", {"data": "test"})
mock_logging.warning.assert_called_with(f"ProxyLogger error in track_event: {error_msg}")
mock_logging.reset_mock()
@patch('backend.common.utils.event_utils.track_event')
@patch('backend.common.utils.event_utils.config')
@patch('backend.common.utils.event_utils.logging')
def test_track_event_with_various_exceptions(self, mock_logging, mock_config, mock_track_event):
"""Test track_event_if_configured with various exception types."""
# Setup
mock_config.APPLICATIONINSIGHTS_CONNECTION_STRING = "valid_connection_string"
# Test different exception types
exceptions = [
ValueError("Invalid value"),
TypeError("Type mismatch"),
ConnectionError("Network error"),
TimeoutError("Request timeout"),
KeyError("Missing key")
]
for exception in exceptions:
mock_track_event.side_effect = exception
track_event_if_configured("test_event", {"data": "test"})
mock_logging.warning.assert_called_with(f"Error in track_event: {exception}")
mock_logging.reset_mock()
@patch('backend.common.utils.event_utils.track_event')
@patch('backend.common.utils.event_utils.config')
@patch('backend.common.utils.event_utils.logging')
def test_track_event_with_whitespace_connection_string(self, mock_logging, mock_config, mock_track_event):
"""Test track_event_if_configured with whitespace-only connection string."""
# Setup
mock_config.APPLICATIONINSIGHTS_CONNECTION_STRING = " " # Whitespace only
event_name = "test_event"
event_data = {"key1": "value1"}
# Execute
track_event_if_configured(event_name, event_data)
# Verify - whitespace should be treated as truthy, so track_event should be called
mock_track_event.assert_called_once_with(event_name, event_data)
@patch('backend.common.utils.event_utils.track_event')
@patch('backend.common.utils.event_utils.config')
def test_track_event_with_none_event_name(self, mock_config, mock_track_event):
"""Test track_event_if_configured with None event name."""
# Setup
mock_config.APPLICATIONINSIGHTS_CONNECTION_STRING = "valid_connection_string"
# Execute
track_event_if_configured(None, {"data": "test"})
# Verify - the function should pass None through to track_event
mock_track_event.assert_called_once_with(None, {"data": "test"})
@patch('backend.common.utils.event_utils.track_event')
@patch('backend.common.utils.event_utils.config')
def test_track_event_with_none_event_data(self, mock_config, mock_track_event):
"""Test track_event_if_configured with None event data."""
# Setup
mock_config.APPLICATIONINSIGHTS_CONNECTION_STRING = "valid_connection_string"
# Execute
track_event_if_configured("test_event", None)
# Verify - the function should pass None through to track_event
mock_track_event.assert_called_once_with("test_event", None)
class TestEventUtilsParameterValidation:
"""Test parameter validation and type handling for event_utils."""
@patch('backend.common.utils.event_utils.track_event')
@patch('backend.common.utils.event_utils.config')
def test_track_event_with_string_types(self, mock_config, mock_track_event):
"""Test track_event_if_configured with various string types."""
# Setup
mock_config.APPLICATIONINSIGHTS_CONNECTION_STRING = "valid_connection_string"
# Test with different string types
string_types = [
"", # Empty string
"simple_string", # Simple string
"string with spaces", # String with spaces
"string_with_unicode_café", # Unicode string
"very_long_string_" + "x" * 1000 # Long string
]
for event_name in string_types:
track_event_if_configured(event_name, {"type": "string_test"})
mock_track_event.assert_called_with(event_name, {"type": "string_test"})
assert mock_track_event.call_count == len(string_types)
@patch('backend.common.utils.event_utils.track_event')
@patch('backend.common.utils.event_utils.config')
def test_track_event_with_different_data_types(self, mock_config, mock_track_event):
"""Test track_event_if_configured with different event data types."""
# Setup
mock_config.APPLICATIONINSIGHTS_CONNECTION_STRING = "valid_connection_string"
# Test with different data types
data_types = [
{"string": "value"},
{"integer": 42},
{"float": 3.14},
{"boolean": True},
{"list": [1, 2, 3]},
{"nested_dict": {"inner": {"deep": "value"}}},
{"mixed": {"str": "text", "num": 123, "bool": False}}
]
for i, event_data in enumerate(data_types):
track_event_if_configured(f"test_event_{i}", event_data)
mock_track_event.assert_called_with(f"test_event_{i}", event_data)
assert mock_track_event.call_count == len(data_types)
if __name__ == "__main__":
pytest.main([__file__, "-v"])