Files

217 lines
6.6 KiB
Python

"""Tests for CLI functionality."""
import importlib.util
from pathlib import Path
from unittest.mock import Mock, patch
import pytest
from langchain_model_profiles.cli import refresh
@pytest.fixture
def mock_models_dev_response() -> dict:
"""Create a mock response from models.dev API."""
return {
"anthropic": {
"id": "anthropic",
"name": "Anthropic",
"models": {
"claude-3-opus": {
"id": "claude-3-opus",
"name": "Claude 3 Opus",
"tool_call": True,
"limit": {"context": 200000, "output": 4096},
"modalities": {"input": ["text", "image"], "output": ["text"]},
},
"claude-3-sonnet": {
"id": "claude-3-sonnet",
"name": "Claude 3 Sonnet",
"tool_call": True,
"limit": {"context": 200000, "output": 4096},
"modalities": {"input": ["text", "image"], "output": ["text"]},
},
},
},
"openai": {
"id": "openai",
"name": "OpenAI",
"models": {
"gpt-4": {
"id": "gpt-4",
"name": "GPT-4",
"tool_call": True,
"limit": {"context": 8192, "output": 4096},
"modalities": {"input": ["text"], "output": ["text"]},
}
},
},
}
def test_refresh_generates_profiles_file(
tmp_path: Path, mock_models_dev_response: dict
) -> None:
"""Test that refresh command generates _profiles.py with merged data."""
data_dir = tmp_path / "data"
data_dir.mkdir()
# Create augmentations file
aug_file = data_dir / "profile_augmentations.toml"
aug_file.write_text("""
provider = "anthropic"
[overrides]
image_url_inputs = true
pdf_inputs = true
""")
# Mock the httpx.get call
mock_response = Mock()
mock_response.json.return_value = mock_models_dev_response
mock_response.raise_for_status = Mock()
with (
patch("langchain_model_profiles.cli.httpx.get", return_value=mock_response),
patch("builtins.input", return_value="y"),
):
refresh("anthropic", data_dir)
# Verify _profiles.py was created
profiles_file = data_dir / "_profiles.py"
assert profiles_file.exists()
# Import and verify content
profiles_content = profiles_file.read_text()
assert "DO NOT EDIT THIS FILE MANUALLY" in profiles_content
assert "PROFILES:" in profiles_content
assert "claude-3-opus" in profiles_content
assert "claude-3-sonnet" in profiles_content
# Check that augmentations were applied
assert "image_url_inputs" in profiles_content
assert "pdf_inputs" in profiles_content
def test_refresh_raises_error_for_missing_provider(
tmp_path: Path, mock_models_dev_response: dict
) -> None:
"""Test that refresh exits with error for non-existent provider."""
data_dir = tmp_path / "data"
data_dir.mkdir()
# Mock the httpx.get call
mock_response = Mock()
mock_response.json.return_value = mock_models_dev_response
mock_response.raise_for_status = Mock()
with (
patch("langchain_model_profiles.cli.httpx.get", return_value=mock_response),
patch("builtins.input", return_value="y"),
):
with pytest.raises(SystemExit) as exc_info:
refresh("nonexistent-provider", data_dir)
assert exc_info.value.code == 1
# Output file should not be created
profiles_file = data_dir / "_profiles.py"
assert not profiles_file.exists()
def test_refresh_works_without_augmentations(
tmp_path: Path, mock_models_dev_response: dict
) -> None:
"""Test that refresh works even without augmentations file."""
data_dir = tmp_path / "data"
data_dir.mkdir()
# Mock the httpx.get call
mock_response = Mock()
mock_response.json.return_value = mock_models_dev_response
mock_response.raise_for_status = Mock()
with (
patch("langchain_model_profiles.cli.httpx.get", return_value=mock_response),
patch("builtins.input", return_value="y"),
):
refresh("anthropic", data_dir)
# Verify _profiles.py was created
profiles_file = data_dir / "_profiles.py"
assert profiles_file.exists()
assert profiles_file.stat().st_size > 0
def test_refresh_aborts_when_user_declines_external_directory(
tmp_path: Path, mock_models_dev_response: dict
) -> None:
"""Test that refresh aborts when user declines writing to external directory."""
data_dir = tmp_path / "data"
data_dir.mkdir()
# Mock the httpx.get call
mock_response = Mock()
mock_response.json.return_value = mock_models_dev_response
mock_response.raise_for_status = Mock()
with (
patch("langchain_model_profiles.cli.httpx.get", return_value=mock_response),
patch("builtins.input", return_value="n"), # User declines
):
with pytest.raises(SystemExit) as exc_info:
refresh("anthropic", data_dir)
assert exc_info.value.code == 1
# Verify _profiles.py was NOT created
profiles_file = data_dir / "_profiles.py"
assert not profiles_file.exists()
def test_refresh_includes_models_defined_only_in_augmentations(
tmp_path: Path, mock_models_dev_response: dict
) -> None:
"""Ensure models that only exist in augmentations are emitted."""
data_dir = tmp_path / "data"
data_dir.mkdir()
aug_file = data_dir / "profile_augmentations.toml"
aug_file.write_text("""
provider = "anthropic"
[overrides."custom-offline-model"]
structured_output = true
pdf_inputs = true
max_input_tokens = 123
""")
mock_response = Mock()
mock_response.json.return_value = mock_models_dev_response
mock_response.raise_for_status = Mock()
with (
patch("langchain_model_profiles.cli.httpx.get", return_value=mock_response),
patch("builtins.input", return_value="y"),
):
refresh("anthropic", data_dir)
profiles_file = data_dir / "_profiles.py"
assert profiles_file.exists()
spec = importlib.util.spec_from_file_location(
"generated_profiles_aug_only", profiles_file
)
assert spec
assert spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) # type: ignore[union-attr]
assert "custom-offline-model" in module._PROFILES # type: ignore[attr-defined]
assert (
module._PROFILES["custom-offline-model"]["structured_output"] is True # type: ignore[index]
)
assert (
module._PROFILES["custom-offline-model"]["max_input_tokens"] == 123 # type: ignore[index]
)