All checks were successful
Publish Python Package to Gitea / publish (push) Successful in 19s
205 lines
8.3 KiB
Python
205 lines
8.3 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""runtime_env:JIANGCHANG_* 路径契约与兄弟技能根解析。"""
|
||
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()
|