rewrite phase 1

This commit is contained in:
2026-07-22 23:10:23 +02:00
parent 7527831af6
commit 98ac4abca1
89 changed files with 9179 additions and 2795 deletions
+178
View File
@@ -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