mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-15 17:25:26 -04:00
f939cb65ce
* refactor(tests): replace local function copies in test_endpoint_resolver with real imports The test file carried 9 verbatim copies of src/endpoint_resolver.py functions to avoid import-pollution concerns, but these copies are a drift hazard — PR #3343 had to update both in parallel. Replace them with direct imports so future changes to endpoint_resolver are automatically exercised by the test suite. Also fixes _ollama_api_root in endpoint_resolver.py: the bare-URL Ollama case (e.g. http://nas:11434 with empty path) was already handled correctly in the test copy but was missing from the real function, which would return /chat instead of /api/chat for native Ollama endpoints without an explicit /api prefix. Closes #3351 * refactor: import _ollama_api_root from llm_core instead of duplicating it endpoint_resolver already imports _detect_provider and _host_match from llm_core. Add _ollama_api_root to that import and remove the local copy, collapsing two implementations to one source of truth. llm_core's version is a superset (also strips /api/chat|tags|generate paths), and since normalize_base already removes those suffixes upstream the result is identical for every input used here.
149 lines
5.7 KiB
Python
149 lines
5.7 KiB
Python
"""Tests for endpoint_resolver — pure functions tested directly."""
|
|
import json
|
|
|
|
from src.endpoint_resolver import (
|
|
_first_chat_model,
|
|
_endpoint_hidden_models,
|
|
_endpoint_enabled_models,
|
|
normalize_base,
|
|
build_chat_url,
|
|
build_models_url,
|
|
build_headers,
|
|
)
|
|
|
|
|
|
class TestNormalizeBase:
|
|
def test_strips_models(self):
|
|
assert normalize_base("https://api.openai.com/v1/models") == "https://api.openai.com/v1"
|
|
|
|
def test_strips_chat_completions(self):
|
|
assert normalize_base("https://api.openai.com/v1/chat/completions") == "https://api.openai.com/v1"
|
|
|
|
def test_strips_completions(self):
|
|
assert normalize_base("https://api.openai.com/v1/completions") == "https://api.openai.com/v1"
|
|
|
|
def test_strips_v1_messages(self):
|
|
assert normalize_base("https://api.anthropic.com/v1/messages") == "https://api.anthropic.com"
|
|
|
|
def test_strips_ollama_native_chat(self):
|
|
assert normalize_base("https://ollama.com/api/chat") == "https://ollama.com/api"
|
|
|
|
def test_trailing_slash(self):
|
|
assert normalize_base("https://api.openai.com/v1/") == "https://api.openai.com/v1"
|
|
|
|
def test_clean_url_unchanged(self):
|
|
assert normalize_base("https://api.openai.com/v1") == "https://api.openai.com/v1"
|
|
|
|
def test_empty_string(self):
|
|
assert normalize_base("") == ""
|
|
|
|
def test_none_safe(self):
|
|
assert normalize_base(None) == ""
|
|
|
|
|
|
class TestBuildChatUrl:
|
|
def test_openai_style(self):
|
|
assert build_chat_url("https://api.openai.com/v1") == "https://api.openai.com/v1/chat/completions"
|
|
|
|
def test_anthropic_style(self):
|
|
assert build_chat_url("https://api.anthropic.com") == "https://api.anthropic.com/v1/messages"
|
|
|
|
def test_anthropic_v1_base_does_not_double_v1(self):
|
|
assert build_chat_url("https://api.anthropic.com/v1") == "https://api.anthropic.com/v1/messages"
|
|
|
|
def test_local_endpoint(self):
|
|
assert build_chat_url("http://localhost:8000/v1") == "http://localhost:8000/v1/chat/completions"
|
|
|
|
def test_ollama_cloud_native_api(self):
|
|
assert build_chat_url("https://ollama.com/api") == "https://ollama.com/api/chat"
|
|
|
|
def test_ollama_cloud_root_adds_api(self):
|
|
assert build_chat_url("https://ollama.com") == "https://ollama.com/api/chat"
|
|
|
|
def test_ollama_bare_url_adds_api(self):
|
|
assert build_chat_url("http://nas:11434") == "http://nas:11434/api/chat"
|
|
|
|
def test_ollama_v1_preserves_openai_compat(self):
|
|
assert build_chat_url("http://nas:11434/v1") == "http://nas:11434/v1/chat/completions"
|
|
|
|
|
|
class TestBuildModelsUrl:
|
|
def test_openai_models(self):
|
|
assert build_models_url("https://api.openai.com/v1") == "https://api.openai.com/v1/models"
|
|
|
|
def test_ollama_tags(self):
|
|
assert build_models_url("https://ollama.com/api") == "https://ollama.com/api/tags"
|
|
|
|
|
|
class TestBuildHeaders:
|
|
def test_no_key(self):
|
|
assert build_headers(None, "https://api.openai.com/v1") == {}
|
|
|
|
def test_openai_bearer(self):
|
|
assert build_headers("sk-abc", "https://api.openai.com/v1") == {"Authorization": "Bearer sk-abc"}
|
|
|
|
def test_anthropic_headers(self):
|
|
assert build_headers("sk-ant-abc", "https://api.anthropic.com") == {"x-api-key": "sk-ant-abc", "anthropic-version": "2023-06-01"}
|
|
|
|
def test_empty_key(self):
|
|
assert build_headers("", "https://api.openai.com/v1") == {}
|
|
|
|
|
|
class _Ep:
|
|
"""Minimal ModelEndpoint stand-in for the model-picking helpers."""
|
|
def __init__(self, cached=None, hidden=None):
|
|
self.cached_models = json.dumps(cached) if cached is not None else None
|
|
self.hidden_models = json.dumps(hidden) if hidden is not None else None
|
|
|
|
|
|
class TestFirstChatModel:
|
|
def test_skips_embedding_and_tts(self):
|
|
models = ["text-embedding-ada-002", "whisper-large-v3", "gpt-4o"]
|
|
assert _first_chat_model(models) == "gpt-4o"
|
|
|
|
def test_falls_back_to_first_when_all_non_chat(self):
|
|
assert _first_chat_model(["whisper-large-v3"]) == "whisper-large-v3"
|
|
|
|
def test_empty(self):
|
|
assert _first_chat_model([]) is None
|
|
|
|
|
|
class TestEnabledModels:
|
|
def test_excludes_hidden(self):
|
|
# The Groq repro: 16 models, only gpt-oss-120b enabled.
|
|
cached = [
|
|
"openai/gpt-oss-safeguard-20b", "canopylabs/orpheus-arabic-saudi",
|
|
"whisper-large-v3", "openai/gpt-oss-120b",
|
|
]
|
|
hidden = [
|
|
"openai/gpt-oss-safeguard-20b", "canopylabs/orpheus-arabic-saudi",
|
|
"whisper-large-v3",
|
|
]
|
|
ep = _Ep(cached=cached, hidden=hidden)
|
|
assert _endpoint_enabled_models(ep) == ["openai/gpt-oss-120b"]
|
|
|
|
def test_no_hidden_returns_all(self):
|
|
ep = _Ep(cached=["a", "b"], hidden=None)
|
|
assert _endpoint_enabled_models(ep) == ["a", "b"]
|
|
|
|
def test_picker_never_selects_disabled_model(self):
|
|
# Regression: a disabled model listed first must not be auto-picked.
|
|
cached = ["canopylabs/orpheus-arabic-saudi", "openai/gpt-oss-120b"]
|
|
hidden = ["canopylabs/orpheus-arabic-saudi"]
|
|
ep = _Ep(cached=cached, hidden=hidden)
|
|
assert _first_chat_model(_endpoint_enabled_models(ep)) == "openai/gpt-oss-120b"
|
|
|
|
def test_stale_configured_model_is_discarded(self):
|
|
# A configured model that's been disabled is dropped, falling through
|
|
# to the first enabled chat model.
|
|
ep = _Ep(
|
|
cached=["canopylabs/orpheus-arabic-saudi", "openai/gpt-oss-120b"],
|
|
hidden=["canopylabs/orpheus-arabic-saudi"],
|
|
)
|
|
configured = "canopylabs/orpheus-arabic-saudi"
|
|
if configured in _endpoint_hidden_models(ep):
|
|
configured = ""
|
|
if not configured:
|
|
configured = _first_chat_model(_endpoint_enabled_models(ep))
|
|
assert configured == "openai/gpt-oss-120b"
|