docs(config): unify config.get reads and remove ALLOW_* gates
All checks were successful
技能自动化发布 / release (push) Successful in 5s

This commit is contained in:
2026-06-30 10:29:03 +08:00
parent 30803a0834
commit a3bd8faf87
11 changed files with 87 additions and 169 deletions

View File

@@ -17,8 +17,8 @@
|------|------|----------|
| **`mock`** | 纯内存或 fixture**默认单测/CI** | 模板 `.env.example` 默认 `OPENCLAW_TEST_TARGET=mock` |
| **`simulator_rpa`** | 仿真站点或桌面仿真,可半集成 | 开发联调可选 |
| **`real_api`** | 真实 API | **必须** `ALLOW_REAL_API=1` |
| **`real_rpa`** | 真实浏览器/真实系统 | **必须** `ALLOW_REAL_RPA=1` |
| **`real_api`** | 真实 API | 生产 / 集成测试显式设 `OPENCLAW_TEST_TARGET=real_api` |
| **`real_rpa`** | 真实浏览器/真实系统 | 生产 / 集成测试显式设 `OPENCLAW_TEST_TARGET=real_rpa` |
- **mock**:纯离线、不联网,给单测 / CI / 开发自测,**保证可重复**。
- **simulator_rpa**:操作仿真平台(如 `sandbox.jc2009.com`),跑端到端流程但不碰生产。
@@ -27,17 +27,7 @@
> 推荐优先级:**real_api > simulator_rpa > real_rpa**mock 永远保留做 CI。
## 权限开关
未显式授权时**不得**落到真实 API/RPA
| 变量 | 作用 |
|------|------|
| `ALLOW_REAL_API=1` | 允许 `real_api` 访问真实 HTTP |
| `ALLOW_REAL_RPA=1` | 允许 `real_rpa` 驱动真实浏览器/RPA |
| `ALLOW_WRITE_ACTIONS=1` | 在 `real_*` 下允许**写**操作(提交表单、下单等) |
进程环境变量优先于用户 `.env`(见 `CONFIG.md` 三层优先级)。
配置读取见 `CONFIG.md`**bootstrap 之后业务代码只通过 `config.get*()``OPENCLAW_TEST_TARGET` 等项**(进程 env > 用户 `.env` > `.env.example`)。
## 目录骨架
@@ -74,8 +64,10 @@ scripts/service/<domain>_adapter/
```python
# __init__.py
from jiangchang_skill_core import config
def get_adapter():
target = (os.environ.get("OPENCLAW_TEST_TARGET") or "mock").lower()
target = (config.get("OPENCLAW_TEST_TARGET") or "mock").lower()
if target in ("unit", "mock"):
return MockAdapter()
if target == "real_api":
@@ -85,7 +77,7 @@ def get_adapter():
return SimRpaAdapter()
```
`get_adapter()` 实现应配合 `tests/adapter_test_utils.py` 的 profile 策略:**未授权时不 silently 开启真实网络/RPA**。
`get_adapter()` 实现应配合 `tests/adapter_test_utils.py` 的 profile 策略:**默认必跑测试不得误设 `OPENCLAW_TEST_TARGET=real_*`**。
## contract tests

View File

