|
6 | 6 |
|
7 | 7 | import pyarrow as pa |
8 | 8 | import pytest |
| 9 | +from dbtsl.error import RetryTimeoutError |
9 | 10 |
|
| 11 | +from dbt_mcp.errors.semantic_layer import SemanticLayerQueryTimeoutError |
10 | 12 | from dbt_mcp.semantic_layer.client import DEFAULT_RESULT_FORMATTER, SemanticLayerFetcher |
| 13 | +from dbt_mcp.semantic_layer.types import QueryMetricsError |
11 | 14 |
|
12 | 15 |
|
13 | 16 | def test_default_result_formatter_outputs_iso_dates() -> None: |
@@ -376,3 +379,62 @@ async def test_get_dimensions_includes_metadata( |
376 | 379 | assert result[0].metadata == {"display_name": "Order Date"} |
377 | 380 | assert result[1].metadata is None |
378 | 381 | assert result[2].metadata is None |
| 382 | + |
| 383 | + |
| 384 | +class TestQueryMetricsCompiledTimeout: |
| 385 | + """Tests for COMPILED timeout handling in query_metrics.""" |
| 386 | + |
| 387 | + @pytest.fixture |
| 388 | + def mock_sl_client(self): |
| 389 | + client = MagicMock() |
| 390 | + session_ctx = MagicMock() |
| 391 | + client.session.return_value = session_ctx |
| 392 | + session_ctx.__enter__ = MagicMock(return_value=client) |
| 393 | + session_ctx.__exit__ = MagicMock(return_value=False) |
| 394 | + return client |
| 395 | + |
| 396 | + @pytest.fixture |
| 397 | + def compiled_fetcher(self, mock_config_provider, mock_sl_client): |
| 398 | + client_provider = AsyncMock() |
| 399 | + client_provider.get_client.return_value = mock_sl_client |
| 400 | + return SemanticLayerFetcher( |
| 401 | + config_provider=mock_config_provider, |
| 402 | + client_provider=client_provider, |
| 403 | + ) |
| 404 | + |
| 405 | + async def test_compiled_timeout_raises_client_error( |
| 406 | + self, compiled_fetcher, mock_sl_client |
| 407 | + ): |
| 408 | + """COMPILED status timeout should raise SemanticLayerQueryTimeoutError.""" |
| 409 | + mock_sl_client.query.side_effect = RetryTimeoutError( |
| 410 | + timeout_s=60, status="COMPILED" |
| 411 | + ) |
| 412 | + |
| 413 | + with pytest.raises(SemanticLayerQueryTimeoutError) as exc_info: |
| 414 | + await compiled_fetcher.query_metrics(metrics=["revenue"]) |
| 415 | + |
| 416 | + assert "COMPILED" in str(exc_info.value) |
| 417 | + |
| 418 | + async def test_running_timeout_returns_error_result( |
| 419 | + self, compiled_fetcher, mock_sl_client |
| 420 | + ): |
| 421 | + """RUNNING status timeout should return QueryMetricsError, not raise.""" |
| 422 | + mock_sl_client.query.side_effect = RetryTimeoutError( |
| 423 | + timeout_s=60, status="RUNNING" |
| 424 | + ) |
| 425 | + |
| 426 | + result = await compiled_fetcher.query_metrics(metrics=["revenue"]) |
| 427 | + |
| 428 | + assert isinstance(result, QueryMetricsError) |
| 429 | + assert result.error is not None |
| 430 | + |
| 431 | + async def test_none_status_timeout_returns_error_result( |
| 432 | + self, compiled_fetcher, mock_sl_client |
| 433 | + ): |
| 434 | + """None status timeout should return QueryMetricsError, not raise.""" |
| 435 | + mock_sl_client.query.side_effect = RetryTimeoutError(timeout_s=60) |
| 436 | + |
| 437 | + result = await compiled_fetcher.query_metrics(metrics=["revenue"]) |
| 438 | + |
| 439 | + assert isinstance(result, QueryMetricsError) |
| 440 | + assert result.error is not None |
0 commit comments