diff --git a/airbyte-ci/connectors/connectors_qa/src/connectors_qa/checks/documentation/documentation.py b/airbyte-ci/connectors/connectors_qa/src/connectors_qa/checks/documentation/documentation.py index 510d3550f8c5..c77890fbe1d6 100644 --- a/airbyte-ci/connectors/connectors_qa/src/connectors_qa/checks/documentation/documentation.py +++ b/airbyte-ci/connectors/connectors_qa/src/connectors_qa/checks/documentation/documentation.py @@ -52,15 +52,23 @@ def _run(self, connector: Connector) -> CheckResult: expected_title = f"# {connector.name_from_metadata} Migration Guide" expected_version_header_start = "## Upgrading to " migration_guide_content = migration_guide_file_path.read_text() - try: - first_line = migration_guide_content.splitlines()[0] - except IndexError: - first_line = migration_guide_content - if not first_line == expected_title: + + from .helpers import get_first_heading + + first_heading = get_first_heading(migration_guide_content) + + if not first_heading: + return self.create_check_result( + connector=connector, + passed=False, + message=f"Migration guide file for {connector.technical_name} does not contain a markdown heading.", + ) + + if first_heading != expected_title: return self.create_check_result( connector=connector, passed=False, - message=f"Migration guide file for {connector.technical_name} does not start with the correct header. Expected '{expected_title}', got '{first_line}'", + message=f"Migration guide file for {connector.technical_name} does not start with the correct header. Expected '{expected_title}', got '{first_heading}'", ) # Check that the migration guide contains a section for each breaking change key ## Upgrading to {version} diff --git a/airbyte-ci/connectors/connectors_qa/src/connectors_qa/checks/documentation/helpers.py b/airbyte-ci/connectors/connectors_qa/src/connectors_qa/checks/documentation/helpers.py index 1c947d031344..2f2db92b7c45 100644 --- a/airbyte-ci/connectors/connectors_qa/src/connectors_qa/checks/documentation/helpers.py +++ b/airbyte-ci/connectors/connectors_qa/src/connectors_qa/checks/documentation/helpers.py @@ -150,3 +150,42 @@ def generate_description(template_file: str, kwargs: dict[str, Any]) -> str: template = environment.get_template(template_file) template_content = template.render(**kwargs) return template_content + + +def get_first_heading(content: str) -> str: + """ + Extract the first markdown heading from content, skipping frontmatter, MDX imports, and empty lines. + + Args: + content: The markdown file content + + Returns: + The first line starting with '#', or empty string if none found + """ + lines = content.splitlines() + in_frontmatter = False + + for i, line in enumerate(lines): + stripped = line.strip() + + if stripped == "---": + if i == 0: + in_frontmatter = True + continue + elif in_frontmatter: + in_frontmatter = False + continue + + if in_frontmatter: + continue + + if stripped.startswith("import "): + continue + + if not stripped: + continue + + if stripped.startswith("#"): + return stripped + + return "" diff --git a/airbyte-ci/connectors/connectors_qa/tests/unit_tests/test_checks/test_documentation.py b/airbyte-ci/connectors/connectors_qa/tests/unit_tests/test_checks/test_documentation.py index 5b8d9060fbe8..adfe148cec37 100644 --- a/airbyte-ci/connectors/connectors_qa/tests/unit_tests/test_checks/test_documentation.py +++ b/airbyte-ci/connectors/connectors_qa/tests/unit_tests/test_checks/test_documentation.py @@ -50,7 +50,7 @@ def test_fail_when_migration_guide_file_path_is_none(self, mocker): assert connector.technical_name in result.message assert "Please create a migration guide in" in result.message - def test_fail_when_migration_guide_file_does_not_start_with_correct_header(self, mocker, tmp_path): + def test_fail_when_migration_guide_file_is_empty(self, mocker, tmp_path): # Arrange connector = mocker.Mock( name_from_metadata="Test Connector", @@ -63,10 +63,45 @@ def test_fail_when_migration_guide_file_does_not_start_with_correct_header(self, # Act result = documentation.CheckMigrationGuide()._run(connector) + # Assert + assert result.status == CheckStatus.FAILED + assert "Migration guide file for test-connector does not contain a markdown heading" in result.message + + def test_fail_when_migration_guide_file_does_not_start_with_correct_header(self, mocker, tmp_path): + # Arrange + connector = mocker.Mock( + name_from_metadata="Test Connector", + technical_name="test-connector", + metadata={"releases": {"breakingChanges": {"1.0.0": "Description"}}}, + migration_guide_file_path=tmp_path / "migration_guide.md", + ) + connector.migration_guide_file_path.write_text("# Wrong Header") + + # Act + result = documentation.CheckMigrationGuide()._run(connector) + # Assert assert result.status == CheckStatus.FAILED assert "Migration guide file for test-connector does not start with the correct header" in result.message - assert "Expected '# Test Connector Migration Guide', got ''" in result.message + assert "Expected '# Test Connector Migration Guide', got '# Wrong Header'" in result.message + + def test_pass_when_migration_guide_has_mdx_imports_before_title(self, mocker, tmp_path): + # Arrange + connector = mocker.Mock( + name_from_metadata="Test Connector", + technical_name="test-connector", + metadata={"releases": {"breakingChanges": {"1.0.0": "Description"}}}, + migration_guide_file_path=tmp_path / "migration_guide.md", + ) + connector.migration_guide_file_path.write_text( + "import SpecialDoc from '@site/src/components/SpecialDoc';\n\n" "# Test Connector Migration Guide\n" "## Upgrading to 1.0.0\n" + ) + + # Act + result = documentation.CheckMigrationGuide()._run(connector) + + # Assert + assert result.status == CheckStatus.PASSED def test_fail_when_migration_guide_file_has_missing_version_headings(self, mocker, tmp_path): # Arrange