|
6 | 6 | import pytest |
7 | 7 |
|
8 | 8 | from dbt_mcp.config.config import load_config |
| 9 | +from dbt_mcp.config.dbt_project import parse_dbt_version_minor |
9 | 10 | from dbt_mcp.dbt_cli.binary_type import BinaryType |
10 | 11 | from dbt_mcp.mcp.server import create_dbt_mcp |
11 | 12 | from dbt_mcp.product_docs.client import ( |
| 13 | + detect_eol_page, |
12 | 14 | normalize_doc_url, |
13 | 15 | parse_llms_full_txt, |
14 | 16 | parse_llms_txt, |
@@ -93,6 +95,16 @@ def context(mock_client): |
93 | 95 | """Create ProductDocsToolContext with a mocked client.""" |
94 | 96 | ctx = ProductDocsToolContext.__new__(ProductDocsToolContext) |
95 | 97 | ctx.client = mock_client |
| 98 | + ctx.dbt_version = None |
| 99 | + return ctx |
| 100 | + |
| 101 | + |
| 102 | +@pytest.fixture |
| 103 | +def versioned_context(mock_client): |
| 104 | + """Create ProductDocsToolContext with a mocked client and dbt version set.""" |
| 105 | + ctx = ProductDocsToolContext.__new__(ProductDocsToolContext) |
| 106 | + ctx.client = mock_client |
| 107 | + ctx.dbt_version = "1.8" |
96 | 108 | return ctx |
97 | 109 |
|
98 | 110 |
|
@@ -500,6 +512,115 @@ async def test_ranks_by_keyword_frequency(self): |
500 | 512 | assert results[0]["url"].endswith("incremental-models-overview") |
501 | 513 |
|
502 | 514 |
|
| 515 | +class TestParseDbtVersionMinor: |
| 516 | + def test_string_constraint(self): |
| 517 | + assert parse_dbt_version_minor(">=1.8.0") == "1.8" |
| 518 | + |
| 519 | + def test_list_constraint(self): |
| 520 | + assert parse_dbt_version_minor([">=1.8.0", "<2.0"]) == "1.8" |
| 521 | + |
| 522 | + def test_bare_version(self): |
| 523 | + assert parse_dbt_version_minor("1.10") == "1.10" |
| 524 | + |
| 525 | + def test_none(self): |
| 526 | + assert parse_dbt_version_minor(None) is None |
| 527 | + |
| 528 | + def test_unparseable(self): |
| 529 | + assert parse_dbt_version_minor("latest") is None |
| 530 | + |
| 531 | + def test_patch_version(self): |
| 532 | + assert parse_dbt_version_minor(">=1.9.3") == "1.9" |
| 533 | + |
| 534 | + def test_exact_version_string(self): |
| 535 | + assert parse_dbt_version_minor("1.7.0") == "1.7" |
| 536 | + |
| 537 | + |
| 538 | +class TestDetectEolPage: |
| 539 | + def test_older_versions_url(self): |
| 540 | + url = "https://docs.getdbt.com/docs/dbt-versions/core-upgrade/Older versions/upgrading-to-v1.5.md" |
| 541 | + assert detect_eol_page(url) is True |
| 542 | + |
| 543 | + def test_url_encoded_older_versions(self): |
| 544 | + url = "https://docs.getdbt.com/docs/dbt-versions/core-upgrade/Older%20versions/upgrading-to-v1.3.md" |
| 545 | + assert detect_eol_page(url) is True |
| 546 | + |
| 547 | + def test_current_upgrade_url(self): |
| 548 | + url = "https://docs.getdbt.com/docs/dbt-versions/core-upgrade/upgrading-to-v1.9.md" |
| 549 | + assert detect_eol_page(url) is False |
| 550 | + |
| 551 | + def test_regular_docs_url(self): |
| 552 | + url = "https://docs.getdbt.com/docs/build/models.md" |
| 553 | + assert detect_eol_page(url) is False |
| 554 | + |
| 555 | + |
| 556 | +class TestVersionInSearchResponse: |
| 557 | + @pytest.mark.asyncio |
| 558 | + async def test_search_includes_dbt_project_version( |
| 559 | + self, versioned_context, mock_client |
| 560 | + ): |
| 561 | + mock_client.search_index.return_value = [ |
| 562 | + {"title": "Models", "url": "https://docs.getdbt.com/docs/build/models"}, |
| 563 | + ] |
| 564 | + result = await search_product_docs.fn(versioned_context, "models") |
| 565 | + assert result.dbt_project_version == "1.8" |
| 566 | + |
| 567 | + @pytest.mark.asyncio |
| 568 | + async def test_search_version_none_when_not_set(self, context, mock_client): |
| 569 | + mock_client.search_index.return_value = [ |
| 570 | + {"title": "Models", "url": "https://docs.getdbt.com/docs/build/models"}, |
| 571 | + ] |
| 572 | + result = await search_product_docs.fn(context, "models") |
| 573 | + assert result.dbt_project_version is None |
| 574 | + |
| 575 | + @pytest.mark.asyncio |
| 576 | + async def test_search_empty_query_still_has_version(self, versioned_context): |
| 577 | + result = await search_product_docs.fn(versioned_context, "") |
| 578 | + assert result.dbt_project_version == "1.8" |
| 579 | + assert result.error is not None |
| 580 | + |
| 581 | + |
| 582 | +class TestVersionInGetPagesResponse: |
| 583 | + @pytest.mark.asyncio |
| 584 | + async def test_get_pages_includes_dbt_project_version( |
| 585 | + self, versioned_context, mock_client |
| 586 | + ): |
| 587 | + mock_client.get_page.return_value = "# Page Content" |
| 588 | + result = await get_product_doc_pages.fn( |
| 589 | + versioned_context, ["/docs/build/models"] |
| 590 | + ) |
| 591 | + assert result.dbt_project_version == "1.8" |
| 592 | + |
| 593 | + @pytest.mark.asyncio |
| 594 | + async def test_get_pages_version_none_when_not_set(self, context, mock_client): |
| 595 | + mock_client.get_page.return_value = "# Page Content" |
| 596 | + result = await get_product_doc_pages.fn(context, ["/docs/build/models"]) |
| 597 | + assert result.dbt_project_version is None |
| 598 | + |
| 599 | + |
| 600 | +class TestEolPageAnnotation: |
| 601 | + @pytest.mark.asyncio |
| 602 | + async def test_fetched_eol_page_has_warning(self, context, mock_client): |
| 603 | + mock_client.get_page.return_value = "# Upgrading to v1.5\n\nOld content." |
| 604 | + result = await get_product_doc_pages.fn( |
| 605 | + context, |
| 606 | + [ |
| 607 | + "https://docs.getdbt.com/docs/dbt-versions/core-upgrade/Older versions/upgrading-to-v1.5" |
| 608 | + ], |
| 609 | + ) |
| 610 | + page = result.pages[0] |
| 611 | + assert page.version_note is not None |
| 612 | + assert "end-of-life" in page.version_note |
| 613 | + assert page.content.startswith(">>> VERSION NOTICE:") |
| 614 | + |
| 615 | + @pytest.mark.asyncio |
| 616 | + async def test_fetched_current_page_no_annotation(self, context, mock_client): |
| 617 | + mock_client.get_page.return_value = "# Models\n\nCurrent content." |
| 618 | + result = await get_product_doc_pages.fn(context, ["/docs/build/models"]) |
| 619 | + page = result.pages[0] |
| 620 | + assert page.version_note is None |
| 621 | + assert not page.content.startswith(">>>") |
| 622 | + |
| 623 | + |
503 | 624 | class TestProductDocsRegistration: |
504 | 625 | @pytest.mark.asyncio |
505 | 626 | async def test_tools_registered_by_default(self, env_setup): |
|
0 commit comments