|
4 | 4 |
|
5 | 5 | import logging |
6 | 6 | from pathlib import Path |
7 | | -from unittest.mock import patch |
| 7 | +from unittest.mock import MagicMock, patch |
8 | 8 |
|
9 | 9 | import pytest |
10 | 10 |
|
@@ -245,3 +245,74 @@ def mock_read_text(self: Path) -> str: |
245 | 245 | ): |
246 | 246 | monitor.check_memory_usage() |
247 | 247 | assert not caplog.records |
| 248 | + |
| 249 | + |
| 250 | +# --------------------------------------------------------------------------- |
| 251 | +# check_memory_usage — Sentry capture_message |
| 252 | +# --------------------------------------------------------------------------- |
| 253 | + |
| 254 | + |
| 255 | +def test_sentry_capture_message_called_on_high_memory() -> None: |
| 256 | + """sentry_sdk.capture_message() should be called once when memory exceeds 90%.""" |
| 257 | + mock_capture = MagicMock() |
| 258 | + monitor = MemoryMonitor(check_interval=1) |
| 259 | + with ( |
| 260 | + patch.object(Path, "exists", _v2_exists), |
| 261 | + patch.object(Path, "read_text", _v2_mock_read(usage=_MOCK_USAGE_AT_90)), |
| 262 | + patch("airbyte_cdk.utils.memory_monitor.sentry_sdk") as mock_sentry, |
| 263 | + ): |
| 264 | + mock_sentry.capture_message = mock_capture |
| 265 | + monitor.check_memory_usage() |
| 266 | + |
| 267 | + mock_capture.assert_called_once() |
| 268 | + call_args = mock_capture.call_args |
| 269 | + assert "91%" in call_args[0][0] |
| 270 | + assert call_args[1]["level"] == "warning" |
| 271 | + |
| 272 | + |
| 273 | +def test_sentry_capture_message_only_once_per_sync() -> None: |
| 274 | + """sentry_sdk.capture_message() should fire only once even if memory stays high.""" |
| 275 | + mock_capture = MagicMock() |
| 276 | + monitor = MemoryMonitor(check_interval=1) |
| 277 | + with ( |
| 278 | + patch.object(Path, "exists", _v2_exists), |
| 279 | + patch.object(Path, "read_text", _v2_mock_read(usage=_MOCK_USAGE_AT_90)), |
| 280 | + patch("airbyte_cdk.utils.memory_monitor.sentry_sdk") as mock_sentry, |
| 281 | + ): |
| 282 | + mock_sentry.capture_message = mock_capture |
| 283 | + monitor.check_memory_usage() |
| 284 | + monitor.check_memory_usage() |
| 285 | + monitor.check_memory_usage() |
| 286 | + |
| 287 | + mock_capture.assert_called_once() |
| 288 | + |
| 289 | + |
| 290 | +def test_sentry_not_called_below_threshold() -> None: |
| 291 | + """sentry_sdk.capture_message() should not be called when memory is below 90%.""" |
| 292 | + mock_capture = MagicMock() |
| 293 | + monitor = MemoryMonitor(check_interval=1) |
| 294 | + with ( |
| 295 | + patch.object(Path, "exists", _v2_exists), |
| 296 | + patch.object(Path, "read_text", _v2_mock_read(usage=_MOCK_USAGE_BELOW)), |
| 297 | + patch("airbyte_cdk.utils.memory_monitor.sentry_sdk") as mock_sentry, |
| 298 | + ): |
| 299 | + mock_sentry.capture_message = mock_capture |
| 300 | + monitor.check_memory_usage() |
| 301 | + |
| 302 | + mock_capture.assert_not_called() |
| 303 | + |
| 304 | + |
| 305 | +def test_sentry_unavailable_degrades_gracefully(caplog: pytest.LogCaptureFixture) -> None: |
| 306 | + """When sentry_sdk is None (not installed), warning log should still be emitted.""" |
| 307 | + monitor = MemoryMonitor(check_interval=1) |
| 308 | + with ( |
| 309 | + caplog.at_level(logging.WARNING, logger="airbyte"), |
| 310 | + patch.object(Path, "exists", _v2_exists), |
| 311 | + patch.object(Path, "read_text", _v2_mock_read(usage=_MOCK_USAGE_AT_90)), |
| 312 | + patch("airbyte_cdk.utils.memory_monitor.sentry_sdk", None), |
| 313 | + ): |
| 314 | + monitor.check_memory_usage() |
| 315 | + |
| 316 | + # Warning log should still be emitted even when sentry_sdk is unavailable |
| 317 | + assert len(caplog.records) == 1 |
| 318 | + assert "91%" in caplog.records[0].message |
0 commit comments