rewrite phase 1
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
import pytest
|
||||
|
||||
import agentci.runtime as runtime_module
|
||||
from agentci.config import Settings
|
||||
from agentci.runtime import Runtime
|
||||
|
||||
|
||||
class ClosingClient:
|
||||
def __init__(self, name: str, events: list[str], error: Exception | None = None) -> None:
|
||||
self.name = name
|
||||
self.events = events
|
||||
self.error = error
|
||||
|
||||
async def close(self) -> None:
|
||||
self.events.append(self.name)
|
||||
if self.error is not None:
|
||||
raise self.error
|
||||
|
||||
|
||||
def runtime(opencode: object, gitea: object) -> Runtime:
|
||||
return Runtime(
|
||||
settings=SimpleNamespace(), # type: ignore[arg-type]
|
||||
repository=SimpleNamespace(), # type: ignore[arg-type]
|
||||
gitea=gitea, # type: ignore[arg-type]
|
||||
git=SimpleNamespace(), # type: ignore[arg-type]
|
||||
opencode=opencode, # type: ignore[arg-type]
|
||||
worker=SimpleNamespace(), # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
|
||||
async def test_close_releases_provider_clients_in_order() -> None:
|
||||
events: list[str] = []
|
||||
value = runtime(ClosingClient("opencode", events), ClosingClient("gitea", events))
|
||||
|
||||
await value.close()
|
||||
|
||||
assert events == ["opencode", "gitea"]
|
||||
|
||||
|
||||
async def test_close_still_releases_gitea_when_opencode_close_fails() -> None:
|
||||
events: list[str] = []
|
||||
value = runtime(
|
||||
ClosingClient("opencode", events, RuntimeError("opencode close failed")),
|
||||
ClosingClient("gitea", events),
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError, match="opencode close failed"):
|
||||
await value.close()
|
||||
|
||||
assert events == ["opencode", "gitea"]
|
||||
|
||||
|
||||
async def test_close_propagates_gitea_close_failure_after_opencode_closes() -> None:
|
||||
events: list[str] = []
|
||||
value = runtime(
|
||||
ClosingClient("opencode", events),
|
||||
ClosingClient("gitea", events, RuntimeError("gitea close failed")),
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError, match="gitea close failed"):
|
||||
await value.close()
|
||||
|
||||
assert events == ["opencode", "gitea"]
|
||||
|
||||
|
||||
async def test_build_runtime_wires_components_without_starting_real_clients(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
token_file = tmp_path / "gitea-token"
|
||||
password_file = tmp_path / "opencode-password"
|
||||
token_file.write_text("token\n")
|
||||
password_file.write_text("password\n")
|
||||
settings = Settings(
|
||||
_env_file=None, # type: ignore[call-arg]
|
||||
data_dir=tmp_path / "data",
|
||||
gitea_url="https://gitea.example/",
|
||||
gitea_token_file=token_file,
|
||||
opencode_url="https://opencode.example/",
|
||||
opencode_server_password_file=password_file,
|
||||
install_scripts=["python"],
|
||||
max_concurrent_jobs=4,
|
||||
)
|
||||
initialize = AsyncMock()
|
||||
repository = SimpleNamespace(initialize=initialize)
|
||||
gitea = SimpleNamespace()
|
||||
git = SimpleNamespace()
|
||||
opencode = SimpleNamespace()
|
||||
development = SimpleNamespace()
|
||||
prompts = SimpleNamespace()
|
||||
services = SimpleNamespace()
|
||||
worker = SimpleNamespace()
|
||||
repository_constructor = Mock(return_value=repository)
|
||||
gitea_constructor = Mock(return_value=gitea)
|
||||
git_constructor = Mock(return_value=git)
|
||||
opencode_constructor = Mock(return_value=opencode)
|
||||
development_constructor = Mock(return_value=development)
|
||||
prompt_constructor = Mock(return_value=prompts)
|
||||
services_constructor = Mock(return_value=services)
|
||||
worker_constructor = Mock(return_value=worker)
|
||||
monkeypatch.setattr(runtime_module, "Repository", repository_constructor)
|
||||
monkeypatch.setattr(runtime_module, "Gitea", gitea_constructor)
|
||||
monkeypatch.setattr(runtime_module, "Git", git_constructor)
|
||||
monkeypatch.setattr(runtime_module, "OpenCode", opencode_constructor)
|
||||
monkeypatch.setattr(runtime_module, "DevelopmentEnvironment", development_constructor)
|
||||
monkeypatch.setattr(runtime_module, "PromptLibrary", prompt_constructor)
|
||||
monkeypatch.setattr(runtime_module, "WorkflowServices", services_constructor)
|
||||
monkeypatch.setattr(runtime_module, "Worker", worker_constructor)
|
||||
|
||||
built = await runtime_module.build_runtime(settings)
|
||||
|
||||
assert runtime_module.__file__ is not None
|
||||
package_dir = Path(runtime_module.__file__).parent
|
||||
repository_constructor.assert_called_once_with(
|
||||
settings.database_path, package_dir / "migrations"
|
||||
)
|
||||
initialize.assert_awaited_once_with()
|
||||
gitea_constructor.assert_called_once_with("https://gitea.example", "token")
|
||||
git_constructor.assert_called_once_with(
|
||||
gitea_url="https://gitea.example",
|
||||
username=settings.bot_username,
|
||||
token="token",
|
||||
askpass_path=settings.askpass_path,
|
||||
commit_name=settings.bot_name,
|
||||
commit_email=settings.bot_email,
|
||||
)
|
||||
opencode_constructor.assert_called_once_with(
|
||||
base_url="https://opencode.example",
|
||||
username=settings.opencode_server_username,
|
||||
password="password",
|
||||
schemas_dir=package_dir / "prompts" / "schemas",
|
||||
health_directory=settings.workspaces_dir,
|
||||
required_models=(
|
||||
(settings.plan_model, settings.plan_variant),
|
||||
(settings.implement_model, settings.implement_variant),
|
||||
(settings.explore_model, settings.explore_variant),
|
||||
(settings.research_model, settings.research_variant),
|
||||
),
|
||||
timeout_seconds=settings.turn_timeout_seconds,
|
||||
)
|
||||
development_constructor.assert_called_once_with(
|
||||
scripts=["python"],
|
||||
scripts_dir=settings.install_scripts_dir,
|
||||
tools_dir=settings.dev_tools_dir,
|
||||
timeout_seconds=settings.install_script_timeout_seconds,
|
||||
python_version=settings.python_version,
|
||||
dotnet_channel=settings.dotnet_channel,
|
||||
)
|
||||
prompt_constructor.assert_called_once_with()
|
||||
services_constructor.assert_called_once_with(
|
||||
settings=settings,
|
||||
repository=repository,
|
||||
gitea=gitea,
|
||||
git=git,
|
||||
opencode=opencode,
|
||||
prompts=prompts,
|
||||
development=development,
|
||||
)
|
||||
worker_constructor.assert_called_once_with(
|
||||
repository=repository,
|
||||
gitea=gitea,
|
||||
opencode=opencode,
|
||||
services=services,
|
||||
poll_seconds=settings.worker_poll_seconds,
|
||||
max_concurrent_jobs=4,
|
||||
workspaces_dir=settings.workspaces_dir,
|
||||
bot_username=settings.bot_username,
|
||||
)
|
||||
assert settings.data_dir.is_dir()
|
||||
assert settings.workspaces_dir.is_dir()
|
||||
assert built.repository is repository
|
||||
assert built.gitea is gitea
|
||||
assert built.git is git
|
||||
assert built.opencode is opencode
|
||||
assert built.worker is worker
|
||||
Reference in New Issue
Block a user