mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-14 20:25:26 +02:00
120 lines
4.9 KiB
Python
120 lines
4.9 KiB
Python
from collections.abc import Callable
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Literal
|
|
|
|
import pytest
|
|
|
|
from llm.cli.selector import save_config
|
|
from llm.core.interface import LLMProvider
|
|
from llm.core.types import ProviderType
|
|
from llm.providers.astraflow import AstraflowCNProvider, AstraflowProvider
|
|
from llm.providers.atlas import AtlasProvider
|
|
from llm.providers.ollama import OllamaProvider
|
|
from llm.providers.resolver import get_provider
|
|
|
|
pytestmark = pytest.mark.unit
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ProviderCase:
|
|
provider_type: ProviderType
|
|
constructor: Callable[..., LLMProvider]
|
|
model_env: str
|
|
builtin_model: str
|
|
|
|
|
|
PROVIDERS = (
|
|
ProviderCase(ProviderType.OLLAMA, OllamaProvider, "OLLAMA_MODEL", "llama3.2"),
|
|
ProviderCase(ProviderType.ATLAS, AtlasProvider, "ATLAS_MODEL", "deepseek-ai/deepseek-v4-pro"),
|
|
ProviderCase(ProviderType.ASTRAFLOW, AstraflowProvider, "ASTRAFLOW_MODEL", "gpt-4o-mini"),
|
|
ProviderCase(ProviderType.ASTRAFLOW_CN, AstraflowCNProvider, "ASTRAFLOW_CN_MODEL", "gpt-4o-mini"),
|
|
)
|
|
PROVIDER_IDS = tuple(case.provider_type.value for case in PROVIDERS)
|
|
BLANK_MODELS = (" \t ", ' " \t " ')
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def isolated_model_config(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
|
monkeypatch.chdir(tmp_path)
|
|
for name in (
|
|
"LLM_PROVIDER", "LLM_MODEL", "OLLAMA_MODEL", "ATLAS_MODEL",
|
|
"ASTRAFLOW_MODEL", "ASTRAFLOW_CN_MODEL", "OLLAMA_BASE_URL",
|
|
"ATLAS_BASE_URL", "ASTRAFLOW_BASE_URL", "ASTRAFLOW_CN_BASE_URL",
|
|
):
|
|
# Record even absent variables: save_config(persist=True) writes them directly.
|
|
monkeypatch.setenv(name, "")
|
|
monkeypatch.delenv(name)
|
|
for name in ("ATLAS_API_KEY", "ASTRAFLOW_API_KEY", "ASTRAFLOW_CN_API_KEY"):
|
|
monkeypatch.setenv(name, "test-key")
|
|
|
|
|
|
def set_provider_model(
|
|
monkeypatch: pytest.MonkeyPatch, case: ProviderCase, value: str
|
|
) -> None:
|
|
monkeypatch.setenv(case.model_env, value)
|
|
if case.provider_type == ProviderType.ASTRAFLOW_CN:
|
|
monkeypatch.setenv("ASTRAFLOW_MODEL", value)
|
|
|
|
|
|
@pytest.mark.parametrize("case", PROVIDERS, ids=PROVIDER_IDS)
|
|
@pytest.mark.parametrize("blank_model", BLANK_MODELS, ids=("whitespace", "quoted-whitespace"))
|
|
@pytest.mark.parametrize("source", ("environment", "saved"))
|
|
def test_blank_provider_model_falls_back_to_selected_model(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
case: ProviderCase,
|
|
blank_model: str,
|
|
source: Literal["environment", "saved"],
|
|
) -> None:
|
|
save_config(case.provider_type.value, "Selected/MixedCase-model", persist=source == "environment")
|
|
set_provider_model(monkeypatch, case, blank_model)
|
|
|
|
assert get_provider().get_default_model() == "Selected/MixedCase-model"
|
|
|
|
|
|
@pytest.mark.parametrize("case", PROVIDERS, ids=PROVIDER_IDS)
|
|
@pytest.mark.parametrize("blank_model", BLANK_MODELS, ids=("whitespace", "quoted-whitespace"))
|
|
def test_blank_provider_model_without_selection_retains_builtin_default(
|
|
monkeypatch: pytest.MonkeyPatch, case: ProviderCase, blank_model: str
|
|
) -> None:
|
|
set_provider_model(monkeypatch, case, blank_model)
|
|
|
|
assert get_provider(case.provider_type).get_default_model() == case.builtin_model
|
|
assert case.constructor().get_default_model() == case.builtin_model
|
|
|
|
|
|
@pytest.mark.parametrize("case", PROVIDERS, ids=PROVIDER_IDS)
|
|
@pytest.mark.parametrize("model", (' "Provider/MixedCase-model" ', " 'Provider/MixedCase-model' "))
|
|
def test_quoted_provider_model_is_normalized_without_changing_case(
|
|
monkeypatch: pytest.MonkeyPatch, case: ProviderCase, model: str
|
|
) -> None:
|
|
monkeypatch.setenv("LLM_MODEL", "selected-model")
|
|
monkeypatch.setenv(case.model_env, model)
|
|
|
|
assert get_provider(case.provider_type).get_default_model() == "Provider/MixedCase-model"
|
|
assert case.constructor().get_default_model() == "Provider/MixedCase-model"
|
|
|
|
|
|
@pytest.mark.parametrize("case", PROVIDERS, ids=PROVIDER_IDS)
|
|
def test_explicit_default_model_keeps_precedence_and_original_value(
|
|
monkeypatch: pytest.MonkeyPatch, case: ProviderCase
|
|
) -> None:
|
|
save_config(case.provider_type.value, "selected-model", persist=True)
|
|
set_provider_model(monkeypatch, case, ' "Provider/MixedCase-model" ')
|
|
explicit_model = ' "Explicit/MixedCase-model" '
|
|
|
|
assert get_provider(default_model=explicit_model).get_default_model() == explicit_model
|
|
assert case.constructor(default_model=explicit_model).get_default_model() == explicit_model
|
|
|
|
|
|
@pytest.mark.parametrize("blank_model", BLANK_MODELS, ids=("whitespace", "quoted-whitespace"))
|
|
def test_cn_blank_specific_model_uses_normalized_shared_model(
|
|
monkeypatch: pytest.MonkeyPatch, blank_model: str
|
|
) -> None:
|
|
monkeypatch.setenv("ASTRAFLOW_CN_MODEL", blank_model)
|
|
monkeypatch.setenv("ASTRAFLOW_MODEL", ' "Shared/MixedCase-model" ')
|
|
monkeypatch.setenv("LLM_MODEL", "selected-model")
|
|
|
|
assert get_provider(ProviderType.ASTRAFLOW_CN).get_default_model() == "Shared/MixedCase-model"
|
|
assert AstraflowCNProvider().get_default_model() == "Shared/MixedCase-model"
|