ForcePilot/backend/test/unit/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

158 lines
4.9 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.

"""external_systems 单元测试共享 fixture。
本文件仅放置跨多个测试文件复用的 fixture 与工厂函数。单文件独有的 helper
应保留在对应测试文件内,避免过度集中(见 testing-guidelines.md
"""
from __future__ import annotations
from typing import Any
from unittest.mock import AsyncMock
import pytest
from yuxi.external_systems.core.models import (
Environment,
ExternalSystem,
ExternalTool,
)
# ─── 工厂函数 ─────────────────────────────────────────────────────────────
@pytest.fixture
def make_external_tool() -> type[ExternalTool]:
"""返回 ExternalTool 工厂类型,调用时按需覆盖字段。
用法::
tool = make_external_tool(slug="my-tool", adapter_type="http")
"""
def _build(**overrides: Any) -> ExternalTool:
defaults: dict[str, Any] = {
"slug": "test-tool",
"name": "Test Tool",
"description": "A tool for testing",
"adapter_type": "http",
"auth_type": "none",
"timeout": 30,
"enabled": True,
}
defaults.update(overrides)
return ExternalTool(**defaults)
return _build
@pytest.fixture
def make_external_system() -> type[ExternalSystem]:
"""返回 ExternalSystem 工厂类型,调用时按需覆盖字段。"""
def _build(**overrides: Any) -> ExternalSystem:
defaults: dict[str, Any] = {
"slug": "test-system",
"name": "Test System",
"description": "A system for testing",
"adapter_type": "http",
"auth_type": "none",
"enabled": True,
}
defaults.update(overrides)
return ExternalSystem(**defaults)
return _build
@pytest.fixture
def make_environment() -> type[Environment]:
"""返回 Environment 工厂类型。"""
def _build(**overrides: Any) -> Environment:
defaults: dict[str, Any] = {
"system_id": 1,
"env_key": "default",
"name": "Default Env",
"is_default": False,
"enabled": True,
}
defaults.update(overrides)
return Environment(**defaults)
return _build
# ─── Stub 端口实现 ─────────────────────────────────────────────────────────
class StubCryptoService:
"""内存版 CryptoService 桩,用于 webhook_verifier / secret_rotation_scheduler 等测试。
- encrypt_text / decrypt_text 互为逆操作(基于 base64
- mask 系列方法返回固定掩码占位。
"""
def __init__(self) -> None:
self._store: dict[str, str] = {}
def encrypt_text(self, plaintext: str) -> str:
import base64
encoded = base64.b64encode(plaintext.encode()).decode()
self._store[encoded] = plaintext
return encoded
def decrypt_text(self, ciphertext: str) -> str:
import base64
if ciphertext in self._store:
return self._store[ciphertext]
return base64.b64decode(ciphertext.encode()).decode()
def encrypt_sensitive_fields(self, data: dict[str, Any], sensitive_keys: set[str]) -> dict[str, Any]:
result = dict(data)
for key in sensitive_keys:
if key in result and isinstance(result[key], str):
result[key] = self.encrypt_text(result[key])
return result
def decrypt_sensitive_fields(self, data: dict[str, Any], sensitive_keys: set[str]) -> dict[str, Any]:
result = dict(data)
for key in sensitive_keys:
if key in result and isinstance(result[key], str):
result[key] = self.decrypt_text(result[key])
return result
def mask_sensitive_fields(self, data: dict[str, Any], sensitive_keys: set[str]) -> dict[str, Any]:
result = dict(data)
for key in sensitive_keys:
if key in result:
result[key] = "***"
return result
def mask_all_string_values(self, data: dict[str, Any]) -> dict[str, Any]:
return {k: "***" if isinstance(v, str) else v for k, v in data.items()}
@staticmethod
def is_sensitive_key(key: str) -> bool:
return key.lower() in {"password", "secret", "token", "api_key"}
@staticmethod
def sensitive_keywords() -> list[str]:
return ["password", "secret", "token", "api_key"]
@pytest.fixture
def stub_crypto() -> StubCryptoService:
"""提供内存版 CryptoService 桩。"""
return StubCryptoService()
@pytest.fixture
def stub_secret_resolver() -> AsyncMock:
"""提供 SecretResolver 异步桩resolve 返回原始引用值。"""
resolver = AsyncMock()
resolver.resolve = AsyncMock(side_effect=lambda ref: ref)
return resolver