reorg files

This commit is contained in:
2026-07-22 23:49:44 +02:00
parent 98ac4abca1
commit 0526406472
69 changed files with 760 additions and 539 deletions
+8 -7
View File
@@ -4,7 +4,8 @@ from types import SimpleNamespace
import pytest
from httpx import ASGITransport, AsyncClient
import agentci.app as app_module
import agentci.api.app as app_module
import agentci.api.lifespan as lifespan_module
class BlockingWorker:
@@ -56,8 +57,8 @@ async def test_lifespan_starts_worker_cancels_it_and_closes_runtime(
built_with.append(settings)
return runtime
monkeypatch.setattr(app_module, "build_runtime", build)
monkeypatch.setattr(app_module, "configure_logging", lambda: configured.append(True))
monkeypatch.setattr(lifespan_module, "build_runtime", build)
monkeypatch.setattr(lifespan_module, "configure_logging", lambda: configured.append(True))
application = app_module.create_app(selected_settings) # type: ignore[arg-type]
async with application.router.lifespan_context(application):
@@ -80,8 +81,8 @@ async def test_lifespan_closes_runtime_when_worker_task_fails(
async def build(_settings: object) -> FakeRuntime:
return runtime
monkeypatch.setattr(app_module, "build_runtime", build)
monkeypatch.setattr(app_module, "configure_logging", lambda: None)
monkeypatch.setattr(lifespan_module, "build_runtime", build)
monkeypatch.setattr(lifespan_module, "configure_logging", lambda: None)
application = app_module.create_app(SimpleNamespace()) # type: ignore[arg-type]
with pytest.raises(RuntimeError, match="worker failed"):
@@ -100,8 +101,8 @@ async def test_lifespan_propagates_runtime_startup_failure_without_starting_work
async def fail_build(_settings: object) -> None:
raise RuntimeError("database unavailable")
monkeypatch.setattr(app_module, "build_runtime", fail_build)
monkeypatch.setattr(app_module, "configure_logging", lambda: configured.append(True))
monkeypatch.setattr(lifespan_module, "build_runtime", fail_build)
monkeypatch.setattr(lifespan_module, "configure_logging", lambda: configured.append(True))
application = app_module.create_app(SimpleNamespace()) # type: ignore[arg-type]
with pytest.raises(RuntimeError, match="database unavailable"):
+6 -6
View File
@@ -2,7 +2,7 @@ from pathlib import Path
import pytest
from agentci.codegraph import CodeGraph, CodeGraphError
from agentci.integrations.codegraph import CodeGraph, CodeGraphError
class FakeProcess:
@@ -27,7 +27,7 @@ async def test_initializes_incomplete_index_and_excludes_it_from_git(
return FakeProcess()
monkeypatch.setattr(
"agentci.codegraph.asyncio.create_subprocess_exec",
"agentci.integrations.codegraph.asyncio.create_subprocess_exec",
create_subprocess_exec,
)
@@ -53,7 +53,7 @@ async def test_syncs_an_existing_index_without_duplicating_exclude(
return FakeProcess()
monkeypatch.setattr(
"agentci.codegraph.asyncio.create_subprocess_exec",
"agentci.integrations.codegraph.asyncio.create_subprocess_exec",
create_subprocess_exec,
)
@@ -86,7 +86,7 @@ async def test_handles_exclude_file_boundaries(
return FakeProcess()
monkeypatch.setattr(
"agentci.codegraph.asyncio.create_subprocess_exec",
"agentci.integrations.codegraph.asyncio.create_subprocess_exec",
create_subprocess_exec,
)
@@ -103,7 +103,7 @@ async def test_reports_missing_executable(tmp_path: Path, monkeypatch: pytest.Mo
raise FileNotFoundError(2, "No such file or directory", "codegraph")
monkeypatch.setattr(
"agentci.codegraph.asyncio.create_subprocess_exec",
"agentci.integrations.codegraph.asyncio.create_subprocess_exec",
create_subprocess_exec,
)
@@ -125,7 +125,7 @@ async def test_reports_nonzero_exit_with_bounded_non_utf8_stderr(
return FakeProcess(returncode=7, stderr=stderr)
monkeypatch.setattr(
"agentci.codegraph.asyncio.create_subprocess_exec",
"agentci.integrations.codegraph.asyncio.create_subprocess_exec",
create_subprocess_exec,
)
+1 -1
View File
@@ -4,7 +4,7 @@ from pathlib import Path
import pytest
from pydantic import ValidationError
from agentci.config import Settings
from agentci.config.settings import Settings
@pytest.fixture(autouse=True)
+2 -1
View File
@@ -1,7 +1,8 @@
from typing import cast
from agentci.engine.repository import Repository
from agentci.gitea import CommentInfo, Gitea, IssueInfo, PullRequestInfo
from agentci.integrations.gitea.client import Gitea
from agentci.integrations.gitea.models import CommentInfo, IssueInfo, PullRequestInfo
from agentci.workflows.context import build_issue_context, build_pull_request_context
+2 -2
View File
@@ -3,7 +3,7 @@ from pathlib import Path
import pytest
from agentci.development import (
from agentci.integrations.development import (
DevelopmentEnvironment,
DevelopmentEnvironmentError,
)
@@ -169,7 +169,7 @@ async def test_wraps_subprocess_start_error(
raise OSError("exec unavailable")
monkeypatch.setattr(
"agentci.development.asyncio.create_subprocess_exec",
"agentci.integrations.development.asyncio.create_subprocess_exec",
create_subprocess_exec,
)
+1 -1
View File
@@ -5,7 +5,7 @@ from typing import Any
import pytest
from agentci.git import Git, GitError
from agentci.integrations.git import Git, GitError
class FakeProcess:
+9 -8
View File
@@ -1,17 +1,18 @@
import json
from collections.abc import AsyncIterator, Callable, Coroutine
from collections.abc import AsyncGenerator, Callable, Coroutine
from contextlib import asynccontextmanager
import httpx
import pytest
from agentci.gitea import CommentInfo, Gitea, GiteaError, IssueInfo, PullRequestInfo
from agentci.integrations.gitea.client import Gitea, GiteaError
from agentci.integrations.gitea.models import CommentInfo, IssueInfo, PullRequestInfo
Handler = Callable[[httpx.Request], Coroutine[None, None, httpx.Response]]
@asynccontextmanager
async def gitea_client(handler: Handler, *, retries: int = 3) -> AsyncIterator[Gitea]:
async def gitea_client(handler: Handler, *, retries: int = 3) -> AsyncGenerator[Gitea]:
client = Gitea(
"https://gitea.example/",
"secret",
@@ -232,7 +233,7 @@ async def test_nonretryable_status_fails_once(status: int, monkeypatch: pytest.M
async def sleep(delay: int) -> None:
sleeps.append(delay)
monkeypatch.setattr("agentci.gitea.asyncio.sleep", sleep)
monkeypatch.setattr("agentci.integrations.gitea.client.asyncio.sleep", sleep)
async with gitea_client(handler) as client:
with pytest.raises(
GiteaError,
@@ -261,7 +262,7 @@ async def test_retryable_status_recovers_after_backoff(
async def sleep(delay: int) -> None:
sleeps.append(delay)
monkeypatch.setattr("agentci.gitea.asyncio.sleep", sleep)
monkeypatch.setattr("agentci.integrations.gitea.client.asyncio.sleep", sleep)
async with gitea_client(handler) as client:
assert await client.default_branch("org", "repo") == "main"
@@ -283,7 +284,7 @@ async def test_retryable_status_exhaustion_uses_exponential_backoff(
async def sleep(delay: int) -> None:
sleeps.append(delay)
monkeypatch.setattr("agentci.gitea.asyncio.sleep", sleep)
monkeypatch.setattr("agentci.integrations.gitea.client.asyncio.sleep", sleep)
async with gitea_client(handler) as client:
with pytest.raises(
GiteaError,
@@ -309,7 +310,7 @@ async def test_transport_failure_retries_and_recovers(monkeypatch: pytest.Monkey
async def sleep(delay: int) -> None:
sleeps.append(delay)
monkeypatch.setattr("agentci.gitea.asyncio.sleep", sleep)
monkeypatch.setattr("agentci.integrations.gitea.client.asyncio.sleep", sleep)
async with gitea_client(handler) as client:
assert await client.default_branch("org", "repo") == "main"
@@ -331,7 +332,7 @@ async def test_transport_failure_exhaustion_preserves_cause(
async def sleep(delay: int) -> None:
sleeps.append(delay)
monkeypatch.setattr("agentci.gitea.asyncio.sleep", sleep)
monkeypatch.setattr("agentci.integrations.gitea.client.asyncio.sleep", sleep)
async with gitea_client(handler, retries=2) as client:
with pytest.raises(
GiteaError,
+1 -1
View File
@@ -4,7 +4,7 @@ import pytest
from fastapi import FastAPI
from httpx import ASGITransport, AsyncClient, Response
from agentci.health import router
from agentci.api.routes.health import router
class Provider:
+1 -1
View File
@@ -6,7 +6,7 @@ from datetime import UTC, datetime
import pytest
from agentci.logging import JsonFormatter, configure_logging
from agentci.observability.logging import JsonFormatter, configure_logging
@pytest.fixture(autouse=True)
+1 -1
View File
@@ -5,7 +5,7 @@ from pathlib import Path
import httpx
import pytest
from agentci.opencode import OpenCode, OpenCodeError
from agentci.integrations.opencode.client import OpenCode, OpenCodeError
from agentci.workflows.model import AgentResult
API_DOCUMENT = {
+1 -1
View File
@@ -3,7 +3,7 @@ from pathlib import Path
import httpx
import pytest
from agentci.opencode import OpenCode, OpenCodeError
from agentci.integrations.opencode.client import OpenCode, OpenCodeError
def client(tmp_path: Path, status: int) -> OpenCode:
+1 -1
View File
@@ -1,6 +1,6 @@
import pytest
from agentci.opencode import api_contract_ready, models_ready
from agentci.integrations.opencode.readiness import api_contract_ready, models_ready
def test_api_contract_requires_session_message_and_abort_routes() -> None:
+2 -2
View File
@@ -5,8 +5,8 @@ import pytest
from pydantic import BaseModel
import agentci.prompts
from agentci.opencode import load_schema
from agentci.prompts import PromptLibrary
from agentci.integrations.opencode.schemas import load_schema
from agentci.prompts.library import PromptLibrary
from agentci.workflows.model import (
AgentResult,
DiscussionReply,
+6 -10
View File
@@ -4,9 +4,9 @@ from unittest.mock import AsyncMock, Mock
import pytest
import agentci.runtime as runtime_module
from agentci.config import Settings
from agentci.runtime import Runtime
import agentci.application.runtime as runtime_module
from agentci.application.runtime import Runtime
from agentci.config.settings import Settings
class ClosingClient:
@@ -90,7 +90,7 @@ async def test_build_runtime_wires_components_without_starting_real_clients(
git = SimpleNamespace()
opencode = SimpleNamespace()
development = SimpleNamespace()
prompts = SimpleNamespace()
prompts = SimpleNamespace(schemas_dir=tmp_path / "schemas")
services = SimpleNamespace()
worker = SimpleNamespace()
repository_constructor = Mock(return_value=repository)
@@ -112,11 +112,7 @@ async def test_build_runtime_wires_components_without_starting_real_clients(
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"
)
repository_constructor.assert_called_once_with(settings.database_path)
initialize.assert_awaited_once_with()
gitea_constructor.assert_called_once_with("https://gitea.example", "token")
git_constructor.assert_called_once_with(
@@ -131,7 +127,7 @@ async def test_build_runtime_wires_components_without_starting_real_clients(
base_url="https://opencode.example",
username=settings.opencode_server_username,
password="password",
schemas_dir=package_dir / "prompts" / "schemas",
schemas_dir=prompts.schemas_dir,
health_directory=settings.workspaces_dir,
required_models=(
(settings.plan_model, settings.plan_variant),
+6 -2
View File
@@ -7,8 +7,12 @@ import pytest
from fastapi import FastAPI
from httpx import ASGITransport, AsyncClient, Response
from agentci.api.routes.webhook import router
from agentci.engine.model import IncomingCommand
from agentci.webhook import _event_from_payload, router, valid_signature
from agentci.integrations.gitea.webhooks import (
incoming_command_from_payload,
valid_signature,
)
class FakeRepository:
@@ -167,7 +171,7 @@ def test_payload_parser_supports_owner_username_and_pull_request() -> None:
value = payload("/agent plan", is_pull=True)
value["repository"]["owner"] = {"username": "fallback-owner"}
event = _event_from_payload("delivery", value)
event = incoming_command_from_payload("delivery", value)
assert event is not None
assert event.repo_owner == "fallback-owner"
+8 -7
View File
@@ -5,6 +5,8 @@ from types import SimpleNamespace
import pytest
from agentci.application.worker.errors import safe_error
from agentci.application.worker.runner import Worker
from agentci.engine.events import (
CommentLinked,
JobCompleted,
@@ -30,8 +32,7 @@ from agentci.engine.model import (
)
from agentci.engine.reducer import render_job_comment
from agentci.engine.repository import Repository
from agentci.gitea import CommentInfo
from agentci.worker import Worker, _safe_error
from agentci.integrations.gitea.models import CommentInfo
from agentci.workflows.render import JobRejected
MIGRATIONS = Path(__file__).parents[1] / "src" / "agentci" / "migrations"
@@ -408,7 +409,7 @@ async def test_execute_completes_with_workflow_comment(
async def dispatch(_job: Job, _run: object, _services: object) -> str:
return "final workflow body"
monkeypatch.setattr("agentci.worker.dispatch", dispatch)
monkeypatch.setattr("agentci.application.worker.execution.dispatch", dispatch)
execute = task()
await value._execute(execute, current)
@@ -432,7 +433,7 @@ async def test_execute_records_expected_rejection(
async def dispatch(_job: Job, _run: object, _services: object) -> str:
raise JobRejected("pull request is closed")
monkeypatch.setattr("agentci.worker.dispatch", dispatch)
monkeypatch.setattr("agentci.application.worker.execution.dispatch", dispatch)
await make_worker(tmp_path, repository)._execute(task(), current)
@@ -452,7 +453,7 @@ async def test_execute_records_failure_at_latest_persisted_stage(
repository.job = replace(repository.job, stage="cloning")
raise RuntimeError("provider\nfailed")
monkeypatch.setattr("agentci.worker.dispatch", dispatch)
monkeypatch.setattr("agentci.application.worker.execution.dispatch", dispatch)
await make_worker(tmp_path, repository)._execute(task(), current)
@@ -471,7 +472,7 @@ async def test_execute_marks_running_job_interrupted_after_restart(
async def unexpected_dispatch(_job: Job, _run: object, _services: object) -> str:
pytest.fail("running jobs must not be dispatched again")
monkeypatch.setattr("agentci.worker.dispatch", unexpected_dispatch)
monkeypatch.setattr("agentci.application.worker.execution.dispatch", unexpected_dispatch)
execute = task()
await make_worker(tmp_path, repository)._execute(execute, current)
@@ -642,7 +643,7 @@ async def test_abort_without_persisted_sessions_is_noop(tmp_path: Path) -> None:
def test_safe_error_is_single_line_and_bounded() -> None:
value = _safe_error(RuntimeError("bad\n" + "x" * 2000))
value = safe_error(RuntimeError("bad\n" + "x" * 2000))
assert "\n" not in value
assert len(value) == 1000
+1 -1
View File
@@ -7,7 +7,7 @@ import pytest
from agentci.engine.model import Job, JobKind, Workflow, WorkflowKind, WorkflowStatus
from agentci.engine.run import JobRun
from agentci.gitea import CommentInfo, IssueInfo, PullRequestInfo
from agentci.integrations.gitea.models import CommentInfo, IssueInfo, PullRequestInfo
from agentci.workflows.implementation import implement
from agentci.workflows.model import AgentResult, ReviewReport
from agentci.workflows.render import JobRejected
+1 -1
View File
@@ -8,7 +8,7 @@ from pydantic import BaseModel
from agentci.engine.model import Job, JobKind, Workflow, WorkflowKind, WorkflowStatus
from agentci.engine.run import JobRun
from agentci.gitea import CommentInfo, IssueInfo, PullRequestInfo
from agentci.integrations.gitea.models import CommentInfo, IssueInfo, PullRequestInfo
from agentci.workflows.model import DiscussionReply, PlanArtifact, ReviewReport
from agentci.workflows.plan import create_plan, discuss_plan, iterate_plan
from agentci.workflows.render import JobRejected
+1 -1
View File
@@ -8,7 +8,7 @@ from pydantic import BaseModel
from agentci.engine.model import Job, JobKind, Workflow, WorkflowKind, WorkflowStatus
from agentci.engine.run import JobRun
from agentci.gitea import CommentInfo, IssueInfo, PullRequestInfo
from agentci.integrations.gitea.models import CommentInfo, IssueInfo, PullRequestInfo
from agentci.workflows.model import AgentResult, ReviewReport
from agentci.workflows.pull_request import fix_pull_request, iterate_implementation
from agentci.workflows.render import JobRejected