"""
Provider smoke tests - basic connectivity and response validation.
These tests verify that each provider:
1. Is available (CLI installed and accessible)
2. Can accept a simple prompt
3. Returns a valid response
Run with: pytest tests/integration/providers/test_provider_smoke.py -m smoke
Enable live tests: FOUNDRY_ENABLE_LIVE_PROVIDER_TESTS=1
"""
import pytest
from foundry_mcp.core.providers import (
ProviderHooks,
ProviderRequest,
ProviderResult,
ProviderStatus,
detect_provider_availability,
resolve_provider,
)
# =============================================================================
# Provider Availability Tests (no API calls)
# =============================================================================
class TestProviderAvailability:
"""Test provider detection without making API calls."""
def test_gemini_detection(self):
"""Check if gemini CLI is detected."""
available = detect_provider_availability("gemini")
# Just report - don't fail if not available
print(f"gemini available: {available}")
def test_codex_detection(self):
"""Check if codex CLI is detected."""
available = detect_provider_availability("codex")
print(f"codex available: {available}")
def test_claude_detection(self):
"""Check if claude CLI is detected."""
available = detect_provider_availability("claude")
print(f"claude available: {available}")
def test_cursor_agent_detection(self):
"""Check if cursor-agent CLI is detected."""
available = detect_provider_availability("cursor-agent")
print(f"cursor-agent available: {available}")
def test_opencode_detection(self):
"""Check if opencode CLI is detected."""
available = detect_provider_availability("opencode")
print(f"opencode available: {available}")
def test_list_available_providers(self, available_providers_list):
"""List all currently available providers."""
print(f"Available providers: {available_providers_list}")
# At least one provider should be available for meaningful tests
# This is informational, not a hard requirement
if not available_providers_list:
pytest.skip("No providers available - informational only")
# =============================================================================
# Live Provider Smoke Tests
# =============================================================================
@pytest.mark.smoke
@pytest.mark.live_providers
@pytest.mark.gemini
class TestGeminiSmoke:
"""Smoke tests for Gemini provider."""
def test_gemini_simple_response(
self, simple_prompt, provider_request_factory, validate_provider_result
):
"""Test gemini responds to a simple prompt."""
provider = resolve_provider("gemini", hooks=ProviderHooks())
request = provider_request_factory(simple_prompt, timeout=30.0)
result = provider.generate(request)
validated = validate_provider_result(result)
assert "PONG" in validated.content.upper(), f"Expected PONG in response: {validated.content}"
def test_gemini_with_model_override(
self, simple_prompt, provider_request_factory, validate_provider_result
):
"""Test gemini with explicit model selection."""
provider = resolve_provider("gemini", hooks=ProviderHooks())
# Use gemini-2.5-flash model explicitly
request = provider_request_factory(simple_prompt, model="gemini-2.5-flash", timeout=30.0)
result = provider.generate(request)
validate_provider_result(result)
@pytest.mark.smoke
@pytest.mark.live_providers
@pytest.mark.codex
class TestCodexSmoke:
"""Smoke tests for Codex provider."""
def test_codex_simple_response(
self, simple_prompt, provider_request_factory, validate_provider_result
):
"""Test codex responds to a simple prompt."""
provider = resolve_provider("codex", hooks=ProviderHooks())
request = provider_request_factory(simple_prompt, timeout=30.0)
result = provider.generate(request)
validated = validate_provider_result(result)
assert "PONG" in validated.content.upper(), f"Expected PONG in response: {validated.content}"
@pytest.mark.smoke
@pytest.mark.live_providers
@pytest.mark.claude
class TestClaudeSmoke:
"""Smoke tests for Claude provider."""
def test_claude_simple_response(
self, simple_prompt, provider_request_factory, validate_provider_result
):
"""Test claude responds to a simple prompt."""
provider = resolve_provider("claude", hooks=ProviderHooks())
request = provider_request_factory(simple_prompt, model="haiku", timeout=30.0)
result = provider.generate(request)
validated = validate_provider_result(result)
assert "PONG" in validated.content.upper(), f"Expected PONG in response: {validated.content}"
def test_claude_with_haiku_model(
self, simple_prompt, provider_request_factory, validate_provider_result
):
"""Test claude with haiku model."""
provider = resolve_provider("claude", hooks=ProviderHooks())
request = provider_request_factory(simple_prompt, model="haiku", timeout=30.0)
result = provider.generate(request)
validate_provider_result(result)
@pytest.mark.smoke
@pytest.mark.live_providers
@pytest.mark.cursor_agent
class TestCursorAgentSmoke:
"""Smoke tests for Cursor Agent provider."""
def test_cursor_agent_simple_response(
self, simple_prompt, provider_request_factory, validate_provider_result
):
"""Test cursor-agent responds to a simple prompt."""
provider = resolve_provider("cursor-agent", hooks=ProviderHooks())
request = provider_request_factory(simple_prompt, timeout=30.0)
result = provider.generate(request)
validated = validate_provider_result(result)
assert "PONG" in validated.content.upper(), f"Expected PONG in response: {validated.content}"
@pytest.mark.smoke
@pytest.mark.live_providers
@pytest.mark.opencode
class TestOpenCodeSmoke:
"""Smoke tests for OpenCode provider."""
def test_opencode_simple_response(
self, simple_prompt, provider_request_factory, validate_provider_result
):
"""Test opencode responds to a simple prompt."""
provider = resolve_provider("opencode", hooks=ProviderHooks())
request = provider_request_factory(simple_prompt, timeout=30.0)
result = provider.generate(request)
validated = validate_provider_result(result)
assert "PONG" in validated.content.upper(), f"Expected PONG in response: {validated.content}"
def test_opencode_with_backend_routing(
self, simple_prompt, provider_request_factory, validate_provider_result
):
"""Test opencode with backend/model routing."""
provider = resolve_provider("opencode", hooks=ProviderHooks())
# Route through openai backend
request = provider_request_factory(simple_prompt, model="openai/gpt-5.1-mini", timeout=60.0)
result = provider.generate(request)
validate_provider_result(result)
# =============================================================================
# Cross-Provider Comparison Tests
# =============================================================================
@pytest.mark.smoke
@pytest.mark.live_providers
@pytest.mark.slow
class TestCrossProviderComparison:
"""Tests that run the same prompt across multiple providers."""
def test_all_available_providers_respond(
self,
simple_prompt,
available_providers_list,
provider_request_factory,
validate_provider_result,
):
"""Test that all available providers can respond to the same prompt."""
if not available_providers_list:
pytest.skip("No providers available")
results = {}
failures = {}
for provider_id in available_providers_list:
try:
provider = resolve_provider(provider_id, hooks=ProviderHooks())
request = provider_request_factory(simple_prompt, timeout=30.0)
result = provider.generate(request)
validated = validate_provider_result(result)
results[provider_id] = validated.content
except Exception as e:
failures[provider_id] = str(e)
# Report results
print(f"\nProvider Results:")
for provider_id, content in results.items():
status = "PASS" if "PONG" in content.upper() else "FAIL"
print(f" {provider_id}: {status} - {content[:50]}...")
if failures:
print(f"\nProvider Failures:")
for provider_id, error in failures.items():
print(f" {provider_id}: {error}")
# At least one provider should succeed
assert results, f"All providers failed: {failures}"