Files
jiangchang-platform-kit/tests/test_runtime_env.py
chendelian 1632828372
All checks were successful
Publish Python Package to Gitea / publish (push) Successful in 19s
统一运行时环境变量契约,移除 CLAW_* 别名 (v1.0.17)
2026-06-30 14:41:38 +08:00

205 lines
8.3 KiB
Python
Raw Permalink 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.
# -*- coding: utf-8 -*-
"""runtime_envJIANGCHANG_* 路径契约与兄弟技能根解析。"""
from __future__ import annotations
import os
import sys
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from jiangchang_skill_core import config, unified_logging as ul
from jiangchang_skill_core.runtime_diagnostics import (
collect_runtime_diagnostics,
format_runtime_health_lines,
runtime_diagnostics_dict,
)
from jiangchang_skill_core.runtime_env import (
apply_cli_local_defaults,
get_data_root,
get_sibling_skills_root,
get_skills_root,
get_user_id,
platform_default_data_root,
)
def _legacy_env_key(suffix: str) -> str:
return "CL" + "AW_" + suffix
class _EnvIsolation(unittest.TestCase):
_KEYS = (
"JIANGCHANG_DATA_ROOT",
"JIANGCHANG_USER_ID",
"JIANGCHANG_SKILLS_ROOT",
"JIANGCHANG_APP_ROOT",
_legacy_env_key("DATA_ROOT"),
_legacy_env_key("USER_ID"),
_legacy_env_key("SKILLS_ROOT"),
"JIANGCHANG_CLI_LOCAL_DEV",
)
def setUp(self) -> None:
self._saved = {k: os.environ.get(k) for k in self._KEYS}
for k in self._KEYS:
os.environ.pop(k, None)
config.reset_cache()
def tearDown(self) -> None:
for k, v in self._saved.items():
if v is None:
os.environ.pop(k, None)
else:
os.environ[k] = v
config.reset_cache()
class TestGetDataRoot(_EnvIsolation):
def test_jiangchang_data_root_only(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
os.environ["JIANGCHANG_DATA_ROOT"] = tmp
self.assertEqual(get_data_root(), tmp)
def test_platform_default_when_unset(self) -> None:
self.assertEqual(get_data_root(), platform_default_data_root())
def test_legacy_claw_data_root_ignored(self) -> None:
with tempfile.TemporaryDirectory() as claw_tmp, tempfile.TemporaryDirectory() as jc_tmp:
os.environ[_legacy_env_key("DATA_ROOT")] = claw_tmp
os.environ["JIANGCHANG_DATA_ROOT"] = jc_tmp
self.assertEqual(get_data_root(), jc_tmp)
def test_legacy_claw_data_root_alone_ignored(self) -> None:
with tempfile.TemporaryDirectory() as claw_tmp:
os.environ[_legacy_env_key("DATA_ROOT")] = claw_tmp
self.assertEqual(get_data_root(), platform_default_data_root())
class TestGetUserId(_EnvIsolation):
def test_jiangchang_user_id_only(self) -> None:
os.environ["JIANGCHANG_USER_ID"] = "user-42"
self.assertEqual(get_user_id(), "user-42")
def test_anon_when_unset(self) -> None:
self.assertEqual(get_user_id(), "_anon")
def test_legacy_claw_user_id_ignored(self) -> None:
os.environ[_legacy_env_key("USER_ID")] = "claw-only"
os.environ["JIANGCHANG_USER_ID"] = "jc-user"
self.assertEqual(get_user_id(), "jc-user")
def test_legacy_claw_user_id_alone_ignored(self) -> None:
os.environ[_legacy_env_key("USER_ID")] = "claw-only"
self.assertEqual(get_user_id(), "_anon")
class TestApplyCliLocalDefaults(_EnvIsolation):
def test_sets_jiangchang_only(self) -> None:
with patch("jiangchang_skill_core.runtime_env.CLI_LOCAL_DEV_ENABLED", True):
apply_cli_local_defaults()
self.assertTrue(os.environ.get("JIANGCHANG_DATA_ROOT"))
self.assertTrue(os.environ.get("JIANGCHANG_USER_ID"))
self.assertNotIn(_legacy_env_key("DATA_ROOT"), os.environ)
self.assertNotIn(_legacy_env_key("USER_ID"), os.environ)
def test_does_not_override_existing(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
os.environ["JIANGCHANG_DATA_ROOT"] = tmp
os.environ["JIANGCHANG_USER_ID"] = "existing"
with patch("jiangchang_skill_core.runtime_env.CLI_LOCAL_DEV_ENABLED", True):
apply_cli_local_defaults()
self.assertEqual(os.environ["JIANGCHANG_DATA_ROOT"], tmp)
self.assertEqual(os.environ["JIANGCHANG_USER_ID"], "existing")
class TestGetSkillsRoot(_EnvIsolation):
def test_jiangchang_skills_root_override(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
os.environ["JIANGCHANG_SKILLS_ROOT"] = tmp
self.assertEqual(get_skills_root(), os.path.normpath(tmp))
def test_legacy_claw_skills_root_ignored(self) -> None:
with tempfile.TemporaryDirectory() as claw_tmp, tempfile.TemporaryDirectory() as jc_tmp:
os.environ[_legacy_env_key("SKILLS_ROOT")] = claw_tmp
os.environ["JIANGCHANG_SKILLS_ROOT"] = jc_tmp
self.assertEqual(get_skills_root(), os.path.normpath(jc_tmp))
def test_legacy_claw_skills_root_alone_ignored(self) -> None:
with tempfile.TemporaryDirectory() as claw_tmp:
os.environ[_legacy_env_key("SKILLS_ROOT")] = claw_tmp
root = get_skills_root()
self.assertNotEqual(root, os.path.normpath(claw_tmp))
class TestGetSiblingSkillsRoot(_EnvIsolation):
def _make_skills_tree(self, base: Path) -> Path:
skills_root = base / "skills"
(skills_root / "account-manager").mkdir(parents=True)
(skills_root / "receive-order" / "scripts").mkdir(parents=True)
return skills_root
def test_inference_preferred_over_env(self) -> None:
with tempfile.TemporaryDirectory() as tree_tmp, tempfile.TemporaryDirectory() as env_tmp:
skills_root = self._make_skills_tree(Path(tree_tmp))
scripts_dir = str(skills_root / "receive-order" / "scripts")
os.environ["JIANGCHANG_SKILLS_ROOT"] = env_tmp
result = get_sibling_skills_root(scripts_dir)
self.assertEqual(result, os.path.normpath(str(skills_root)))
def test_env_override_when_inference_fails(self) -> None:
with tempfile.TemporaryDirectory() as env_tmp:
os.environ["JIANGCHANG_SKILLS_ROOT"] = env_tmp
result = get_sibling_skills_root("/nonexistent/skill/scripts")
self.assertEqual(result, os.path.normpath(env_tmp))
def test_legacy_claw_skills_root_ignored_for_sibling(self) -> None:
with tempfile.TemporaryDirectory() as claw_tmp, tempfile.TemporaryDirectory() as jc_tmp:
os.environ[_legacy_env_key("SKILLS_ROOT")] = claw_tmp
os.environ["JIANGCHANG_SKILLS_ROOT"] = jc_tmp
result = get_sibling_skills_root("/nonexistent/skill/scripts")
self.assertEqual(result, os.path.normpath(jc_tmp))
class TestConfigAndLoggingPaths(_EnvIsolation):
def test_config_env_file_under_user_isolated_dir(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
os.environ["JIANGCHANG_DATA_ROOT"] = tmp
os.environ["JIANGCHANG_USER_ID"] = "u-test"
example = os.path.join(tmp, ".env.example")
with open(example, "w", encoding="utf-8") as f:
f.write("FOO=bar\n")
dest = config.ensure_env_file("my-skill", example)
expected_prefix = os.path.join(tmp, "u-test", "my-skill")
self.assertTrue(os.path.normpath(dest).startswith(os.path.normpath(expected_prefix)))
def test_unified_logging_path_under_user_dir(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
os.environ["JIANGCHANG_DATA_ROOT"] = tmp
os.environ["JIANGCHANG_USER_ID"] = "log-user"
log_dir = ul.get_unified_logs_dir()
self.assertTrue(
os.path.normpath(log_dir).startswith(
os.path.normpath(os.path.join(tmp, "log-user", "logs"))
)
)
class TestDiagnosticsNoClawFields(_EnvIsolation):
def test_diagnostics_json_and_text_exclude_claw(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
os.environ["JIANGCHANG_DATA_ROOT"] = tmp
diag = collect_runtime_diagnostics(skill_slug="x", env=dict(os.environ))
payload = runtime_diagnostics_dict(diag)
text = "\n".join(format_runtime_health_lines(diag))
legacy_data_root_key = _legacy_env_key("DATA_ROOT")
self.assertNotIn(legacy_data_root_key, payload)
self.assertNotIn(legacy_data_root_key, text)
self.assertIn("JIANGCHANG_DATA_ROOT", payload)
self.assertEqual(payload["resolved_data_root"], tmp)
if __name__ == "__main__":
unittest.main()