feat(ai): gRPC clients 扩展 + server interceptors + proto_gen + 测试 + nextstep 文档
This commit is contained in:
@@ -37,17 +37,24 @@ from src.ai.middleware.error_handler import (
|
||||
|
||||
@pytest.fixture
|
||||
async def client() -> AsyncGenerator[httpx.AsyncClient, None]:
|
||||
"""HTTP client wired to the FastAPI app (dev_mode=True, permissions skipped)."""
|
||||
from src.ai.main import _permission_guard, app
|
||||
"""HTTP client wired to the FastAPI app (dev_mode=True, permissions skipped).
|
||||
|
||||
注入 ContentClientMock 到 workflow_service,因为测试环境无真实 content gRPC server。
|
||||
"""
|
||||
from src.ai.clients import ContentClientMock
|
||||
from src.ai.main import _permission_guard, _workflow_service, app
|
||||
|
||||
original = _permission_guard._dev_mode
|
||||
original_content = _workflow_service._content_client # noqa: SLF001
|
||||
_permission_guard._dev_mode = True
|
||||
_workflow_service._content_client = ContentClientMock() # noqa: SLF001
|
||||
try:
|
||||
transport = ASGITransport(app=app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url="http://test") as c:
|
||||
yield c
|
||||
finally:
|
||||
_permission_guard._dev_mode = original
|
||||
_workflow_service._content_client = original_content # noqa: SLF001
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -136,14 +143,14 @@ async def test_handle_unknown_error() -> None:
|
||||
async def test_grpc_error_mapper_ai_error() -> None:
|
||||
"""grpc_error_mapper maps AIError to correct gRPC status code."""
|
||||
cases = [
|
||||
(ErrorCode.AI_UNAUTHORIZED, 8), # UNAUTHENTICATED
|
||||
(ErrorCode.AI_FORBIDDEN, 7), # PERMISSION_DENIED
|
||||
(ErrorCode.AI_RATE_LIMITED, 9), # RESOURCE_EXHAUSTED
|
||||
(ErrorCode.AI_QUOTA_EXCEEDED, 9), # RESOURCE_EXHAUSTED
|
||||
(ErrorCode.AI_INVALID_MODEL, 3), # INVALID_ARGUMENT
|
||||
(ErrorCode.AI_UNAUTHORIZED, 8), # UNAUTHENTICATED
|
||||
(ErrorCode.AI_FORBIDDEN, 7), # PERMISSION_DENIED
|
||||
(ErrorCode.AI_RATE_LIMITED, 9), # RESOURCE_EXHAUSTED
|
||||
(ErrorCode.AI_QUOTA_EXCEEDED, 9), # RESOURCE_EXHAUSTED
|
||||
(ErrorCode.AI_INVALID_MODEL, 3), # INVALID_ARGUMENT
|
||||
(ErrorCode.AI_WORKFLOW_NOT_FOUND, 5), # NOT_FOUND
|
||||
(ErrorCode.AI_WORKFLOW_STATE_INVALID, 10), # FAILED_PRECONDITION
|
||||
(ErrorCode.AI_INTERNAL_ERROR, 13), # INTERNAL
|
||||
(ErrorCode.AI_INTERNAL_ERROR, 13), # INTERNAL
|
||||
]
|
||||
for code, expected_grpc_status in cases:
|
||||
exc = AIError(code, f"test {code.value}")
|
||||
@@ -390,9 +397,7 @@ async def test_confirm_lesson_plan_success(client: httpx.AsyncClient) -> None:
|
||||
break
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
assert status == "pending_review", (
|
||||
f"Workflow did not reach pending_review, got: {status}"
|
||||
)
|
||||
assert status == "pending_review", f"Workflow did not reach pending_review, got: {status}"
|
||||
|
||||
resp = await client.post(f"/v1/ai/lesson-plan/confirm/{workflow_id}")
|
||||
assert resp.status_code == 200
|
||||
|
||||
Reference in New Issue
Block a user