| 1 | #!/usr/bin/env python3
|
| 2 | """Fire tests: Real API integration tests that cost money.
|
| 3 |
|
| 4 | ################################################################################
|
| 5 | # #
|
| 6 | # ⚠️ CRITICAL WARNING ⚠️ #
|
| 7 | # #
|
| 8 | # THIS TEST FILE SHOULD NEVER BE RUN BY AN AI AGENT. #
|
| 9 | # IT REQUIRES EXPLICIT HUMAN REQUEST AND SUPERVISION. #
|
| 10 | # #
|
| 11 | # These tests make REAL API calls that: #
|
| 12 | # - Cost real money (API usage fees) #
|
| 13 | # - Require valid API keys for multiple providers #
|
| 14 | # - May have rate limits and quotas #
|
| 15 | # #
|
| 16 | # To run: pytest tests/test_fire.py -v --run-fire #
|
| 17 | # Only run when explicitly requested by a human operator. #
|
| 18 | # #
|
| 19 | ################################################################################
|
| 20 | """
|
| 21 |
|
| 22 | import os
|
| 23 | import subprocess
|
| 24 | import sys
|
| 25 |
|
| 26 | import pytest
|
| 27 |
|
| 28 |
|
| 29 | def pytest_configure(config):
|
| 30 | """Register custom markers."""
|
| 31 | config.addinivalue_line("markers", "fire: mark test as a fire test (real API calls)")
|
| 32 |
|
| 33 |
|
| 34 | @pytest.fixture(autouse=True)
|
| 35 | def skip_without_fire_flag(request):
|
| 36 | """Skip fire tests unless --run-fire is provided."""
|
| 37 | if not request.config.getoption("--run-fire", default=False):
|
| 38 | pytest.skip("Fire tests require --run-fire flag and cost real money")
|
| 39 |
|
| 40 |
|
| 41 | SIMPLE_TASK = "Your job is to run `ls`, verify that you see files, then quit."
|
| 42 |
|
| 43 | requires_openai = pytest.mark.skipif(not os.environ.get("OPENAI_API_KEY"), reason="OPENAI_API_KEY not set")
|
| 44 | requires_openrouter = pytest.mark.skipif(not os.environ.get("OPENROUTER_API_KEY"), reason="OPENROUTER_API_KEY not set")
|
| 45 | requires_portkey = pytest.mark.skipif(not os.environ.get("PORTKEY_API_KEY"), reason="PORTKEY_API_KEY not set")
|
| 46 | requires_requesty = pytest.mark.skipif(not os.environ.get("REQUESTY_API_KEY"), reason="REQUESTY_API_KEY not set")
|
| 47 |
|
| 48 |
|
| 49 | def run_mini_command(extra_options: list[str]) -> subprocess.CompletedProcess:
|
| 50 | """Run the mini command with the given extra options."""
|
| 51 | cmd = [
|
| 52 | sys.executable,
|
| 53 | "-m",
|
| 54 | "minisweagent",
|
| 55 | "--exit-immediately",
|
| 56 | "-y",
|
| 57 | "--cost-limit",
|
| 58 | "0.03",
|
| 59 | "-t",
|
| 60 | SIMPLE_TASK,
|
| 61 | *extra_options,
|
| 62 | ]
|
| 63 | env = os.environ.copy()
|
| 64 | env["MSWEA_MODEL_RETRY_STOP_AFTER_ATTEMPT"] = "8"
|
| 65 | return subprocess.run(cmd, timeout=120, env=env)
|
| 66 |
|
| 67 |
|
| 68 | # =============================================================================
|
| 69 | # LiteLLM Models (default, toolcall, response_toolcall)
|
| 70 | # =============================================================================
|
| 71 |
|
| 72 |
|
| 73 | @requires_openai
|
| 74 | def test_litellm_textbased():
|
| 75 | """Test with litellm_textbased model class."""
|
| 76 | result = run_mini_command(
|
| 77 | ["--model", "openai/gpt-5-mini", "--model-class", "litellm_textbased", "-c", "mini_textbased"]
|
| 78 | )
|
| 79 | assert result.returncode == 0
|
| 80 |
|
| 81 |
|
| 82 | @requires_openai
|
| 83 | def test_litellm_toolcall():
|
| 84 | """Test with litellm_toolcall model class."""
|
| 85 | result = run_mini_command(["--model", "openai/gpt-5.2"])
|
| 86 | assert result.returncode == 0
|
| 87 |
|
| 88 |
|
| 89 | @requires_openai
|
| 90 | def test_litellm_toolcall_explicit():
|
| 91 | """Test with litellm_toolcall model class."""
|
| 92 | result = run_mini_command(["--model", "openai/gpt-5.2", "--model-class", "litellm", "-c", "mini"])
|
| 93 | assert result.returncode == 0
|
| 94 |
|
| 95 |
|
| 96 | @requires_openai
|
| 97 | def test_litellm_response_toolcall():
|
| 98 | """Test with litellm_response_toolcall model class (OpenAI Responses API)."""
|
| 99 | result = run_mini_command(["--model", "openai/gpt-5.2", "--model-class", "litellm_response"])
|
| 100 | assert result.returncode == 0
|
| 101 |
|
| 102 |
|
| 103 | # =============================================================================
|
| 104 | # OpenRouter Models
|
| 105 | # =============================================================================
|
| 106 |
|
| 107 |
|
| 108 | @requires_openrouter
|
| 109 | def test_openrouter_textbased():
|
| 110 | """Test with openrouter_textbased model class."""
|
| 111 | result = run_mini_command(
|
| 112 | ["--model", "anthropic/claude-sonnet-4", "--model-class", "openrouter_textbased", "-c", "mini_textbased"]
|
| 113 | )
|
| 114 | assert result.returncode == 0
|
| 115 |
|
| 116 |
|
| 117 | @requires_openrouter
|
| 118 | def test_openrouter_toolcall():
|
| 119 | """Test with openrouter_toolcall model class."""
|
| 120 | result = run_mini_command(["--model", "anthropic/claude-sonnet-4", "--model-class", "openrouter"])
|
| 121 | assert result.returncode == 0
|
| 122 |
|
| 123 |
|
| 124 | @requires_openrouter
|
| 125 | def test_openrouter_response_toolcall():
|
| 126 | """Test with openrouter_response_toolcall model class (OpenAI Responses API via OpenRouter)."""
|
| 127 | result = run_mini_command(["--model", "openai/gpt-5.2", "--model-class", "openrouter_response"])
|
| 128 | assert result.returncode == 0
|
| 129 |
|
| 130 |
|
| 131 | # =============================================================================
|
| 132 | # Portkey Models
|
| 133 | # =============================================================================
|
| 134 |
|
| 135 |
|
| 136 | @requires_portkey
|
| 137 | def test_portkey_default():
|
| 138 | """Test with default portkey model class."""
|
| 139 | result = run_mini_command(["--model", "@openai/gpt-5-mini", "--model-class", "portkey"])
|
| 140 | assert result.returncode == 0
|
| 141 |
|
| 142 |
|
| 143 | @requires_portkey
|
| 144 | def test_portkey_response():
|
| 145 | """Test with portkey_response model class (OpenAI Responses API via Portkey)."""
|
| 146 | result = run_mini_command(["--model", "@openai/gpt-5.2", "--model-class", "portkey_response"])
|
| 147 | assert result.returncode == 0
|
| 148 |
|
| 149 |
|
| 150 | # =============================================================================
|
| 151 | # Requesty Models
|
| 152 | # =============================================================================
|
| 153 |
|
| 154 |
|
| 155 | @requires_requesty
|
| 156 | def test_requesty():
|
| 157 | """Test with requesty model class."""
|
| 158 | result = run_mini_command(["--model", "openai/gpt-5-mini", "--model-class", "requesty"])
|
| 159 | assert result.returncode == 0
|
| 160 |
|