初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1,134 @@
|
||||
"""Agent 循环工具确认 approve/deny 端点测试。
|
||||
|
||||
覆盖:
|
||||
- approve / deny 成功 resolve 记录(200, status=resolved)
|
||||
- 重复 resolve 返回 404(记录已消费)
|
||||
- 记录不存在的场景 → 404
|
||||
- registry 未挂载 → 503
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import AsyncGenerator
|
||||
|
||||
import pytest
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.application.agent.confirmation import ConfirmationRegistry
|
||||
from app.db.session import AsyncSessionLocal
|
||||
from app.main import app
|
||||
from app.models.safety import PendingConfirmationRecord
|
||||
|
||||
_RUN_ID = "run_test_confirmation_0001"
|
||||
_CONFIRM = "confirm_test_confirmation_0001"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def client(auth_headers: dict[str, str]) -> AsyncGenerator[AsyncClient, None]:
|
||||
"""挂载 ConfirmationRegistry 到 app.state。"""
|
||||
registry = ConfirmationRegistry()
|
||||
app.state.confirmation_registry = registry
|
||||
transport = ASGITransport(app=app)
|
||||
try:
|
||||
async with AsyncClient(
|
||||
transport=transport, base_url="http://test", headers=auth_headers
|
||||
) as http_client:
|
||||
yield http_client
|
||||
finally:
|
||||
app.state.confirmation_registry = ConfirmationRegistry()
|
||||
|
||||
|
||||
async def _seed_pending_record() -> None:
|
||||
"""在 DB 中插入一条待确认记录。"""
|
||||
async with AsyncSessionLocal() as session:
|
||||
record = PendingConfirmationRecord(
|
||||
id=_CONFIRM,
|
||||
tool="shell",
|
||||
input_json='{"cmd": "echo hi"}',
|
||||
risk_level="L3",
|
||||
reason="高风险命令需确认",
|
||||
run_id=_RUN_ID,
|
||||
character_id="character_test",
|
||||
status="pending",
|
||||
)
|
||||
session.add(record)
|
||||
await session.commit()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_approve_resolves_future(client: AsyncClient) -> None:
|
||||
await _seed_pending_record()
|
||||
|
||||
resp = await client.post(f"/api/v1/runs/{_RUN_ID}/confirmations/{_CONFIRM}/approve")
|
||||
assert resp.status_code == 200
|
||||
assert resp.json() == {"approved": True, "status": "resolved"}
|
||||
|
||||
# 验证 DB 状态已更新
|
||||
async with AsyncSessionLocal() as session:
|
||||
record = (
|
||||
await session.execute(
|
||||
select(PendingConfirmationRecord).where(PendingConfirmationRecord.id == _CONFIRM)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
assert record is not None
|
||||
assert record.status == "approved"
|
||||
assert record.resolved_at is not None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_deny_resolves_future(client: AsyncClient) -> None:
|
||||
await _seed_pending_record()
|
||||
|
||||
resp = await client.post(f"/api/v1/runs/{_RUN_ID}/confirmations/{_CONFIRM}/deny")
|
||||
assert resp.status_code == 200
|
||||
assert resp.json() == {"approved": False, "status": "resolved"}
|
||||
|
||||
async with AsyncSessionLocal() as session:
|
||||
record = (
|
||||
await session.execute(
|
||||
select(PendingConfirmationRecord).where(PendingConfirmationRecord.id == _CONFIRM)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
assert record is not None
|
||||
assert record.status == "denied"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_idempotent_second_call_returns_404(client: AsyncClient) -> None:
|
||||
"""重复 approve:第一次成功,第二次返回 404(记录已 resolve)。"""
|
||||
await _seed_pending_record()
|
||||
|
||||
first = await client.post(f"/api/v1/runs/{_RUN_ID}/confirmations/{_CONFIRM}/approve")
|
||||
assert first.status_code == 200
|
||||
|
||||
second = await client.post(f"/api/v1/runs/{_RUN_ID}/confirmations/{_CONFIRM}/approve")
|
||||
assert second.status_code == 404
|
||||
assert second.json()["error"]["code"] == "confirmation_not_found"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_missing_confirmation_returns_404(client: AsyncClient) -> None:
|
||||
"""确认项不存在 → 404。"""
|
||||
resp = await client.post(f"/api/v1/runs/{_RUN_ID}/confirmations/{_CONFIRM}/deny")
|
||||
assert resp.status_code == 404
|
||||
assert resp.json()["error"]["code"] == "confirmation_not_found"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_registry_not_configured_returns_503() -> None:
|
||||
app.state.confirmation_registry = None
|
||||
try:
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as http_client:
|
||||
resp = await http_client.post(
|
||||
f"/api/v1/runs/{_RUN_ID}/confirmations/{_CONFIRM}/approve"
|
||||
)
|
||||
assert resp.status_code == 503
|
||||
assert resp.json()["error"]["code"] == "service_unavailable"
|
||||
finally:
|
||||
app.state.confirmation_registry = ConfirmationRegistry()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
Reference in new issue
Block a user