rewrite phase 1
This commit is contained in:
+132
-43
@@ -1,61 +1,150 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from agentci.config import Settings
|
||||
|
||||
|
||||
def test_parses_comma_delimited_install_scripts() -> None:
|
||||
settings = Settings(
|
||||
_env_file=None, # type: ignore[call-arg]
|
||||
install_scripts=" python, dotnet, company-tools, ",
|
||||
@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", "boundaries"),
|
||||
[
|
||||
("plan_review_rounds", (1, 20)),
|
||||
("implement_review_rounds", (1, 20)),
|
||||
("turn_timeout_seconds", (60,)),
|
||||
("install_script_timeout_seconds", (1,)),
|
||||
("worker_poll_seconds", (0.1,)),
|
||||
("max_concurrent_jobs", (1, 32)),
|
||||
],
|
||||
)
|
||||
def test_accepts_documented_numeric_boundaries(
|
||||
field: str, boundaries: tuple[int | float, ...]
|
||||
) -> None:
|
||||
for boundary in boundaries:
|
||||
assert getattr(settings(**{field: boundary}), field) == boundary
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("field", "value"),
|
||||
[
|
||||
("plan_review_rounds", 0),
|
||||
("plan_review_rounds", 21),
|
||||
("implement_review_rounds", 0),
|
||||
("implement_review_rounds", 21),
|
||||
("turn_timeout_seconds", 59),
|
||||
("install_script_timeout_seconds", 0),
|
||||
("worker_poll_seconds", 0.09),
|
||||
("max_concurrent_jobs", 0),
|
||||
("max_concurrent_jobs", 33),
|
||||
],
|
||||
)
|
||||
def test_rejects_values_outside_numeric_boundaries(field: str, value: int | float) -> None:
|
||||
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 settings.install_scripts == ["python", "dotnet", "company-tools"]
|
||||
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"
|
||||
|
||||
|
||||
def test_parses_comma_delimited_install_scripts() -> None:
|
||||
value = settings(install_scripts=" python, dotnet, company-tools, ")
|
||||
|
||||
assert value.install_scripts == ["python", "dotnet", "company-tools"]
|
||||
|
||||
|
||||
@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(_env_file=None, install_scripts=value) # type: ignore[call-arg]
|
||||
settings(install_scripts=value)
|
||||
|
||||
|
||||
def test_empty_install_scripts_disable_setup() -> None:
|
||||
settings = Settings(
|
||||
_env_file=None, # type: ignore[call-arg]
|
||||
install_scripts="",
|
||||
@pytest.mark.parametrize("value", ["", None, []])
|
||||
def test_empty_install_scripts_disable_setup(value: object) -> None:
|
||||
assert settings(install_scripts=value).install_scripts == []
|
||||
|
||||
|
||||
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 settings.install_scripts == []
|
||||
|
||||
|
||||
def test_defaults_research_variant_to_high(monkeypatch) -> None:
|
||||
monkeypatch.delenv("AGENTCI_RESEARCH_VARIANT", raising=False)
|
||||
settings = Settings(_env_file=None) # type: ignore[call-arg]
|
||||
assert settings.research_variant == "high"
|
||||
|
||||
|
||||
def test_defaults_explore_agent_to_luna_low() -> None:
|
||||
settings = Settings(_env_file=None) # type: ignore[call-arg]
|
||||
assert settings.explore_model == "openai/gpt-5.6-luna"
|
||||
assert settings.explore_variant == "low"
|
||||
|
||||
|
||||
def test_defaults_to_two_concurrent_jobs() -> None:
|
||||
settings = Settings(_env_file=None) # type: ignore[call-arg]
|
||||
assert settings.max_concurrent_jobs == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [0, 33])
|
||||
def test_rejects_unsafe_job_concurrency(value: int) -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
Settings(_env_file=None, max_concurrent_jobs=value) # type: ignore[call-arg]
|
||||
|
||||
|
||||
def test_reads_comma_delimited_install_scripts_from_environment(monkeypatch) -> None:
|
||||
monkeypatch.setenv("AGENTCI_INSTALL_SCRIPTS", "python,dotnet")
|
||||
|
||||
settings = Settings(_env_file=None) # type: ignore[call-arg]
|
||||
|
||||
assert settings.install_scripts == ["python", "dotnet"]
|
||||
assert value.research_variant == "high"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -63,4 +152,4 @@ def test_reads_comma_delimited_install_scripts_from_environment(monkeypatch) ->
|
||||
)
|
||||
def test_requires_provider_qualified_opencode_models(field: str) -> None:
|
||||
with pytest.raises(ValidationError, match="provider/model"):
|
||||
Settings(_env_file=None, **{field: "model-only"}) # type: ignore[call-arg]
|
||||
settings(**{field: "model-only"})
|
||||
|
||||
Reference in New Issue
Block a user