@@ -71,22 +71,14 @@ def test_whatever():
| `mock` | 与 `unit` 类似的安全档位(显式语义) |
| `simulator_api` | 仅允许 **仿真 HTTP** 类集成(如 localhost |
| `simulator_rpa` | 仅允许 **仿真页面 / 录播 RPA** |
| `real_api` | 真实 API另需 `ALLOW_REAL_API=1` |
| `real_rpa` | 真实 RPA另需 `ALLOW_REAL_RPA=1` |
| `real_api` | 真实 API显式设 `OPENCLAW_TEST_TARGET=real_api` |
| `real_rpa` | 真实 RPA显式设 `OPENCLAW_TEST_TARGET=real_rpa` |
未设置环境变量 ⇒ 等价 `unit`
授权开关(显式 `1`)语义 **`ALLOW_REAL_API` / `ALLOW_REAL_RPA` / `ALLOW_WRITE_ACTIONS`**——摘录 [`tests/README.md`](../tests/README.md) §5.2
默认策略摘要:**不要在 unittest 必跑路径误设 `OPENCLAW_TEST_TARGET=real_*`**。
| 变量 | 作用 |
|------|------|
| `ALLOW_REAL_API=1` | 允许 `real_api` profile 访问真实 HTTP 通道 |
| `ALLOW_REAL_RPA=1` | 允许 `real_rpa` profile 驱动真实浏览器/RPA |
| `ALLOW_WRITE_ACTIONS=1` | 在 `real_*` 下允许**写**操作(提交表单、下单等) |
默认策略摘要见 §5.3**不要在 unittest 必跑路径误把闸门打开**。
兼容别名:`OPENCLOW_TEST_TARGET`(历史拼写)。
档位读取与业务代码一致:经 `jiangchang_skill_core.config.get("OPENCLAW_TEST_TARGET")`(见 `CONFIG.md`)。
---
@@ -164,7 +156,7 @@ Golden fixture 流程同理([`tests/samples/test_golden_cases.py.sample`](../t
| **零硬编码凭证** | token / cookie / 生产 URL → 用虚构域名或 vault ref |
| mock 优先 | 逻辑应在 service + FakeAdapter而不是巨胖 CLI 断言 |
| 结构化错误 | 断言错误码字段,而不是 substring of stderr 漂移集合 |
| **integration = 显式开关** | `OPENCLAW_TEST_TARGET` + `ALLOW_REAL_*` / `ALLOW_WRITE_ACTIONS` |
| **integration = 显式档位** | `OPENCLAW_TEST_TARGET``real_*` 只放 integration / 手动触发) |
---

View File

@@ -3,9 +3,10 @@
from __future__ import annotations
import logging
import os
from typing import Optional
from jiangchang_skill_core import config
from service.adapter.base import BatchAdapterBase, BatchItem, BatchSubmitResult
from service.adapter.mock import MockBatchAdapter
from service.adapter.simulator_rpa import SimulatorBrowserRpaAdapter
@@ -23,7 +24,7 @@ __all__ = [
def select_adapter(artifacts_dir: Optional[str] = None) -> BatchAdapterBase:
target = os.environ.get("OPENCLAW_TEST_TARGET", "").strip().lower()
target = (config.get("OPENCLAW_TEST_TARGET") or "").strip().lower()
if target in ("mock", "unit"):
logger.info("target '%s': MockBatchAdapter", target)

View File

@@ -9,6 +9,8 @@ import re
import time
from typing import List, Optional
from jiangchang_skill_core import config
from service.adapter.base import BatchAdapterBase, BatchItem, BatchSubmitResult
from service.browser_session import browser_session, find_chrome_executable
from util.constants import (
@@ -45,8 +47,7 @@ class SimulatorBrowserRpaAdapter(BatchAdapterBase):
) -> None:
self.base_url = (base_url or resolve_simulator_base_url()).rstrip("/")
if headless is None:
env = os.environ.get("OPENCLAW_BROWSER_HEADLESS", "").strip().lower()
self.headless = env in ("1", "true", "yes")
self.headless = config.get_bool("OPENCLAW_BROWSER_HEADLESS", default=False)
else:
self.headless = headless
self.artifacts_dir = artifacts_dir

View File

@@ -1,6 +1,6 @@
# skill-template 测试说明(企业数字员工技能)
本目录提供面向 **ERP / TMS / WMS / 船司 / 报关 / 邮件 / 浏览器 / LLM / RPA** 等外联场景的**分层测试骨架**:默认仅内存与本地 SQLite**真实 API / 真实 RPA** 必须通过环境变量显式授权,且仅以 ``*.sample`` 或复制后的文件提供范式。
本目录提供面向 **ERP / TMS / WMS / 船司 / 报关 / 邮件 / 浏览器 / LLM / RPA** 等外联场景的**分层测试骨架**:默认仅内存与本地 SQLite**真实 API / 真实 RPA** 必须显式设置 ``OPENCLAW_TEST_TARGET``,且仅以 ``*.sample`` 或复制后的文件提供范式。
复制为新技能后,请保留隔离数据根与 profile 策略,再按域扩展用例。
@@ -16,9 +16,9 @@
| 纯函数 / 业务规则 | `tests/test_*.py` | 是 | 不依赖真实外部系统 |
| service 层契约 | 从 `tests/samples/test_service_contract.py.sample` 复制到 `tests/test_service_contract.py` | 是,复制后 | 用 `FakeAdapter` / stub 注入外部依赖 |
| golden fixture 回归 | 从 `tests/samples/test_golden_cases.py.sample` 复制到 `tests/test_golden_cases.py` | 是,复制后 | 输入样例 + 期望输出,适合解析、计算、校验类技能 |
| 仿真 API | `tests/integration/test_simulator_api_contract.py.sample` | 否 | 复制并设置 `OPENCLAW_TEST_TARGET=simulator_api`(或兼容键名 `OPENCLOW_TEST_TARGET`后按需运行 |
| 仿真 API | `tests/integration/test_simulator_api_contract.py.sample` | 否 | 复制并设置 `OPENCLAW_TEST_TARGET=simulator_api` 后按需运行 |
| 仿真 RPA / 页面操作 | `tests/integration/test_simulator_rpa_contract.py.sample` | 否 | 用 localhost / 仿真页面,不碰真实系统 |
| 真实 API | `tests/integration/test_real_api_contract.py.sample` | 否 | 必须 `OPENCLAW_TEST_TARGET=real_api` 且 `ALLOW_REAL_API=1` |
| 真实 API | `tests/integration/test_real_api_contract.py.sample` | 否 | 必须 `OPENCLAW_TEST_TARGET=real_api` |
| 真实 RPA | `tests/integration/test_real_rpa_contract.py.sample` | 否 | 最高风险,只能手动触发 |
| 桌面宿主 E2E | `tests/desktop/test_desktop_smoke.py.sample` | 否 | 需要 pytest + `jiangchang_desktop_sdk` |
@@ -125,11 +125,11 @@ python tests/run_tests.py test_cli_smoke
---
## 5. 环境变量:测试档位与授权
## 5. 环境变量:测试档位
### 5.1 测试档位(二选一变量名,后者为历史拼写兼容)
### 5.1 测试档位
- ``OPENCLAW_TEST_TARGET`` 或 ``OPENCLOW_TEST_TARGET``
- ``OPENCLAW_TEST_TARGET``
**合法取值**(非法值将使 ``get_test_target()`` 抛 ``ValueError``
@@ -139,23 +139,15 @@ python tests/run_tests.py test_cli_smoke
| ``mock`` | 与 ``unit`` 类似的安全档位(显式语义) |
| ``simulator_api`` | 仅允许 **仿真 HTTP** 类集成(如 localhost |
| ``simulator_rpa`` | 仅允许 **仿真页面 / 录播 RPA** |
| ``real_api`` | 真实 API另需 ``ALLOW_REAL_API=1`` |
| ``real_rpa`` | 真实 RPA另需 ``ALLOW_REAL_RPA=1`` |
| ``real_api`` | 真实 API显式设 ``OPENCLAW_TEST_TARGET=real_api`` |
| ``real_rpa`` | 真实 RPA显式设 ``OPENCLAW_TEST_TARGET=real_rpa`` |
未设置时等价 ``unit``。
### 5.2 授权开关(显式 ``1``
| 变量 | 作用 |
|------|------|
| ``ALLOW_REAL_API=1`` | 允许 ``real_api`` profile 访问真实 HTTP 通道 |
| ``ALLOW_REAL_RPA=1`` | 允许 ``real_rpa`` profile 驱动真实浏览器/RPA |
| ``ALLOW_WRITE_ACTIONS=1`` | 在 ``real_*`` 下允许**写**操作(提交表单、下单等) |
### 5.3 默认安全策略(``adapter_test_utils``
### 5.2 默认安全策略(``adapter_test_utils``
- 默认 ``unit``:不访问真实 API、不跑真实 RPA、不做写操作、不读取真实密钥、不写真实数据根。
- ``assert_no_real_network_env()`` 断言默认未误 ``ALLOW_REAL_*``(用于必跑用例自检)。
- ``assert_no_real_network_env()`` 断言默认未误 ``OPENCLAW_TEST_TARGET`` 为 ``real_api`` / ``real_rpa``(用于必跑用例自检)。
---
@@ -170,7 +162,7 @@ python tests/run_tests.py test_cli_smoke
- 默认测试只能使用 **mock、stub、`FakeAdapter`、fixtures、隔离 SQLite** 等安全手段。
- 业务逻辑应优先在 **service 层** 编写与测试,不要只断言 CLI 文案。
- 异常路径应返回**结构化错误**(如统一错误码/字段),避免裸异常直接穿透到 CLI。
- 有真实系统联调需求时,测试应写在 `tests/integration/`,并配合 `OPENCLAW_TEST_TARGET` / `ALLOW_REAL_*` / `ALLOW_WRITE_ACTIONS` 等**显式开关**;不得默认开启。
- 有真实系统联调需求时,测试应写在 `tests/integration/`,并显式设置 ``OPENCLAW_TEST_TARGET`` ``real_api`` / ``real_rpa``;不得默认开启。
---

View File

@@ -1,17 +1,17 @@
# -*- coding: utf-8 -*-
"""
企业技能对接外部系统ERP / TMS / WMS / 船司 / 报关 / 邮件 / 浏览器 / LLM / RPA
测试侧共用的 **profile / 授权 / 禁止真实外呼** 工具。
测试侧共用的 **profile / 禁止真实外呼** 工具。
- 仅标准库;不发起 HTTP不启动浏览器。
- 真实 API / RPA 契约测试请放在 ``tests/integration/*.sample``,并配合环境变量显式授权
- 仅标准库 + jiangchang_skill_core.config;不发起 HTTP不启动浏览器。
- 真实 API / RPA 契约测试请放在 ``tests/integration/*.sample``,并显式设置 ``OPENCLAW_TEST_TARGET``
"""
from __future__ import annotations
import os
from typing import Any, Final
# 允许的 OPENCLAW_TEST_TARGET / OPENCLOW_TEST_TARGET 取值(后者为历史拼写兼容)
from jiangchang_skill_core import config
ALLOWED_TEST_TARGETS: Final[frozenset[str]] = frozenset(
{
"unit",
@@ -25,12 +25,12 @@ ALLOWED_TEST_TARGETS: Final[frozenset[str]] = frozenset(
def _raw_test_target_env() -> str:
return (os.environ.get("OPENCLAW_TEST_TARGET") or os.environ.get("OPENCLOW_TEST_TARGET") or "").strip()
return (config.get("OPENCLAW_TEST_TARGET") or "").strip()
def get_test_target() -> str:
"""
读取 ``OPENCLAW_TEST_TARGET`` ``OPENCLOW_TEST_TARGET``(兼容旧拼写)。
读取 ``OPENCLAW_TEST_TARGET``(经 ``config.get`` 三级合并)。
未设置时返回 ``unit``。非法取值抛出 ``ValueError``,便于尽早发现拼写错误。
"""
@@ -46,18 +46,15 @@ def get_test_target() -> str:
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)`` 组合检查)。
判断当前 ``OPENCLAW_TEST_TARGET`` 是否允许运行 ``profile`` 档位测试
"""
if profile == "real_api":
return os.environ.get("ALLOW_REAL_API", "") == "1"
if profile == "real_rpa":
return os.environ.get("ALLOW_REAL_RPA", "") == "1"
target = get_test_target()
if profile in ("mock", "simulator_api", "simulator_rpa"):
return True
if profile == "real_api":
return target == "real_api"
if profile == "real_rpa":
return target == "real_rpa"
raise ValueError(f"unknown profile for real_access_allowed: {profile!r}")
@@ -67,42 +64,19 @@ def should_skip_profile(profile: str, write: bool = False) -> tuple[bool, str]:
策略摘要:
- ``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``。
- ``simulator_api`` / ``simulator_rpa``:仅当 ``get_test_target()`` 与 profile 一致时不跳过。
- ``real_api`` / ``real_rpa``:仅当 ``get_test_target()`` 与 profile 一致时不跳过。
- ``write`` 参数保留以兼容 integration 样例签名;档位一致即允许写操作测试
"""
_ = write
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)")
if profile in ("simulator_api", "simulator_rpa", "real_api", "real_rpa"):
if target != profile:
return (True, f"OPENCLAW_TEST_TARGET must be {profile} (got {target!r})")
return (False, "")
raise ValueError(f"unknown profile: {profile!r}")
@@ -134,9 +108,11 @@ class FakeAdapter:
def assert_no_real_network_env() -> None:
"""
断言当前进程未显式打开「真实外呼」授权开关(默认安全 CI / 本地开发)。
断言默认必跑套件未误设真实外联档位(``real_api`` / ``real_rpa``)。
若需在受控环境跑真实集成,请显式设置 ``ALLOW_REAL_*`` 并使用 ``tests/integration/*.sample``。
若需在受控环境跑真实集成,请显式设置 ``OPENCLAW_TEST_TARGET`` 并使用 ``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"
target = get_test_target()
assert target not in ("real_api", "real_rpa"), (
f"OPENCLAW_TEST_TARGET must not be {target!r} in default unit tests"
)

View File

@@ -11,7 +11,7 @@
|------|------------------|------------------------|
| 目的 | 默认可**升级**为根目录 `test_*.py`**业务单元 / service 契约 / golden** 范式 | **外联联调**范式:网络、浏览器、仿真或真实系统 |
| 默认执行 | `.sample` 不执行;复制到根目录并改名后才进入默认套件 | **永远不**作为默认 unittest 的一部分;复制后仍需显式环境变量与手动/CI 任务触发 |
| 典型内容 | `FakeAdapter`、fixture、隔离 DB | `localhost` 仿真、真实 API(授权)、真实 RPA最高风险 |
| 典型内容 | `FakeAdapter`、fixture、隔离 DB | `localhost` 仿真、真实 API、真实 RPA最高风险 |
**原则**:只要会访问**网络**、**浏览器**、**真实或仿真外部系统**,应优先放在 `integration`(并保持 `.sample` 直至团队同意启用),而不是写进默认 `tests/test_*.py`
@@ -21,20 +21,15 @@
|------|------------------------------|----------|
| 本地 / CI 对接**仿真 HTTP** | ``simulator_api`` | 仿真服务如 ``http://localhost:5180`` |
| **仿真页面** / 录播 RPA | ``simulator_rpa`` | 本地页面或沙箱浏览器 profile |
| **真实 API**(只读优先) | ``real_api`` | ``ALLOW_REAL_API=1``;写操作另需 ``ALLOW_WRITE_ACTIONS=1`` |
| **真实 RPA**(最高风险) | ``real_rpa`` | ``ALLOW_REAL_RPA=1``**仅手动触发** |
| **真实 API** | ``real_api`` | **仅手动触发** |
| **真实 RPA**(最高风险) | ``real_rpa`` | **仅手动触发** |
## 环境变量(与 ``adapter_test_utils`` 一致)
- **目标档位**(二选一键名,后者为历史拼写):``OPENCLAW_TEST_TARGET`` 或 ``OPENCLOW_TEST_TARGET``
- **目标档位**``OPENCLAW_TEST_TARGET``
取值:``unit`` | ``mock`` | ``simulator_api`` | ``simulator_rpa`` | ``real_api`` | ``real_rpa``
默认未设置等价于 ``unit``。
- **授权开关**(显式 ``1`` 才启用):
- ``ALLOW_REAL_API=1``
- ``ALLOW_REAL_RPA=1``
- ``ALLOW_WRITE_ACTIONS=1``(对写操作 / 提交表单类步骤)
## 安全约束
- **禁止**在样例或复制后的测试代码中硬编码真实账号、密码、token、cookie、生产 URL。

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# 样例:复制为 test_real_api_contract.py需 OPENCLAW_TEST_TARGET=real_api 且 ALLOW_REAL_API=1
# 样例:复制为 test_real_api_contract.py需 OPENCLAW_TEST_TARGET=real_api。
"""真实 HTTP API只读优先默认跳过。"""
from __future__ import annotations
@@ -15,7 +15,6 @@ class TestRealApiContractSample(unittest.TestCase):
raise unittest.SkipTest(reason)
# 写操作示例(复制后使用):
# skip_w, _ = should_skip_profile("real_api", write=True)
# 需要 ALLOW_WRITE_ACTIONS=1
# 禁止在此文件写入真实 token从 vault / 环境注入读取 endpoint。
self.assertFalse(skip)

View File

@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
# 样例:复制为 test_real_rpa_contract.py需 OPENCLAW_TEST_TARGET=real_rpa 且 ALLOW_REAL_RPA=1
# 样例:复制为 test_real_rpa_contract.py需 OPENCLAW_TEST_TARGET=real_rpa。
"""
真实 RPA / 桌面自动化(**最高风险**,仅手动触发)。
默认只读思路示例:打开沙箱门户、读取标题、**不提交表单**。
任何写操作必须 ``should_skip_profile('real_rpa', write=True)`` 且 ``ALLOW_WRITE_ACTIONS=1``
写操作测试同样须显式设 ``OPENCLAW_TEST_TARGET=real_rpa`` 后手动触发
"""
from __future__ import annotations

View File

@@ -5,6 +5,8 @@ from __future__ import annotations
import os
import unittest
from jiangchang_skill_core import config
from adapter_test_utils import (
ALLOWED_TEST_TARGETS,
FakeAdapter,
@@ -24,34 +26,22 @@ def _restore_env(saved: dict[str, str | None]) -> None:
os.environ.pop(k, None)
else:
os.environ[k] = v
config.reset_cache()
_ENV_KEYS = (
"OPENCLAW_TEST_TARGET",
"OPENCLOW_TEST_TARGET",
"ALLOW_REAL_API",
"ALLOW_REAL_RPA",
"ALLOW_WRITE_ACTIONS",
)
_ENV_KEYS = ("OPENCLAW_TEST_TARGET",)
class TestAdapterProfilePolicy(unittest.TestCase):
def test_get_test_target_reads_opencylow_typo_alias(self) -> None:
"""兼容环境变量历史拼写 OPENCLOW_TEST_TARGET。"""
saved = _save_env(_ENV_KEYS)
try:
os.environ.pop("OPENCLAW_TEST_TARGET", None)
os.environ.pop("OPENCLOW_TEST_TARGET", None)
os.environ["OPENCLOW_TEST_TARGET"] = "mock"
self.assertEqual(get_test_target(), "mock")
finally:
_restore_env(saved)
def tearDown(self) -> None:
config.reset_cache()
def test_get_test_target_defaults_to_unit(self) -> None:
saved = _save_env(_ENV_KEYS)
try:
for k in _ENV_KEYS:
os.environ.pop(k, None)
config.reset_cache()
self.assertEqual(get_test_target(), "unit")
finally:
_restore_env(saved)
@@ -62,6 +52,7 @@ class TestAdapterProfilePolicy(unittest.TestCase):
for k in _ENV_KEYS:
os.environ.pop(k, None)
os.environ["OPENCLAW_TEST_TARGET"] = "not_a_valid_target"
config.reset_cache()
with self.assertRaises(ValueError):
get_test_target()
finally:
@@ -74,6 +65,7 @@ class TestAdapterProfilePolicy(unittest.TestCase):
saved = _save_env(_ENV_KEYS)
try:
os.environ["OPENCLAW_TEST_TARGET"] = "simulator_api"
config.reset_cache()
skip, _ = should_skip_profile("simulator_api")
self.assertFalse(skip)
finally:
@@ -84,6 +76,7 @@ class TestAdapterProfilePolicy(unittest.TestCase):
try:
for k in _ENV_KEYS:
os.environ.pop(k, None)
config.reset_cache()
self.assertEqual(get_test_target(), "unit")
for prof in ("simulator_api", "simulator_rpa", "real_api", "real_rpa"):
skip, reason = should_skip_profile(prof)
@@ -93,46 +86,23 @@ class TestAdapterProfilePolicy(unittest.TestCase):
finally:
_restore_env(saved)
def test_real_api_requires_allow_flag(self) -> None:
def test_real_api_runs_when_target_matches(self) -> None:
saved = _save_env(_ENV_KEYS)
try:
os.environ["OPENCLAW_TEST_TARGET"] = "real_api"
os.environ.pop("ALLOW_REAL_API", None)
skip, reason = should_skip_profile("real_api")
self.assertTrue(skip)
self.assertIn("ALLOW_REAL_API", reason)
os.environ["ALLOW_REAL_API"] = "1"
skip2, _ = should_skip_profile("real_api", write=False)
self.assertFalse(skip2)
config.reset_cache()
skip, _ = should_skip_profile("real_api", write=True)
self.assertFalse(skip)
finally:
_restore_env(saved)
def test_real_api_write_requires_allow_write_actions(self) -> None:
saved = _save_env(_ENV_KEYS)
try:
os.environ["OPENCLAW_TEST_TARGET"] = "real_api"
os.environ["ALLOW_REAL_API"] = "1"
os.environ.pop("ALLOW_WRITE_ACTIONS", None)
skip, reason = should_skip_profile("real_api", write=True)
self.assertTrue(skip)
self.assertIn("ALLOW_WRITE_ACTIONS", reason)
os.environ["ALLOW_WRITE_ACTIONS"] = "1"
skip2, _ = should_skip_profile("real_api", write=True)
self.assertFalse(skip2)
finally:
_restore_env(saved)
def test_real_rpa_requires_allow_rpa_flag(self) -> None:
def test_real_rpa_runs_when_target_matches(self) -> None:
saved = _save_env(_ENV_KEYS)
try:
os.environ["OPENCLAW_TEST_TARGET"] = "real_rpa"
os.environ.pop("ALLOW_REAL_RPA", None)
skip, reason = should_skip_profile("real_rpa")
self.assertTrue(skip)
self.assertIn("ALLOW_REAL_RPA", reason)
os.environ["ALLOW_REAL_RPA"] = "1"
skip2, _ = should_skip_profile("real_rpa", write=False)
self.assertFalse(skip2)
config.reset_cache()
skip, _ = should_skip_profile("real_rpa", write=True)
self.assertFalse(skip)
finally:
_restore_env(saved)
@@ -146,10 +116,10 @@ class TestAdapterProfilePolicy(unittest.TestCase):
FakeAdapter("unauthorized").call({})
def test_assert_no_real_network_env(self) -> None:
saved = _save_env(("ALLOW_REAL_API", "ALLOW_REAL_RPA"))
saved = _save_env(_ENV_KEYS)
try:
os.environ.pop("ALLOW_REAL_API", None)
os.environ.pop("ALLOW_REAL_RPA", None)
os.environ.pop("OPENCLAW_TEST_TARGET", None)
config.reset_cache()
assert_no_real_network_env()
finally:
_restore_env(saved)

View File

@@ -165,19 +165,19 @@ class TestDocsStandards(unittest.TestCase):
self.assertIn("RpaVideoSession", text)
self.assertIn("1.0.14", text)
def test_adapter_md_covers_four_tiers_and_allow_flags(self) -> None:
def test_adapter_md_covers_four_tiers_and_config_get(self) -> None:
text = self._read("development/ADAPTER.md")
for marker in (
"mock",
"simulator_rpa",
"real_api",
"real_rpa",
"ALLOW_REAL_API",
"ALLOW_REAL_RPA",
"ALLOW_WRITE_ACTIONS",
"config.get",
"sibling_bridge",
):
self.assertIn(marker, text, msg=f"ADAPTER.md missing {marker!r}")
self.assertNotIn("ALLOW_REAL_API", text)
self.assertNotIn("ALLOW_WRITE_ACTIONS", text)
def test_cli_md_mentions_shared_python_runtime(self) -> None:
text = self._read("references/CLI.md")