初始化提交

This commit is contained in:
chenxin committed 2026-08-21 16:00:13 +08:00
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"])