From a3bd8faf8750ae17da01649ce6b48e090f573a44 Mon Sep 17 00:00:00 2001 From: chendelian <116870791@qq.com> Date: Tue, 30 Jun 2026 10:29:03 +0800 Subject: [PATCH] docs(config): unify config.get reads and remove ALLOW_* gates --- development/ADAPTER.md | 22 ++---- development/TESTING.md | 18 ++--- .../scripts/service/adapter/__init__.py | 5 +- .../scripts/service/adapter/simulator_rpa.py | 5 +- tests/README.md | 30 +++----- tests/adapter_test_utils.py | 76 +++++++------------ tests/integration/README.md | 13 +--- .../test_real_api_contract.py.sample | 3 +- .../test_real_rpa_contract.py.sample | 4 +- tests/test_adapter_profile_policy.py | 72 +++++------------- tests/test_docs_standards.py | 8 +- 11 files changed, 87 insertions(+), 169 deletions(-) diff --git a/development/ADAPTER.md b/development/ADAPTER.md index 35f7cac..1cb66a2 100644 --- a/development/ADAPTER.md +++ b/development/ADAPTER.md @@ -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/_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 diff --git a/development/TESTING.md b/development/TESTING.md index 324438f..8cd11e0 100644 --- a/development/TESTING.md +++ b/development/TESTING.md @@ -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 / 手动触发) | --- diff --git a/examples/simulator_browser_rpa/scripts/service/adapter/__init__.py b/examples/simulator_browser_rpa/scripts/service/adapter/__init__.py index 67ae8e5..4884e90 100644 --- a/examples/simulator_browser_rpa/scripts/service/adapter/__init__.py +++ b/examples/simulator_browser_rpa/scripts/service/adapter/__init__.py @@ -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) diff --git a/examples/simulator_browser_rpa/scripts/service/adapter/simulator_rpa.py b/examples/simulator_browser_rpa/scripts/service/adapter/simulator_rpa.py index f343e89..81d0894 100644 --- a/examples/simulator_browser_rpa/scripts/service/adapter/simulator_rpa.py +++ b/examples/simulator_browser_rpa/scripts/service/adapter/simulator_rpa.py @@ -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 diff --git a/tests/README.md b/tests/README.md index 1732c9f..c7fd870 100644 --- a/tests/README.md +++ b/tests/README.md @@ -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``;不得默认开启。 --- diff --git a/tests/adapter_test_utils.py b/tests/adapter_test_utils.py index f99be16..042775e 100644 --- a/tests/adapter_test_utils.py +++ b/tests/adapter_test_utils.py @@ -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" + ) diff --git a/tests/integration/README.md b/tests/integration/README.md index 643f7f0..2c0939f 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -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。 diff --git a/tests/integration/test_real_api_contract.py.sample b/tests/integration/test_real_api_contract.py.sample index 68c1bef..6eec96a 100644 --- a/tests/integration/test_real_api_contract.py.sample +++ b/tests/integration/test_real_api_contract.py.sample @@ -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) diff --git a/tests/integration/test_real_rpa_contract.py.sample b/tests/integration/test_real_rpa_contract.py.sample index f1e6d25..4db607e 100644 --- a/tests/integration/test_real_rpa_contract.py.sample +++ b/tests/integration/test_real_rpa_contract.py.sample @@ -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 diff --git a/tests/test_adapter_profile_policy.py b/tests/test_adapter_profile_policy.py index 5c92e39..a7bc799 100644 --- a/tests/test_adapter_profile_policy.py +++ b/tests/test_adapter_profile_policy.py @@ -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) diff --git a/tests/test_docs_standards.py b/tests/test_docs_standards.py index 35738ee..b9917f1 100644 --- a/tests/test_docs_standards.py +++ b/tests/test_docs_standards.py @@ -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")