"""Verifies get_llm_client() returns a usage-tracked client end to end.""" from __future__ import annotations from unittest.mock import MagicMock, patch import pytest from app.services.llm.llm_factory import LLMFactory, get_llm_client from app.services.llm.tracked_client import TrackedLLMClient from app.shared.model_usage_tracker import get_model_usage_tracker @pytest.fixture(autouse=True) def _reset_singletons(): """Clear the two process-wide singletons this test touches, before and after. LLMFactory._global_instances and get_model_usage_tracker() both persist for the life of the process; without this fixture, tests would leak cached clients/usage data into each other and become order-dependent. """ LLMFactory._global_instances.clear() get_model_usage_tracker().snapshot() # no-op read, just documents intent get_model_usage_tracker()._entries.clear() yield LLMFactory._global_instances.clear() get_model_usage_tracker()._entries.clear() def test_get_llm_client_returns_tracked_client(): """get_llm_client() must return a TrackedLLMClient, not the raw provider client.""" with patch("app.services.llm.llm_factory.DeepSeekClient") as mock_cls: mock_cls.return_value = MagicMock() client = get_llm_client(provider="deepseek", model="deepseek-v4-flash", api_key="test-key") assert isinstance(client, TrackedLLMClient) def test_get_llm_client_caches_the_tracked_instance(): """A second call with the same provider/model must return the same TrackedLLMClient.""" with patch("app.services.llm.llm_factory.DeepSeekClient") as mock_cls: mock_cls.return_value = MagicMock() first = get_llm_client(provider="deepseek", model="deepseek-v4-flash", api_key="test-key") second = get_llm_client(provider="deepseek", model="deepseek-v4-flash", api_key="test-key") assert first is second