"""Integration tests for BitSight API functionality."""
import pytest
import os
from unittest.mock import Mock
from fastmcp import Context
from birre.bscmcp import create_server
from src.birre.config import BirreConfig
# Skip integration tests if API key is not available
pytestmark = pytest.mark.skipif(
not os.getenv("BST_API_KEY"),
reason="BST_API_KEY environment variable not set - skipping integration tests",
)
class TestBitSightAPIIntegration:
"""Integration tests using real BitSight API calls."""
@pytest.fixture
def server(self):
"""Create a real server instance for integration testing."""
return create_server()
@pytest.fixture
def mock_context(self):
"""Create a mock context for integration testing."""
context = Mock(spec=Context)
context.info = Mock()
context.error = Mock()
context.debug = Mock()
context.warning = Mock()
return context
@pytest.mark.integration
@pytest.mark.asyncio
async def test_company_search_real_api(self, server, mock_context):
"""Test company search with real API call using known domain."""
tools = await server.get_tools()
company_search_tool = tools["company_search"]
# Use a well-known domain that should exist in BitSight
result = await company_search_tool.run(
{"domain": "microsoft.com"},
)
# Verify we got results
assert "error" not in result.data or not result.data.get("error")
assert isinstance(result.data["count"], int)
assert isinstance(result.data["companies"], list)
assert result.data["search_term"] == "microsoft.com"
# If results were found, verify structure
if result.data["count"] > 0:
first_company = result.data["companies"][0]
assert "guid" in first_company
assert "name" in first_company
assert first_company["guid"] # Should have a valid GUID
assert first_company["name"] # Should have a company name
@pytest.mark.integration
@pytest.mark.asyncio
async def test_company_search_nonexistent_domain(self, server, mock_context):
"""Test company search with domain that likely doesn't exist."""
tools = await server.get_tools()
company_search_tool = tools["company_search"]
# Use a domain that is unlikely to exist in BitSight
result = await company_search_tool.run(
{"domain": "this-domain-definitely-does-not-exist-12345.com"},
)
# Should succeed but return no results
assert "error" not in result.data or not result.data.get("error")
assert result.data["count"] == 0
assert result.data["companies"] == []
assert (
result.data["search_term"]
== "this-domain-definitely-does-not-exist-12345.com"
)
@pytest.mark.integration
@pytest.mark.asyncio
async def test_company_rating_workflow(self, server, mock_context):
"""Test complete workflow: search company, then get rating."""
tools = await server.get_tools()
company_search_tool = tools["company_search"]
rating_tool = tools["get_company_rating"]
# First, search for a company
search_result = await company_search_tool.run(
{"domain": "microsoft.com"},
)
# Skip if no companies found
if search_result.data["count"] == 0:
pytest.skip("No companies found for test domain")
# Get the first company's GUID
first_company_guid = search_result.data["companies"][0]["guid"]
# Now try to get the rating
rating_result = await rating_tool.run(
{"guid": first_company_guid},
)
# Verify the rating call structure (may or may not have actual rating)
assert isinstance(rating_result.data, dict)
assert "auto_subscribed" in rating_result.data
assert "auto_unsubscribed" in rating_result.data
# If there's an error, it should be a structured error
if "error" in rating_result.data:
assert isinstance(rating_result.data["error"], str)
assert rating_result.data["rating"] is None
else:
# If successful, should have rating data
if rating_result.data["rating"] is not None:
assert isinstance(rating_result.data["rating"], int)
assert 0 <= rating_result.data["rating"] <= 900
assert rating_result.data["grade"] in ["A", "B", "C", "D", "F"]
@pytest.mark.integration
@pytest.mark.asyncio
async def test_company_rating_invalid_guid(self, server, mock_context):
"""Test company rating with invalid GUID."""
tools = await server.get_tools()
rating_tool = tools["get_company_rating"]
# Use an obviously invalid GUID
result = await rating_tool.run(
{"guid": "invalid-guid-12345"},
)
# Should handle gracefully with error
# Either structured error response or API error
assert isinstance(result.data, dict)
if "error" in result.data:
assert isinstance(result.data["error"], str)
assert result.data["rating"] is None
class TestConfigurationIntegration:
"""Integration tests for configuration with real environment."""
@pytest.mark.integration
def test_config_with_real_environment(self):
"""Test configuration loads correctly from real environment."""
# This test runs only if BST_API_KEY is set
config = BirreConfig()
assert config.api_key
assert len(config.api_key) > 0
assert isinstance(config.debug, bool)
@pytest.mark.integration
@pytest.mark.asyncio
async def test_server_creation_with_real_config(self):
"""Test server can be created with real configuration."""
server = create_server()
assert server.name == "BitSight MCP Server"
tools = await server.get_tools()
assert len(tools) == 2
assert "company_search" in tools
assert "get_company_rating" in tools