Files
oh-my-pi/python/robomp/tests/test_config.py
T
can1357 2c773a12a4 Add 'python/robomp/' from commit '553fd1cfcf59e4c501c54fc81bc083ffd2ca007b'
git-subtree-dir: python/robomp
git-subtree-mainline: 4f6e70f779
git-subtree-split: 553fd1cfcf
2026-05-16 21:00:42 +02:00

135 lines
5.2 KiB
Python

from __future__ import annotations
import pytest
from pydantic import ValidationError
from robomp.config import Settings, reset_settings_cache
def test_settings_load_from_env(env: dict[str, str]) -> None:
cfg = Settings() # type: ignore[call-arg]
assert cfg.bot_login == "robomp-bot"
assert cfg.repo_allowlist == frozenset({"octo/widget"})
assert cfg.allows("octo/widget")
assert cfg.allows("Octo/Widget")
assert not cfg.allows("other/widget")
def test_settings_missing_required(monkeypatch: pytest.MonkeyPatch, env: dict[str, str]) -> None:
"""Empty out every credential source: validator MUST trip the
'no GitHub access configured' branch. The `env` fixture keeps the other
required fields satisfied so we isolate the credential-validator path."""
monkeypatch.setenv("GITHUB_TOKEN", "")
monkeypatch.setenv("ROBOMP_GH_PROXY_URL", "")
monkeypatch.setenv("ROBOMP_GH_PROXY_HMAC_KEY", "")
reset_settings_cache()
with pytest.raises(ValidationError, match="no GitHub access configured"):
Settings() # type: ignore[call-arg]
def test_orchestrator_mode_loads_proxy_config(env: dict[str, str]) -> None:
cfg = Settings() # type: ignore[call-arg]
assert cfg.github_token is None
assert cfg.gh_proxy_url == "http://gh-proxy.invalid:8081"
assert cfg.gh_proxy_hmac_key is not None
assert cfg.gh_proxy_hmac_key.get_secret_value().startswith("test-hmac-key")
def test_rejects_token_and_proxy_together(monkeypatch: pytest.MonkeyPatch, env: dict[str, str]) -> None:
monkeypatch.setenv("GITHUB_TOKEN", "x")
reset_settings_cache()
with pytest.raises(ValidationError):
Settings() # type: ignore[call-arg]
def test_rejects_proxy_url_without_key(monkeypatch: pytest.MonkeyPatch, env: dict[str, str]) -> None:
monkeypatch.setenv("ROBOMP_GH_PROXY_HMAC_KEY", "")
reset_settings_cache()
with pytest.raises(ValidationError):
Settings() # type: ignore[call-arg]
def test_proxy_mode_loads_pat(proxy_env: dict[str, str]) -> None:
cfg = Settings() # type: ignore[call-arg]
assert cfg.github_token is not None
assert cfg.github_token.get_secret_value() == "ghp_test_token_value_xxxxxxxxxxxxxxxx"
assert cfg.gh_proxy_url is None
assert cfg.gh_proxy_hmac_key is None
def test_allowlist_csv_parsing(monkeypatch: pytest.MonkeyPatch, env: dict[str, str]) -> None:
monkeypatch.setenv("ROBOMP_REPO_ALLOWLIST", " alpha/one ,beta/two, ,gamma/three ")
reset_settings_cache()
cfg = Settings() # type: ignore[call-arg]
assert cfg.repo_allowlist == frozenset({"alpha/one", "beta/two", "gamma/three"})
def test_blank_replay_token_treated_as_disabled(monkeypatch: pytest.MonkeyPatch, env: dict[str, str]) -> None:
monkeypatch.setenv("ROBOMP_REPLAY_TOKEN", "")
reset_settings_cache()
cfg = Settings() # type: ignore[call-arg]
assert cfg.replay_token is None
def test_whitespace_replay_token_treated_as_disabled(monkeypatch: pytest.MonkeyPatch, env: dict[str, str]) -> None:
monkeypatch.setenv("ROBOMP_REPLAY_TOKEN", " ")
reset_settings_cache()
cfg = Settings() # type: ignore[call-arg]
assert cfg.replay_token is None
def test_real_replay_token_preserved(monkeypatch: pytest.MonkeyPatch, env: dict[str, str]) -> None:
monkeypatch.setenv("ROBOMP_REPLAY_TOKEN", "abc")
reset_settings_cache()
cfg = Settings() # type: ignore[call-arg]
assert cfg.replay_token is not None
assert cfg.replay_token.get_secret_value() == "abc"
def test_blank_bot_login_rejected(monkeypatch: pytest.MonkeyPatch, env: dict[str, str]) -> None:
monkeypatch.setenv("ROBOMP_BOT_LOGIN", " ")
reset_settings_cache()
with pytest.raises(ValidationError):
Settings() # type: ignore[call-arg]
def test_model_pool_single(env: dict[str, str]) -> None:
cfg = Settings() # type: ignore[call-arg]
assert cfg.model_pool == (cfg.model,)
assert cfg.pick_model() == cfg.model
def test_model_pool_csv_parses(monkeypatch: pytest.MonkeyPatch, env: dict[str, str]) -> None:
monkeypatch.setenv(
"ROBOMP_MODEL",
" p-codex/gpt-5.4 , p-anthropic/claude-sonnet-4-6 ,, p-anthropic/claude-opus-4-7 ",
)
reset_settings_cache()
cfg = Settings() # type: ignore[call-arg]
assert cfg.model_pool == (
"p-codex/gpt-5.4",
"p-anthropic/claude-sonnet-4-6",
"p-anthropic/claude-opus-4-7",
)
def test_pick_model_covers_full_pool(monkeypatch: pytest.MonkeyPatch, env: dict[str, str]) -> None:
"""With a 3-item pool and 500 picks, each option appears at least once."""
monkeypatch.setenv("ROBOMP_MODEL", "a,b,c")
reset_settings_cache()
cfg = Settings() # type: ignore[call-arg]
seen = {cfg.pick_model() for _ in range(500)}
assert seen == {"a", "b", "c"}
def test_max_concurrency_default_is_8(env: dict[str, str]) -> None:
cfg = Settings() # type: ignore[call-arg]
assert cfg.max_concurrency == 8
def test_task_timeout_hard_grace_env_parses(monkeypatch: pytest.MonkeyPatch, env: dict[str, str]) -> None:
monkeypatch.setenv("ROBOMP_TASK_TIMEOUT_HARD_GRACE_SECONDS", "12.5")
reset_settings_cache()
cfg = Settings() # type: ignore[call-arg]
assert cfg.task_timeout_hard_grace_seconds == 12.5