/ tests / hermes_cli / test_copilot_context.py
test_copilot_context.py
  1  """Tests for Copilot live /models context-window resolution."""
  2  
  3  from __future__ import annotations
  4  
  5  import time
  6  from unittest.mock import patch
  7  
  8  import pytest
  9  
 10  from hermes_cli.models import get_copilot_model_context
 11  
 12  
 13  # Sample catalog items mimicking the Copilot /models API response
 14  _SAMPLE_CATALOG = [
 15      {
 16          "id": "claude-opus-4.6-1m",
 17          "capabilities": {
 18              "type": "chat",
 19              "limits": {"max_prompt_tokens": 1000000, "max_output_tokens": 64000},
 20          },
 21      },
 22      {
 23          "id": "gpt-4.1",
 24          "capabilities": {
 25              "type": "chat",
 26              "limits": {"max_prompt_tokens": 128000, "max_output_tokens": 32768},
 27          },
 28      },
 29      {
 30          "id": "claude-sonnet-4",
 31          "capabilities": {
 32              "type": "chat",
 33              "limits": {"max_prompt_tokens": 200000, "max_output_tokens": 64000},
 34          },
 35      },
 36      {
 37          "id": "model-without-limits",
 38          "capabilities": {"type": "chat"},
 39      },
 40      {
 41          "id": "model-zero-limit",
 42          "capabilities": {
 43              "type": "chat",
 44              "limits": {"max_prompt_tokens": 0},
 45          },
 46      },
 47  ]
 48  
 49  
 50  @pytest.fixture(autouse=True)
 51  def _clear_cache():
 52      """Reset module-level cache before each test."""
 53      import hermes_cli.models as mod
 54  
 55      mod._copilot_context_cache = {}
 56      mod._copilot_context_cache_time = 0.0
 57      yield
 58      mod._copilot_context_cache = {}
 59      mod._copilot_context_cache_time = 0.0
 60  
 61  
 62  class TestGetCopilotModelContext:
 63      """Tests for get_copilot_model_context()."""
 64  
 65      @patch("hermes_cli.models.fetch_github_model_catalog", return_value=_SAMPLE_CATALOG)
 66      def test_returns_max_prompt_tokens(self, mock_fetch):
 67          assert get_copilot_model_context("claude-opus-4.6-1m") == 1_000_000
 68          assert get_copilot_model_context("gpt-4.1") == 128_000
 69  
 70      @patch("hermes_cli.models.fetch_github_model_catalog", return_value=_SAMPLE_CATALOG)
 71      def test_returns_none_for_unknown_model(self, mock_fetch):
 72          assert get_copilot_model_context("nonexistent-model") is None
 73  
 74      @patch("hermes_cli.models.fetch_github_model_catalog", return_value=_SAMPLE_CATALOG)
 75      def test_skips_models_without_limits(self, mock_fetch):
 76          assert get_copilot_model_context("model-without-limits") is None
 77  
 78      @patch("hermes_cli.models.fetch_github_model_catalog", return_value=_SAMPLE_CATALOG)
 79      def test_skips_zero_limit(self, mock_fetch):
 80          assert get_copilot_model_context("model-zero-limit") is None
 81  
 82      @patch("hermes_cli.models.fetch_github_model_catalog", return_value=_SAMPLE_CATALOG)
 83      def test_caches_results(self, mock_fetch):
 84          get_copilot_model_context("gpt-4.1")
 85          get_copilot_model_context("claude-sonnet-4")
 86          # Only one API call despite two lookups
 87          assert mock_fetch.call_count == 1
 88  
 89      @patch("hermes_cli.models.fetch_github_model_catalog", return_value=_SAMPLE_CATALOG)
 90      def test_cache_expires(self, mock_fetch):
 91          import hermes_cli.models as mod
 92  
 93          get_copilot_model_context("gpt-4.1")
 94          assert mock_fetch.call_count == 1
 95  
 96          # Expire the cache
 97          mod._copilot_context_cache_time = time.time() - 7200
 98          get_copilot_model_context("gpt-4.1")
 99          assert mock_fetch.call_count == 2
100  
101      @patch("hermes_cli.models.fetch_github_model_catalog", return_value=None)
102      def test_returns_none_when_catalog_unavailable(self, mock_fetch):
103          assert get_copilot_model_context("gpt-4.1") is None
104  
105      @patch("hermes_cli.models.fetch_github_model_catalog", return_value=[])
106      def test_returns_none_for_empty_catalog(self, mock_fetch):
107          assert get_copilot_model_context("gpt-4.1") is None
108  
109  
110  class TestModelMetadataCopilotIntegration:
111      """Test that get_model_context_length() uses Copilot live API for copilot provider."""
112  
113      @patch("hermes_cli.models.fetch_github_model_catalog", return_value=_SAMPLE_CATALOG)
114      def test_copilot_provider_uses_live_api(self, mock_fetch):
115          from agent.model_metadata import get_model_context_length
116  
117          ctx = get_model_context_length("claude-opus-4.6-1m", provider="copilot")
118          assert ctx == 1_000_000
119  
120      @patch("hermes_cli.models.fetch_github_model_catalog", return_value=_SAMPLE_CATALOG)
121      def test_copilot_acp_provider_uses_live_api(self, mock_fetch):
122          from agent.model_metadata import get_model_context_length
123  
124          ctx = get_model_context_length("claude-sonnet-4", provider="copilot-acp")
125          assert ctx == 200_000
126  
127      @patch("hermes_cli.models.fetch_github_model_catalog", return_value=None)
128      def test_falls_through_when_catalog_unavailable(self, mock_fetch):
129          from agent.model_metadata import get_model_context_length
130  
131          # Should not raise, should fall through to models.dev or defaults
132          ctx = get_model_context_length("gpt-4.1", provider="copilot")
133          assert isinstance(ctx, int)
134          assert ctx > 0