ForcePilot/backend/test/integration/api/external_systems/conftest.py
Kris 002e6a356b chore: 批量整理代码变更,修复多类细节问题
1.  清理测试文件中未使用的导入与冗余代码
2.  修复审计日志与批量操作的空值约束,统一填充"global"作为默认渠道
3.  调整批量消息撤回的响应语义,对齐其他端点的部分成功契约
4.  修复访问规则批量克隆的唯一约束问题,新增后缀自动处理逻辑
5.  替换anyio为asyncio并行调用,修正时间UTC导入路径
6.  优化前端外部系统概览页的刷新状态提示与缓存逻辑
7.  修复测试用例中的断言与请求方式问题,适配httpx删除请求特性
8.  重构后端路由的依赖注入,移除冗余的数据库会话依赖
9.  调整测试用例的权限校验逻辑,修正强制登出的权限判断
10. 修复语义分块测试的numpy依赖问题,清理冗余导入
2026-07-13 20:48:29 +08:00

205 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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 os
import uuid
from collections.abc import AsyncGenerator
import httpx
import pytest_asyncio
from sqlalchemy import text
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from sqlalchemy.pool import NullPool
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 db_engine():
"""提供独立于 pg_manager 的 async engine避免跨 event loop 连接池问题。
pg_manager 的连接池在 ``ensure_live_api_schema`` fixture 中通过
``anyio.run()`` 创建,绑定到临时 event loop。function-scope 的测试用例
运行在 pytest-asyncio 的 event loop 中,复用 pg_manager 会触发
"got Future attached to a different loop"。使用 ``NullPool`` 每次新建
连接,彻底规避连接复用跨 loop 问题。
"""
db_url = os.getenv("POSTGRES_URL", "postgresql+asyncpg://postgres:postgres@postgres:5432/yuxi")
engine = create_async_engine(db_url, poolclass=NullPool)
try:
yield engine
finally:
await engine.dispose()
@pytest_asyncio.fixture(scope="function")
async def metric_bucket(
external_system: dict,
external_environment: dict,
db_engine,
) -> AsyncGenerator[dict, None]:
"""创建一条指标桶数据,用于 by-system/by-env 聚合查询测试。
依赖 ``external_environment`` 以使 ``env_key`` 与环境记录一致,
从而验证 ``by-env`` 端点的 LEFT JOIN 能返回 ``env_name``。
"""
async_session = async_sessionmaker(db_engine, expire_on_commit=False)
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 async_session() 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 async_session() 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}")