File size: 3,819 Bytes
3be03dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
"""
Tests for config/settings.py — model client routing logic.
"""
import pytest
from unittest.mock import patch, MagicMock


def _get_routing_fn(llm_mode: str):
    import importlib
    import sys
    with patch.dict("os.environ", {"LLM_MODE": llm_mode}):
        for key in list(sys.modules.keys()):
            if "config.settings" in key or key == "config.settings":
                del sys.modules[key]
        import config.settings as s
        importlib.reload(s)
        return s.get_model_client, s


class TestModelClientRouting:
    def _mock_clients(self, settings_mod):
        cheap = MagicMock(name="cheap")
        reasoning = MagicMock(name="reasoning")
        analysis = MagicMock(name="analysis")
        local = MagicMock(name="local")
        settings_mod._cheap_client    = lambda: cheap
        settings_mod._reasoning_client = lambda: reasoning
        settings_mod._analysis_client  = lambda: analysis
        settings_mod._local_client     = lambda: local
        return cheap, reasoning, analysis, local

    def test_cloud_data_uses_cheap(self):
        fn, mod = _get_routing_fn("cloud")
        cheap, reasoning, analysis, local = self._mock_clients(mod)
        result = fn("data")
        assert result is cheap

    def test_cloud_reasoning_uses_reasoning(self):
        fn, mod = _get_routing_fn("cloud")
        cheap, reasoning, analysis, local = self._mock_clients(mod)
        result = fn("reasoning")
        assert result is reasoning

    def test_cloud_analysis_uses_analysis(self):
        fn, mod = _get_routing_fn("cloud")
        cheap, reasoning, analysis, local = self._mock_clients(mod)
        result = fn("analysis")
        assert result is analysis

    def test_local_mode_all_tasks_use_local(self):
        fn, mod = _get_routing_fn("local")
        cheap, reasoning, analysis, local = self._mock_clients(mod)
        for task in ["data", "reasoning", "analysis"]:
            assert fn(task) is local, f"task={task} should use local in local mode"

    def test_hybrid_data_uses_local(self):
        fn, mod = _get_routing_fn("hybrid")
        cheap, reasoning, analysis, local = self._mock_clients(mod)
        assert fn("data") is local

    def test_hybrid_reasoning_uses_reasoning(self):
        fn, mod = _get_routing_fn("hybrid")
        cheap, reasoning, analysis, local = self._mock_clients(mod)
        assert fn("reasoning") is reasoning

    def test_hybrid_analysis_uses_analysis(self):
        fn, mod = _get_routing_fn("hybrid")
        cheap, reasoning, analysis, local = self._mock_clients(mod)
        assert fn("analysis") is analysis

    def test_unknown_task_defaults_to_analysis(self):
        fn, mod = _get_routing_fn("cloud")
        cheap, reasoning, analysis, local = self._mock_clients(mod)
        assert fn("unknown_task") is analysis


class TestGroqClientFallback:
    """Ensure cheap/reasoning clients gracefully fall back when GROQ key absent."""

    def test_cheap_client_falls_back_without_groq_key(self):
        with patch.dict("os.environ", {"GROQ_API_KEY": "", "LLM_MODE": "cloud"}):
            import importlib, sys
            for key in list(sys.modules.keys()):
                if "config.settings" in key:
                    del sys.modules[key]
            import config.settings as s
            importlib.reload(s)
            # Should not raise — falls back to next provider
            # We just assert the function exists and returns something
            try:
                client = s._cheap_client()
                assert client is not None
            except Exception as e:
                # If no fallback keys set either, it's expected to fail downstream
                # but should not be a silent swallowed error
                assert "api_key" in str(e).lower() or "key" in str(e).lower()