新增完善测试模板
This commit is contained in:
142
tests/adapter_test_utils.py
Normal file
142
tests/adapter_test_utils.py
Normal file
@@ -0,0 +1,142 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
企业技能对接外部系统(ERP / TMS / WMS / 船司 / 报关 / 邮件 / 浏览器 / LLM / RPA)时,
|
||||
测试侧共用的 **profile / 授权 / 禁止真实外呼** 工具。
|
||||
|
||||
- 仅标准库;不发起 HTTP;不启动浏览器。
|
||||
- 真实 API / RPA 契约测试请放在 ``tests/integration/*.sample``,并配合环境变量显式授权。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any, Final
|
||||
|
||||
# 允许的 OPENCLAW_TEST_TARGET / OPENCLOW_TEST_TARGET 取值(后者为历史拼写兼容)
|
||||
ALLOWED_TEST_TARGETS: Final[frozenset[str]] = frozenset(
|
||||
{
|
||||
"unit",
|
||||
"mock",
|
||||
"simulator_api",
|
||||
"simulator_rpa",
|
||||
"real_api",
|
||||
"real_rpa",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _raw_test_target_env() -> str:
|
||||
return (os.environ.get("OPENCLAW_TEST_TARGET") or os.environ.get("OPENCLOW_TEST_TARGET") or "").strip()
|
||||
|
||||
|
||||
def get_test_target() -> str:
|
||||
"""
|
||||
读取 ``OPENCLAW_TEST_TARGET`` 或 ``OPENCLOW_TEST_TARGET``(兼容旧拼写)。
|
||||
|
||||
未设置时返回 ``unit``。非法取值抛出 ``ValueError``,便于尽早发现拼写错误。
|
||||
"""
|
||||
raw = _raw_test_target_env()
|
||||
if not raw:
|
||||
return "unit"
|
||||
if raw not in ALLOWED_TEST_TARGETS:
|
||||
raise ValueError(
|
||||
f"Invalid test target {raw!r}. Allowed: {sorted(ALLOWED_TEST_TARGETS)}"
|
||||
)
|
||||
return raw
|
||||
|
||||
|
||||
def real_access_allowed(profile: str) -> bool:
|
||||
"""
|
||||
判断当前环境是否允许对 ``profile`` 做「真实通道」访问(仍不代替业务鉴权)。
|
||||
|
||||
- ``real_api``:需要 ``ALLOW_REAL_API == "1"``。
|
||||
- ``real_rpa``:需要 ``ALLOW_REAL_RPA == "1"``。
|
||||
- 写操作另需 ``ALLOW_WRITE_ACTIONS == "1"``(由 ``should_skip_profile(..., write=True)`` 组合检查)。
|
||||
"""
|
||||
if profile == "real_api":
|
||||
return os.environ.get("ALLOW_REAL_API", "") == "1"
|
||||
if profile == "real_rpa":
|
||||
return os.environ.get("ALLOW_REAL_RPA", "") == "1"
|
||||
if profile in ("mock", "simulator_api", "simulator_rpa"):
|
||||
return True
|
||||
raise ValueError(f"unknown profile for real_access_allowed: {profile!r}")
|
||||
|
||||
|
||||
def should_skip_profile(profile: str, write: bool = False) -> tuple[bool, str]:
|
||||
"""
|
||||
返回 ``(should_skip, reason)``。
|
||||
|
||||
策略摘要:
|
||||
- ``mock``:永不跳过(内存 / FakeAdapter,无外呼)。
|
||||
- ``simulator_api``:仅当 ``get_test_target() == "simulator_api"`` 时不跳过。
|
||||
- ``simulator_rpa``:仅当 target 为 ``simulator_rpa`` 时不跳过。
|
||||
- ``real_api``:target 须为 ``real_api`` 且 ``ALLOW_REAL_API=1``;``write=True`` 还须 ``ALLOW_WRITE_ACTIONS=1``。
|
||||
- ``real_rpa``:target 须为 ``real_rpa`` 且 ``ALLOW_REAL_RPA=1``;``write=True`` 还须 ``ALLOW_WRITE_ACTIONS=1``。
|
||||
"""
|
||||
target = get_test_target()
|
||||
|
||||
if profile == "mock":
|
||||
return (False, "")
|
||||
|
||||
if profile == "simulator_api":
|
||||
if target != "simulator_api":
|
||||
return (True, f"OPENCLAW_TEST_TARGET must be simulator_api (got {target!r})")
|
||||
return (False, "")
|
||||
|
||||
if profile == "simulator_rpa":
|
||||
if target != "simulator_rpa":
|
||||
return (True, f"OPENCLAW_TEST_TARGET must be simulator_rpa (got {target!r})")
|
||||
return (False, "")
|
||||
|
||||
if profile == "real_api":
|
||||
if target != "real_api":
|
||||
return (True, f"OPENCLAW_TEST_TARGET must be real_api (got {target!r})")
|
||||
if os.environ.get("ALLOW_REAL_API", "") != "1":
|
||||
return (True, "ALLOW_REAL_API is not 1")
|
||||
if write and os.environ.get("ALLOW_WRITE_ACTIONS", "") != "1":
|
||||
return (True, "ALLOW_WRITE_ACTIONS is not 1 (write requested)")
|
||||
return (False, "")
|
||||
|
||||
if profile == "real_rpa":
|
||||
if target != "real_rpa":
|
||||
return (True, f"OPENCLAW_TEST_TARGET must be real_rpa (got {target!r})")
|
||||
if os.environ.get("ALLOW_REAL_RPA", "") != "1":
|
||||
return (True, "ALLOW_REAL_RPA is not 1")
|
||||
if write and os.environ.get("ALLOW_WRITE_ACTIONS", "") != "1":
|
||||
return (True, "ALLOW_WRITE_ACTIONS is not 1 (write requested)")
|
||||
return (False, "")
|
||||
|
||||
raise ValueError(f"unknown profile: {profile!r}")
|
||||
|
||||
|
||||
class FakeAdapter:
|
||||
"""
|
||||
模拟外部系统 adapter,用于单元测试中的超时 / 鉴权 / 畸形响应等分支。
|
||||
|
||||
``mode``:``success`` | ``timeout`` | ``invalid_response`` | ``unauthorized``
|
||||
"""
|
||||
|
||||
def __init__(self, mode: str = "success") -> None:
|
||||
if mode not in ("success", "timeout", "invalid_response", "unauthorized"):
|
||||
raise ValueError(f"unsupported FakeAdapter mode: {mode!r}")
|
||||
self._mode = mode
|
||||
|
||||
def call(self, payload: Any) -> Any:
|
||||
if self._mode == "success":
|
||||
return {"success": True, "data": {"echo": payload}}
|
||||
if self._mode == "timeout":
|
||||
raise TimeoutError("simulated upstream timeout")
|
||||
if self._mode == "invalid_response":
|
||||
return object() # 非 dict,模拟无法解析的响应体
|
||||
if self._mode == "unauthorized":
|
||||
raise PermissionError("simulated 401/403")
|
||||
raise RuntimeError("unreachable")
|
||||
|
||||
|
||||
def assert_no_real_network_env() -> None:
|
||||
"""
|
||||
断言当前进程未显式打开「真实外呼」授权开关(默认安全 CI / 本地开发)。
|
||||
|
||||
若需在受控环境跑真实集成,请显式设置 ``ALLOW_REAL_*`` 并使用 ``tests/integration/*.sample``。
|
||||
"""
|
||||
assert os.environ.get("ALLOW_REAL_API", "") != "1", "ALLOW_REAL_API must not be 1 in default unit tests"
|
||||
assert os.environ.get("ALLOW_REAL_RPA", "") != "1", "ALLOW_REAL_RPA must not be 1 in default unit tests"
|
||||
Reference in New Issue
Block a user