ForcePilot/backend/test/integration/api/external_systems/conftest.py
Kris 824471454b test: add complete unit and integration test suite for external_systems module
新增覆盖认证插件、运行时工具、数据库适配器、API路由、业务用例的完整单元与集成测试,包含:
1. 各认证插件的属性校验与异常场景测试
2. 运行时工具类的格式、唯一性与边界测试
3. gRPC/HTTP错误映射器测试
4. 通知解析、枚举类单元测试
5. 回收站、仪表盘API集成测试
6. 全局共享fixture与桩实现
2026-06-20 22:17:01 +08:00

185 lines
6.0 KiB
Python

"""Shared pytest fixtures for HTTP integration tests of the external_systems routers.
Provides function-scoped fixtures that create (and clean up) minimal external
systems, tools, and environments via the live API under
``/api/system/external-systems``. Parent conftest supplies ``test_client``,
``admin_headers`` and ``standard_user``; do not redefine them here.
"""
from __future__ import annotations
import uuid
from collections.abc import AsyncGenerator
import httpx
import pytest_asyncio
from sqlalchemy import text
from yuxi.storage.postgres.manager import pg_manager
from yuxi.storage.postgres.models_external import ExternalSystemMetric
from yuxi.utils.datetime_utils import utc_now_naive
BASE_URL = "/api/system/external-systems"
@pytest_asyncio.fixture(scope="function")
async def external_system(
test_client: httpx.AsyncClient,
admin_headers: dict[str, str],
) -> AsyncGenerator[dict, None]:
suffix = uuid.uuid4().hex[:8]
slug = f"pytest_sys_{suffix}"
name = f"Pytest System {suffix}"
response = await test_client.post(
f"{BASE_URL}/systems",
json={
"slug": slug,
"name": name,
"adapter_type": "http",
"auth_type": "none",
"enabled": True,
},
headers=admin_headers,
)
assert response.status_code == 200, f"Failed to create external system: {response.text}"
system = response.json()["data"]
try:
yield system
finally:
try:
delete_response = await test_client.delete(
f"{BASE_URL}/systems/{system['id']}",
headers=admin_headers,
)
if delete_response.status_code not in (200, 404):
print(f"Warning: Failed to cleanup external system {system['id']}: {delete_response.text}")
except Exception as exc:
print(f"Warning: Exception during cleanup of external system {system['id']}: {exc}")
@pytest_asyncio.fixture(scope="function")
async def external_tool(
test_client: httpx.AsyncClient,
admin_headers: dict[str, str],
external_system: dict,
) -> AsyncGenerator[dict, None]:
suffix = uuid.uuid4().hex[:8]
slug = f"pytest_tool_{suffix}"
name = f"Pytest Tool {suffix}"
response = await test_client.post(
f"{BASE_URL}/tools",
json={
"slug": slug,
"name": name,
"adapter_type": "http",
"auth_type": "none",
"enabled": True,
"system_id": external_system["id"],
},
headers=admin_headers,
)
assert response.status_code == 200, f"Failed to create external tool: {response.text}"
tool = response.json()["data"]
try:
yield tool
finally:
try:
delete_response = await test_client.delete(
f"{BASE_URL}/tools/{tool['id']}",
headers=admin_headers,
)
if delete_response.status_code not in (200, 404):
print(f"Warning: Failed to cleanup external tool {tool['id']}: {delete_response.text}")
except Exception as exc:
print(f"Warning: Exception during cleanup of external tool {tool['id']}: {exc}")
@pytest_asyncio.fixture(scope="function")
async def external_environment(
test_client: httpx.AsyncClient,
admin_headers: dict[str, str],
external_system: dict,
) -> AsyncGenerator[dict, None]:
suffix = uuid.uuid4().hex[:8]
env_key = f"env_{suffix}"
name = f"Pytest Env {suffix}"
response = await test_client.post(
f"{BASE_URL}/environments",
json={
"system_id": external_system["id"],
"env_key": env_key,
"name": name,
"enabled": True,
},
headers=admin_headers,
)
assert response.status_code == 200, f"Failed to create external environment: {response.text}"
environment = response.json()["data"]
try:
yield environment
finally:
try:
delete_response = await test_client.delete(
f"{BASE_URL}/environments/{environment['id']}",
params={"system_id": external_system["id"]},
headers=admin_headers,
)
if delete_response.status_code not in (200, 404):
print(f"Warning: Failed to cleanup external environment {environment['id']}: {delete_response.text}")
except Exception as exc:
print(f"Warning: Exception during cleanup of external environment {environment['id']}: {exc}")
@pytest_asyncio.fixture(scope="function")
async def metric_bucket(
external_system: dict,
external_environment: dict,
) -> AsyncGenerator[dict, None]:
"""创建一条指标桶数据,用于 by-system/by-env 聚合查询测试。
依赖 ``external_environment`` 以使 ``env_key`` 与环境记录一致,
从而验证 ``by-env`` 端点的 LEFT JOIN 能返回 ``env_name``。
"""
if not pg_manager._initialized:
pg_manager.initialize()
metric = ExternalSystemMetric(
system_id=external_system["id"],
env_key=external_environment["env_key"],
bucket_at=utc_now_naive(),
total_calls=100,
success_calls=90,
failed_calls=10,
latency_sum_ms=5000,
latency_count=100,
)
async with pg_manager.get_async_session_context() as db:
db.add(metric)
await db.commit()
await db.refresh(metric)
try:
yield {
"id": metric.id,
"system_id": external_system["id"],
"env_key": external_environment["env_key"],
"system_name": external_system["name"],
"env_name": external_environment["name"],
}
finally:
try:
async with pg_manager.get_async_session_context() as db:
await db.execute(
text("DELETE FROM ext_system_metrics WHERE id = :id"),
{"id": metric.id},
)
await db.commit()
except Exception as exc:
print(f"Warning: Exception during cleanup of metric bucket {metric.id}: {exc}")