/ tests / test_empty_model_fallback.py
test_empty_model_fallback.py
  1  """Tests for empty model fallback — when provider is configured but model is missing."""
  2  
  3  from unittest.mock import MagicMock, patch
  4  import pytest
  5  
  6  
  7  class TestGetDefaultModelForProvider:
  8      """Unit tests for hermes_cli.models.get_default_model_for_provider."""
  9  
 10      def test_known_provider_returns_first_model(self):
 11          from hermes_cli.models import get_default_model_for_provider
 12          result = get_default_model_for_provider("openai-codex")
 13          # Should return first model from _PROVIDER_MODELS["openai-codex"]
 14          assert result
 15          assert isinstance(result, str)
 16  
 17      def test_openrouter_returns_empty(self):
 18          """OpenRouter uses dynamic model fetch, no static catalog entry."""
 19          from hermes_cli.models import get_default_model_for_provider
 20          # OpenRouter is not in _PROVIDER_MODELS — it uses live fetching
 21          result = get_default_model_for_provider("openrouter")
 22          assert result == ""
 23  
 24      def test_unknown_provider_returns_empty(self):
 25          from hermes_cli.models import get_default_model_for_provider
 26          assert get_default_model_for_provider("nonexistent-provider") == ""
 27  
 28      def test_custom_provider_returns_empty(self):
 29          """Custom provider has no model catalog — should return empty."""
 30          from hermes_cli.models import get_default_model_for_provider
 31          # Custom providers don't have entries in _PROVIDER_MODELS
 32          assert get_default_model_for_provider("some-random-custom") == ""
 33  
 34  
 35  class TestGatewayEmptyModelFallback:
 36      """Test that _resolve_session_agent_runtime fills in empty model from provider catalog."""
 37  
 38      def test_empty_model_filled_from_provider(self):
 39          """When config has no model but provider is openai-codex, use first codex model."""
 40          from gateway.run import GatewayRunner
 41  
 42          runner = object.__new__(GatewayRunner)
 43          runner._session_model_overrides = {}
 44  
 45          # Mock _resolve_gateway_model to return empty string
 46          # Mock _resolve_runtime_agent_kwargs to return openai-codex provider
 47          with patch("gateway.run._resolve_gateway_model", return_value=""), \
 48               patch("gateway.run._resolve_runtime_agent_kwargs", return_value={
 49                   "provider": "openai-codex",
 50                   "api_key": "test-key",
 51                   "base_url": "https://chatgpt.com/backend-api/codex",
 52                   "api_mode": "codex_responses",
 53               }):
 54              model, kwargs = runner._resolve_session_agent_runtime()
 55  
 56          # Model should have been filled in from provider catalog
 57          assert model, "Model should not be empty when provider is known"
 58          assert isinstance(model, str)
 59          assert kwargs["provider"] == "openai-codex"
 60  
 61      def test_nonempty_model_not_overridden(self):
 62          """When config has a model set, don't override it."""
 63          from gateway.run import GatewayRunner
 64  
 65          runner = object.__new__(GatewayRunner)
 66          runner._session_model_overrides = {}
 67  
 68          with patch("gateway.run._resolve_gateway_model", return_value="gpt-5.4"), \
 69               patch("gateway.run._resolve_runtime_agent_kwargs", return_value={
 70                   "provider": "openai-codex",
 71                   "api_key": "test-key",
 72                   "base_url": "https://chatgpt.com/backend-api/codex",
 73                   "api_mode": "codex_responses",
 74               }):
 75              model, kwargs = runner._resolve_session_agent_runtime()
 76  
 77          assert model == "gpt-5.4", "Explicit model should not be overridden"
 78  
 79      def test_empty_model_no_provider_stays_empty(self):
 80          """When both model and provider are empty, model stays empty."""
 81          from gateway.run import GatewayRunner
 82  
 83          runner = object.__new__(GatewayRunner)
 84          runner._session_model_overrides = {}
 85  
 86          with patch("gateway.run._resolve_gateway_model", return_value=""), \
 87               patch("gateway.run._resolve_runtime_agent_kwargs", return_value={
 88                   "provider": "",
 89                   "api_key": "test-key",
 90                   "base_url": "https://example.com",
 91                   "api_mode": "chat_completions",
 92               }):
 93              model, kwargs = runner._resolve_session_agent_runtime()
 94  
 95          # Can't fill in a default without knowing the provider
 96          assert model == ""
 97  
 98  
 99  class TestResolveGatewayModel:
100      """Test _resolve_gateway_model reads model from config correctly."""
101  
102      def test_returns_default_key(self):
103          from gateway.run import _resolve_gateway_model
104          assert _resolve_gateway_model({"model": {"default": "gpt-5.4"}}) == "gpt-5.4"
105  
106      def test_returns_model_key_fallback(self):
107          from gateway.run import _resolve_gateway_model
108          assert _resolve_gateway_model({"model": {"model": "gpt-5.4"}}) == "gpt-5.4"
109  
110      def test_returns_empty_when_missing(self):
111          from gateway.run import _resolve_gateway_model
112          assert _resolve_gateway_model({"model": {}}) == ""
113  
114      def test_returns_empty_when_no_model_section(self):
115          from gateway.run import _resolve_gateway_model
116          assert _resolve_gateway_model({}) == ""
117  
118      def test_string_model_config(self):
119          from gateway.run import _resolve_gateway_model
120          assert _resolve_gateway_model({"model": "my-model"}) == "my-model"