Files
agentci/tests/test_config.py
T
StanPonomarev ce9f1e3d20
Publish container image / Build and push (push) Successful in 32s
refactor tests
2026-07-26 23:49:40 +02:00

144 lines
4.5 KiB
Python

import os
from pathlib import Path
import pytest
from pydantic import ValidationError
from agentci.config.settings import Settings
@pytest.fixture(autouse=True)
def isolate_agentci_environment(monkeypatch: pytest.MonkeyPatch) -> None:
for name in tuple(os.environ):
if name.startswith("AGENTCI_"):
monkeypatch.delenv(name)
def settings(**overrides: object) -> Settings:
return Settings(_env_file=None, **overrides) # type: ignore[arg-type,call-arg]
def test_reads_values_from_an_isolated_environment(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("AGENTCI_INSTALL_SCRIPTS", "python,dotnet")
monkeypatch.setenv("AGENTCI_MAX_CONCURRENT_JOBS", "7")
value = settings()
assert value.install_scripts == ["python", "dotnet"]
assert value.max_concurrent_jobs == 7
@pytest.mark.parametrize("field", ["gitea_url", "opencode_url"])
def test_normalizes_service_urls(field: str) -> None:
value = settings(**{field: "https://service.example/base///"})
assert getattr(value, field) == "https://service.example/base"
@pytest.mark.parametrize(
("field", "accepted", "rejected"),
[
("plan_review_rounds", (1, 20), (0, 21)),
("implement_review_rounds", (1, 20), (0, 21)),
("turn_timeout_seconds", (60,), (59,)),
("install_script_timeout_seconds", (1,), (0,)),
("worker_poll_seconds", (0.1,), (0.09,)),
("max_concurrent_jobs", (1, 32), (0, 33)),
],
)
def test_enforces_documented_numeric_boundaries(
field: str,
accepted: tuple[int | float, ...],
rejected: tuple[int | float, ...],
) -> None:
for value in accepted:
assert getattr(settings(**{field: value}), field) == value
for value in rejected:
with pytest.raises(ValidationError):
settings(**{field: value})
def test_reads_and_strips_secret_files(tmp_path: Path) -> None:
token_file = tmp_path / "token"
webhook_file = tmp_path / "webhook"
password_file = tmp_path / "password"
token_file.write_text(" gitea-token\n")
webhook_file.write_text(" webhook-secret \n")
password_file.write_text("opencode-password\n")
value = settings(
gitea_token_file=token_file,
webhook_secret_file=webhook_file,
opencode_server_password_file=password_file,
)
assert value.gitea_token == "gitea-token"
assert value.webhook_secret == b"webhook-secret"
assert value.opencode_server_password == "opencode-password"
def test_missing_secret_file_has_actionable_error(tmp_path: Path) -> None:
missing = tmp_path / "missing-token"
value = settings(gitea_token_file=missing)
with pytest.raises(RuntimeError, match="Cannot read Gitea token") as raised:
_ = value.gitea_token
assert str(missing) in str(raised.value)
def test_empty_secret_file_is_rejected(tmp_path: Path) -> None:
empty = tmp_path / "webhook"
empty.write_text(" \n")
value = settings(webhook_secret_file=empty)
with pytest.raises(RuntimeError, match="webhook secret file") as raised:
_ = value.webhook_secret
assert str(empty) in str(raised.value)
def test_derived_state_paths_follow_data_directory(tmp_path: Path) -> None:
data_dir = tmp_path / "state"
value = settings(data_dir=data_dir)
assert value.database_path == data_dir / "agentci.sqlite3"
assert value.workspaces_dir == data_dir / "workspaces"
@pytest.mark.parametrize("value", ["../script", "tools/setup", "python,python"])
def test_rejects_unsafe_or_duplicate_install_scripts(value: str) -> None:
with pytest.raises(ValidationError):
settings(install_scripts=value)
@pytest.mark.parametrize(
("value", "expected"),
[
(" python, dotnet, company-tools, ", ["python", "dotnet", "company-tools"]),
("", []),
(None, []),
([], []),
],
)
def test_normalizes_install_scripts(value: object, expected: list[str]) -> None:
assert settings(install_scripts=value).install_scripts == expected
def test_agent_defaults_select_expected_capacity_and_research_models() -> None:
value = settings()
assert value.max_concurrent_jobs == 2
assert (value.explore_model, value.explore_variant) == (
"openai/gpt-5.6-luna",
"low",
)
assert value.research_variant == "high"
@pytest.mark.parametrize(
"field", ["plan_model", "implement_model", "explore_model", "research_model"]
)
def test_requires_provider_qualified_opencode_models(field: str) -> None:
with pytest.raises(ValidationError, match="provider/model"):
settings(**{field: "model-only"})