101 lines
4.3 KiB
Python
101 lines
4.3 KiB
Python
from contextlib import asynccontextmanager
|
|
from types import SimpleNamespace
|
|
from unittest.mock import AsyncMock
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
|
|
from app.domain.domain_errors import DomainError
|
|
from app.domain.schemas.operational_task import AgentRunCommand
|
|
from app.infra.db.postgresql.operational_tasks import OperationalTaskRepository
|
|
from app.services.operational_tasks import OperationalTaskService
|
|
|
|
|
|
@pytest.fixture
|
|
def anyio_backend():
|
|
return "asyncio"
|
|
|
|
|
|
@pytest.mark.anyio
|
|
@pytest.mark.parametrize("available,agent_ownership_available,ready", [
|
|
(False, False, False), (True, False, False), (True, True, True),
|
|
])
|
|
async def test_execution_requires_message_and_agent_ownership_storage(
|
|
available, agent_ownership_available, ready,
|
|
):
|
|
cursor = SimpleNamespace(fetchone=AsyncMock(return_value={
|
|
"available": available,
|
|
"agent_ownership_available": agent_ownership_available,
|
|
}))
|
|
connection = SimpleNamespace(execute=AsyncMock(return_value=cursor))
|
|
repository = OperationalTaskRepository(connection, uuid4())
|
|
if ready:
|
|
await repository.require_execution_storage()
|
|
else:
|
|
with pytest.raises(DomainError) as failure:
|
|
await repository.require_execution_storage()
|
|
assert failure.value.status == 503
|
|
assert failure.value.code == "DEPENDENCY_UNAVAILABLE"
|
|
assert "024_agent_messages.sql" in failure.value.message
|
|
assert "030_operational_run_agent_ownership.sql" in failure.value.message
|
|
assert "to_regclass('workbench.agent_messages')" in connection.execute.call_args.args[0]
|
|
|
|
|
|
@pytest.mark.anyio
|
|
@pytest.mark.parametrize("available", [False, True])
|
|
async def test_run_update_uses_agent_owner_column_only_after_migration(available):
|
|
availability = SimpleNamespace(fetchone=AsyncMock(return_value={"available": available}))
|
|
updated = SimpleNamespace(rowcount=1)
|
|
connection = SimpleNamespace(execute=AsyncMock(side_effect=[availability, updated]))
|
|
repository = OperationalTaskRepository(connection, uuid4())
|
|
run_id = str(uuid4())
|
|
await repository.update_run({
|
|
"run_id": run_id, "status": "running", "revision": 2,
|
|
"agent_session_id": "session-1", "agent_instance_id": "agent-a",
|
|
"lease_expires_at": None, "heartbeat_at": None, "started_at": None,
|
|
"finished_at": None, "failure_code": None, "failure_detail": None,
|
|
"dispatch_attempts": 0, "next_dispatch_at": None, "updated_at": None,
|
|
}, 1)
|
|
|
|
query, params = connection.execute.call_args.args
|
|
assert ("agent_instance_id=%s" in query) is available
|
|
assert ("agent-a" in params) is available
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_missing_message_storage_rejects_claim_before_state_changes():
|
|
@asynccontextmanager
|
|
async def transaction():
|
|
yield
|
|
|
|
repository = SimpleNamespace(
|
|
transaction=transaction,
|
|
require_execution_storage=AsyncMock(side_effect=DomainError(
|
|
"DEPENDENCY_UNAVAILABLE", "Apply 024_agent_messages.sql", status=503,
|
|
)),
|
|
get_request=AsyncMock(return_value=None),
|
|
lock=AsyncMock(), update_run=AsyncMock(), update_task=AsyncMock(),
|
|
)
|
|
service = OperationalTaskService(repository, SimpleNamespace(), set())
|
|
with pytest.raises(DomainError, match="024_agent_messages.sql"):
|
|
await service.claim_run(uuid4(), uuid4(), AgentRunCommand(
|
|
request_id=uuid4(), expected_revision=1, source_session_id="test-session",
|
|
))
|
|
repository.get_request.assert_not_awaited()
|
|
repository.lock.assert_not_awaited()
|
|
repository.update_run.assert_not_awaited()
|
|
repository.update_task.assert_not_awaited()
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_presentation_history_storage_filters_project_task_and_committed_cursor():
|
|
project_id, task_id = uuid4(), uuid4()
|
|
connection = SimpleNamespace(execute=AsyncMock(return_value=SimpleNamespace(fetchall=AsyncMock(return_value=[]))))
|
|
repository = OperationalTaskRepository(connection, project_id)
|
|
assert await repository.list_presentation_events(str(task_id), 7, 30, 21) == []
|
|
query, params = connection.execute.call_args.args
|
|
assert params == (str(project_id), str(task_id), 7, 30, 21)
|
|
assert "project_id=%s" in query and "aggregate_id=%s" in query
|
|
assert "operational.presentation.requested" in query and "event_seq<=%s" in query
|
|
assert "ORDER BY event_seq LIMIT %s" in query
|