86 lines
3.5 KiB
Python
86 lines
3.5 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from app.core.config import Settings, supports_reasoning
|
|
from tests.conftest import build_settings
|
|
|
|
|
|
class TestModelFamilyCheck:
|
|
@pytest.mark.parametrize(
|
|
"model",
|
|
["gpt-5.4-mini", "gpt-5.5", "gpt-5", "gpt-5.6-terra", "gpt-4.1", "o3", "o4-mini"],
|
|
)
|
|
def test_accepts_known_structured_output_families(self, model: str) -> None:
|
|
assert build_settings(openai_model=model).openai_model == model
|
|
|
|
def test_accepts_a_future_point_release(self) -> None:
|
|
"""Prefix matching exists so a new gpt-5.x is not rejected on arrival."""
|
|
assert build_settings(openai_model="gpt-5.9-turbo").openai_model == "gpt-5.9-turbo"
|
|
|
|
def test_rejects_chat_latest_variants(self) -> None:
|
|
with pytest.raises(ValidationError, match="chat-product variant"):
|
|
build_settings(openai_model="gpt-5.4-chat-latest")
|
|
|
|
def test_rejects_gpt_4o(self) -> None:
|
|
"""Pre-2024-08-06 snapshots lack structured outputs and aliases hide which is which."""
|
|
with pytest.raises(ValidationError):
|
|
build_settings(openai_model="gpt-4o")
|
|
|
|
def test_rejects_an_unknown_family(self) -> None:
|
|
with pytest.raises(ValidationError):
|
|
build_settings(openai_model="llama-3-70b")
|
|
|
|
def test_rejects_empty(self) -> None:
|
|
with pytest.raises(ValidationError):
|
|
build_settings(openai_model=" ")
|
|
|
|
|
|
class TestReasoningDetection:
|
|
@pytest.mark.parametrize("model", ["gpt-5.4-mini", "gpt-5", "o3", "o4-mini"])
|
|
def test_reasoning_families(self, model: str) -> None:
|
|
assert supports_reasoning(model)
|
|
|
|
def test_gpt_41_is_not_a_reasoning_model(self) -> None:
|
|
"""Allowed as a model, but sending it a reasoning parameter is a 400."""
|
|
assert not supports_reasoning("gpt-4.1")
|
|
|
|
|
|
class TestEffort:
|
|
@pytest.mark.parametrize("effort", ["none", "minimal", "low", "medium", "high", "xhigh"])
|
|
def test_accepts_valid_levels(self, effort: str) -> None:
|
|
assert build_settings(openai_effort=effort).openai_effort == effort
|
|
|
|
def test_normalises_case(self) -> None:
|
|
assert build_settings(openai_effort="LOW").openai_effort == "low"
|
|
|
|
def test_rejects_unknown_level(self) -> None:
|
|
with pytest.raises(ValidationError):
|
|
build_settings(openai_effort="extreme")
|
|
|
|
|
|
class TestDefaults:
|
|
def test_defaults_match_the_documented_baseline(self) -> None:
|
|
settings = Settings(_env_file=None)
|
|
assert settings.openai_model == "gpt-5.4-mini"
|
|
assert settings.openai_max_output_tokens == 4000
|
|
assert settings.openai_effort == "low"
|
|
assert settings.openai_enable_prompt_cache is True
|
|
|
|
def test_has_no_sampling_parameters(self) -> None:
|
|
"""Their presence would be a 400 waiting to happen on a reasoning model."""
|
|
fields = set(Settings.model_fields)
|
|
assert not {"openai_temperature", "openai_top_p"} & fields
|
|
|
|
def test_max_output_tokens_floor_rejects_a_truncating_budget(self) -> None:
|
|
"""Reasoning tokens share this budget, so a small cap truncates the JSON."""
|
|
with pytest.raises(ValidationError):
|
|
build_settings(openai_max_output_tokens=1200)
|
|
|
|
def test_max_output_tokens_floor_is_the_lowest_safe_budget(self) -> None:
|
|
assert build_settings(openai_max_output_tokens=2048).openai_max_output_tokens == 2048
|
|
|
|
def test_size_limit_converts_to_bytes(self) -> None:
|
|
assert build_settings(max_pdf_size_mb=10).max_pdf_size_bytes == 10 * 1024 * 1024
|