Skip to main content
Glama
test_api_completeness_warnings.py12.1 kB
"""Unit tests for API completeness: warnings infrastructure. Tests the warnings infrastructure across catalog tools (build_catalog, search_catalog). Verifies that warnings are always present, properly structured, and follow the expected format. """ from __future__ import annotations import tempfile from pathlib import Path from unittest.mock import Mock import pytest from igloo_mcp.catalog import CatalogService from igloo_mcp.config import Config from igloo_mcp.mcp.tools.build_catalog import BuildCatalogTool from igloo_mcp.mcp.tools.search_catalog import SearchCatalogTool @pytest.fixture def mock_config(): """Create mock config for testing.""" config = Mock(spec=Config) config.snowflake_profile = "test_profile" config.reports_dir = Path(tempfile.mkdtemp()) return config @pytest.fixture def mock_catalog_service(mock_config): """Create mock catalog service.""" service = Mock(spec=CatalogService) # Mock build result build_result = Mock() build_result.output_dir = "/tmp/test_catalog" build_result.totals = Mock( databases=5, schemas=10, tables=50, views=20, materialized_views=5, dynamic_tables=3, tasks=2, functions=15, procedures=8, columns=500, ) service.build = Mock(return_value=build_result) return service @pytest.fixture def temp_catalog_dir(): """Create temporary catalog directory with test data.""" import json with tempfile.TemporaryDirectory() as tmpdir: # Create catalog.json file with empty catalog structure catalog_data = { "databases": [], "schemas": [], "tables": [], "views": [], "materialized_views": [], "dynamic_tables": [], "tasks": [], "functions": [], "procedures": [], } catalog_path = Path(tmpdir) / "catalog.json" catalog_path.write_text(json.dumps(catalog_data)) yield tmpdir class TestBuildCatalogWarnings: """Test warnings infrastructure for build_catalog tool.""" @pytest.mark.asyncio async def test_warnings_field_always_present(self, mock_config, mock_catalog_service): """Test that warnings field is always present in response.""" tool = BuildCatalogTool(mock_config, mock_catalog_service) result = await tool.execute(output_dir="./test_catalog") assert "warnings" in result, "warnings field must always be present" assert isinstance(result["warnings"], list), "warnings must be a list" @pytest.mark.asyncio async def test_warnings_empty_by_default(self, mock_config, mock_catalog_service): """Test that warnings is empty array by default (no warnings).""" tool = BuildCatalogTool(mock_config, mock_catalog_service) result = await tool.execute(output_dir="./test_catalog") assert result["warnings"] == [], "warnings should be empty by default" @pytest.mark.asyncio async def test_warnings_never_null(self, mock_config, mock_catalog_service): """Test that warnings is never null (always empty array if no warnings).""" tool = BuildCatalogTool(mock_config, mock_catalog_service) result = await tool.execute(output_dir="./test_catalog") assert result["warnings"] is not None, "warnings must never be null" assert result["warnings"] == [], "warnings should be empty array, not null" class TestSearchCatalogWarnings: """Test warnings infrastructure for search_catalog tool.""" @pytest.mark.asyncio async def test_warnings_field_always_present(self, temp_catalog_dir): """Test that warnings field is always present in response.""" tool = SearchCatalogTool() result = await tool.execute(catalog_dir=temp_catalog_dir) assert "warnings" in result, "warnings field must always be present" assert isinstance(result["warnings"], list), "warnings must be a list" @pytest.mark.asyncio async def test_warnings_empty_by_default(self, temp_catalog_dir): """Test that warnings is empty array by default (no warnings).""" tool = SearchCatalogTool() result = await tool.execute(catalog_dir=temp_catalog_dir) assert result["warnings"] == [], "warnings should be empty by default" @pytest.mark.asyncio async def test_warnings_in_search_all_databases_mode(self, temp_catalog_dir): """Test that warnings field is present in search_all_databases mode.""" tool = SearchCatalogTool() # Note: search_all_databases requires default catalog_dir result = await tool.execute( catalog_dir="./data_catalogue", search_all_databases=True, ) assert "warnings" in result, "warnings must be present in all code paths" assert isinstance(result["warnings"], list) @pytest.mark.asyncio async def test_warnings_never_null(self, temp_catalog_dir): """Test that warnings is never null (always empty array if no warnings).""" tool = SearchCatalogTool() result = await tool.execute(catalog_dir=temp_catalog_dir) assert result["warnings"] is not None, "warnings must never be null" assert result["warnings"] == [], "warnings should be empty array, not null" class TestWarningStructureFormat: """Test structured warning format (for future use).""" def test_warning_schema_structure(self): """Test expected structure for warnings when they are added.""" # This test documents the expected warning structure for future use expected_warning = { "code": "EXAMPLE_WARNING", "message": "This is an example warning message", "severity": "warning", # or "info" "context": { "database": "EXAMPLE_DB", "operation": "build_catalog", }, } # Validate structure assert "code" in expected_warning assert "message" in expected_warning assert "severity" in expected_warning assert "context" in expected_warning assert isinstance(expected_warning["code"], str) assert isinstance(expected_warning["message"], str) assert expected_warning["severity"] in ["info", "warning"] assert isinstance(expected_warning["context"], dict) def test_warning_severity_levels(self): """Test that warning severity levels are well-defined.""" valid_severities = ["info", "warning"] # These are the only valid severity levels for non-error warnings # (errors should raise exceptions, not return warnings) for severity in valid_severities: assert severity in ["info", "warning"] # Verify we don't use "error" severity in warnings # (errors should be exceptions) assert "error" not in valid_severities def test_warning_code_format(self): """Test that warning codes follow expected format.""" # Warning codes should be UPPER_SNAKE_CASE example_codes = [ "DEPRECATED_PARAMETER", "PARTIAL_RESULTS", "FALLBACK_USED", "PERFORMANCE_HINT", ] for code in example_codes: # Should be uppercase assert code.isupper(), f"Code {code} should be uppercase" # Should use underscores assert "_" in code or len(code.split("_")) == 1 # Should not contain spaces assert " " not in code class TestWarningsConsistencyAcrossTools: """Test warnings consistency across different tools.""" @pytest.mark.asyncio async def test_all_tools_have_warnings_field(self, mock_config, mock_catalog_service, temp_catalog_dir): """Test that all modified tools have warnings field.""" tools_and_params = [ ( BuildCatalogTool(mock_config, mock_catalog_service), {"output_dir": "./test_catalog"}, ), ( SearchCatalogTool(), {"catalog_dir": temp_catalog_dir}, ), ] for tool, params in tools_and_params: result = await tool.execute(**params) assert "warnings" in result, f"{tool.name} must have warnings field" assert isinstance(result["warnings"], list), f"{tool.name} warnings must be a list" @pytest.mark.asyncio async def test_warnings_type_consistency(self, mock_config, mock_catalog_service, temp_catalog_dir): """Test that warnings field type is consistent across tools.""" tools_and_params = [ ( BuildCatalogTool(mock_config, mock_catalog_service), {"output_dir": "./test_catalog"}, ), ( SearchCatalogTool(), {"catalog_dir": temp_catalog_dir}, ), ] warning_types = [] for tool, params in tools_and_params: result = await tool.execute(**params) warning_types.append(type(result["warnings"])) # All should be list type assert all(wt is list for wt in warning_types), "All tools should return warnings as list type" class TestWarningsFutureExtensibility: """Test that warnings infrastructure is ready for future use.""" @pytest.mark.asyncio async def test_warnings_can_hold_structured_data(self, mock_config, mock_catalog_service): """Test that warnings array can hold structured warning objects.""" # This test verifies the infrastructure is ready for future warnings tool = BuildCatalogTool(mock_config, mock_catalog_service) result = await tool.execute(output_dir="./test_catalog") # Warnings is a list that can hold dict objects warnings = result["warnings"] assert isinstance(warnings, list) # Simulate adding a warning (for future implementation) example_warning = { "code": "EXAMPLE_CODE", "message": "Example message", "severity": "info", "context": {}, } # List can hold structured warnings warnings.append(example_warning) assert len(warnings) == 1 assert warnings[0]["code"] == "EXAMPLE_CODE" def test_warning_json_serializability(self): """Test that warning objects are JSON-serializable.""" import json warning = { "code": "TEST_WARNING", "message": "Test message", "severity": "warning", "context": { "database": "TEST_DB", "count": 42, "flag": True, }, } # Should serialize to JSON without errors json_str = json.dumps(warning) assert json_str # Should deserialize back to same structure deserialized = json.loads(json_str) assert deserialized == warning class TestWarningsDocumentation: """Test that warnings are properly documented in schemas.""" def test_build_catalog_schema_documents_warnings(self, mock_config, mock_catalog_service): """Test that build_catalog schema includes warnings documentation.""" tool = BuildCatalogTool(mock_config, mock_catalog_service) # Schema is checked implicitly through tool initialization # While warnings is in the response (not parameters), # the tool's response structure should be documented # This test verifies the tool is set up correctly assert tool.name == "build_catalog" assert tool.category == "metadata" def test_search_catalog_schema_documents_warnings(self): """Test that search_catalog schema includes warnings documentation.""" tool = SearchCatalogTool() # Schema is checked implicitly through tool initialization # Verify tool is set up correctly for warnings assert tool.name == "search_catalog" assert tool.category == "metadata"

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Evan-Kim2028/igloo-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server