File size: 4,111 Bytes
19729e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
98
99
"""模型配置优先级测试:用户 > 管理员 > 环境变量(需求 5 / 15.5)。

验证 ``services.model_config.resolve_chain`` 的优先级、Fallback 拼接与去重,
以及 custom 提供商的可用性判定。导入路径由 tests/conftest.py 设置。
"""

from services.model_config import (  # noqa: E402
    UserModelConfig,
    resolve_chain,
    build_llm_service,
)


def _admin(enabled=True, provider="kimi", api_key="admin-key", model="", base_url=""):
    return {"enabled": enabled, "provider": provider, "api_key": api_key,
            "model": model, "base_url": base_url}


def test_user_config_takes_priority():
    """用户自配可用时,链首为用户配置,source=user。"""
    user = UserModelConfig(provider="deepseek", api_key="user-key", model="deepseek-v4-pro")
    chain = resolve_chain(user_cfg=user, admin_cfg=_admin(), env={"OPENAI_API_KEY": "envk"})
    assert chain.source == "user"
    assert chain.active_provider == "deepseek"
    assert chain.active_model == "deepseek-v4-pro"
    # 管理员与环境档作为 Fallback 仍在链中。
    providers = [s.provider for s in chain.specs]
    assert "kimi" in providers and "openai" in providers


def test_admin_used_when_no_user_config():
    """用户未配置时,使用管理员配置,source=admin。"""
    chain = resolve_chain(user_cfg=None, admin_cfg=_admin(provider="zhipu", api_key="ak"),
                          env={"OPENAI_API_KEY": "envk"})
    assert chain.source == "admin"
    assert chain.active_provider == "zhipu"
    # 环境档兜底仍在。
    assert "openai" in [s.provider for s in chain.specs]


def test_env_used_when_no_user_or_admin():
    """用户与管理员都没有时,回退环境变量,source=env。"""
    chain = resolve_chain(user_cfg=None, admin_cfg=_admin(enabled=False),
                          env={"DEEPSEEK_API_KEY": "envk"})
    assert chain.source == "env"
    assert chain.active_provider == "deepseek"


def test_none_when_nothing_configured():
    chain = resolve_chain(user_cfg=None, admin_cfg=None, env={})
    assert chain.source == "none"
    assert chain.specs == []


def test_disabled_admin_is_ignored():
    chain = resolve_chain(user_cfg=None, admin_cfg=_admin(enabled=False), env={})
    assert chain.source == "none"


def test_custom_user_requires_base_url_and_model():
    """custom 用户配置缺 base_url/model 时不可用,回退下一档。"""
    bad = UserModelConfig(provider="custom", api_key="k")  # 缺 base_url + model
    chain = resolve_chain(user_cfg=bad, admin_cfg=_admin(), env={})
    assert chain.source == "admin"  # 用户档不可用 → 用管理员

    good = UserModelConfig(provider="custom", api_key="k",
                           base_url="https://x/v1/chat/completions", model="m")
    chain2 = resolve_chain(user_cfg=good, admin_cfg=_admin(), env={})
    assert chain2.source == "user"
    assert chain2.active_provider == "custom"
    assert chain2.active_model == "m"


def test_admin_custom_requires_base_url():
    """管理员 custom 配置缺 base_url/model 时视为不可用。"""
    chain = resolve_chain(
        user_cfg=None,
        admin_cfg=_admin(provider="custom", api_key="k"),  # 缺 base_url+model
        env={},
    )
    assert chain.source == "none"


def test_dedup_keeps_higher_priority():
    """用户与环境同为 deepseek 时去重,仅保留用户档(更高优先级)。"""
    user = UserModelConfig(provider="deepseek", api_key="user-key")
    chain = resolve_chain(user_cfg=user, admin_cfg=None, env={"DEEPSEEK_API_KEY": "envk"})
    deepseek_specs = [s for s in chain.specs if s.provider == "deepseek"]
    assert len(deepseek_specs) == 1
    assert deepseek_specs[0].api_key == "user-key"


def test_build_llm_service_preserves_order():
    user = UserModelConfig(provider="deepseek", api_key="uk", model="deepseek-v4-flash")
    chain = resolve_chain(user_cfg=user, admin_cfg=_admin(provider="kimi", api_key="ak"), env={})
    svc = build_llm_service(chain)
    assert svc._specs[0].provider == "deepseek"
    assert svc._specs[1].provider == "kimi"