新增覆盖认证插件、运行时工具、数据库适配器、API路由、业务用例的完整单元与集成测试,包含: 1. 各认证插件的属性校验与异常场景测试 2. 运行时工具类的格式、唯一性与边界测试 3. gRPC/HTTP错误映射器测试 4. 通知解析、枚举类单元测试 5. 回收站、仪表盘API集成测试 6. 全局共享fixture与桩实现
185 lines
6.0 KiB
Python
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}")
|