All checks were successful
技能自动化发布 / release (push) Successful in 5s
Document skill-owned file layout under user data dir, extend runtime_paths with standard subdir helpers, add POLICY-DATA-PATH-001, and update CONFIG/RUNTIME cross-references. Co-authored-by: Cursor <cursoragent@cursor.com>
150 lines
5.5 KiB
Python
150 lines
5.5 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""util.runtime_paths:技能根、DB 路径、数据目录均落在隔离数据根下。"""
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
import unittest
|
||
|
||
from _support import IsolatedDataRoot, get_skill_root
|
||
|
||
_FAKE_REAL_ROOT = r"D:\jiangchang-data"
|
||
|
||
|
||
def _legacy_env_key(suffix: str) -> str:
|
||
return "CL" + "AW_" + suffix
|
||
|
||
|
||
class TestRuntimePaths(unittest.TestCase):
|
||
def test_paths_under_isolated_root(self) -> None:
|
||
win_default = r"D:\jiangchang-data"
|
||
with IsolatedDataRoot() as tmp:
|
||
import importlib
|
||
|
||
import util.runtime_paths as rp
|
||
|
||
importlib.reload(rp)
|
||
|
||
skill_root = rp.get_skill_root()
|
||
self.assertTrue(os.path.isdir(skill_root))
|
||
self.assertTrue(os.path.isfile(os.path.join(skill_root, "SKILL.md")))
|
||
|
||
db_path = rp.get_db_path()
|
||
self.assertTrue(os.path.normpath(db_path).startswith(os.path.normpath(tmp)))
|
||
self.assertNotIn(win_default, db_path)
|
||
|
||
data_dir = rp.get_skill_data_dir()
|
||
self.assertTrue(os.path.normpath(data_dir).startswith(os.path.normpath(tmp)))
|
||
self.assertIn("_test", data_dir.replace("\\", "/"))
|
||
self.assertNotIn(win_default, data_dir)
|
||
|
||
self.assertEqual(get_skill_root(), skill_root)
|
||
|
||
def test_isolation_overrides_prefixed_real_data_root(self) -> None:
|
||
"""先设假数据根,再进入隔离;get_db_path 必须落在 tempdir。"""
|
||
saved = {
|
||
k: os.environ.get(k)
|
||
for k in ("JIANGCHANG_DATA_ROOT", "JIANGCHANG_USER_ID")
|
||
}
|
||
try:
|
||
os.environ["JIANGCHANG_DATA_ROOT"] = _FAKE_REAL_ROOT
|
||
with IsolatedDataRoot() as tmp:
|
||
import importlib
|
||
|
||
import util.runtime_paths as rp
|
||
|
||
importlib.reload(rp)
|
||
db_path = rp.get_db_path()
|
||
norm_db = os.path.normcase(os.path.normpath(db_path))
|
||
norm_tmp = os.path.normcase(os.path.normpath(tmp))
|
||
self.assertTrue(norm_db.startswith(norm_tmp), msg=f"db_path={db_path!r} tmp={tmp!r}")
|
||
norm_fake = os.path.normcase(os.path.normpath(_FAKE_REAL_ROOT))
|
||
self.assertFalse(
|
||
norm_db.startswith(norm_fake),
|
||
msg=f"db_path must not be under fake root: {db_path!r}",
|
||
)
|
||
finally:
|
||
for k, v in saved.items():
|
||
if v is None:
|
||
os.environ.pop(k, None)
|
||
else:
|
||
os.environ[k] = v
|
||
|
||
def test_legacy_env_vars_do_not_override_canonical(self) -> None:
|
||
with IsolatedDataRoot() as tmp:
|
||
os.environ[_legacy_env_key("DATA_ROOT")] = _FAKE_REAL_ROOT
|
||
os.environ[_legacy_env_key("USER_ID")] = "legacy-user"
|
||
import importlib
|
||
|
||
import util.runtime_paths as rp
|
||
|
||
importlib.reload(rp)
|
||
data_dir = rp.get_skill_data_dir()
|
||
self.assertTrue(os.path.normpath(data_dir).startswith(os.path.normpath(tmp)))
|
||
self.assertIn("_test", data_dir.replace("\\", "/"))
|
||
|
||
def test_resolve_data_path_defaults_under_skill_data_dir(self) -> None:
|
||
with IsolatedDataRoot() as tmp:
|
||
import importlib
|
||
|
||
import util.runtime_paths as rp
|
||
|
||
importlib.reload(rp)
|
||
path = rp.resolve_data_path(None, "downloads", create=True)
|
||
norm = os.path.normcase(os.path.normpath(path))
|
||
norm_tmp = os.path.normcase(os.path.normpath(tmp))
|
||
self.assertTrue(norm.startswith(norm_tmp))
|
||
self.assertTrue(norm.endswith(os.path.normcase(os.path.join("downloads"))))
|
||
|
||
def test_resolve_data_path_relative_uses_data_dir_not_cwd(self) -> None:
|
||
with IsolatedDataRoot() as tmp:
|
||
import importlib
|
||
|
||
import util.runtime_paths as rp
|
||
|
||
importlib.reload(rp)
|
||
os.environ["SKILL_DOWNLOAD_DIR"] = "downloads/videos"
|
||
from jiangchang_skill_core import config
|
||
|
||
config.reset_cache()
|
||
importlib.reload(rp)
|
||
path = rp.resolve_data_path("SKILL_DOWNLOAD_DIR", "downloads", create=False)
|
||
norm = os.path.normcase(os.path.normpath(path))
|
||
self.assertIn(os.path.normcase("downloads"), norm)
|
||
self.assertIn(os.path.normcase("videos"), norm)
|
||
self.assertTrue(norm.startswith(os.path.normcase(os.path.normpath(tmp))))
|
||
os.environ.pop("SKILL_DOWNLOAD_DIR", None)
|
||
config.reset_cache()
|
||
|
||
def test_resolve_input_path_relative_under_imports(self) -> None:
|
||
with IsolatedDataRoot() as tmp:
|
||
import importlib
|
||
|
||
import util.runtime_paths as rp
|
||
|
||
importlib.reload(rp)
|
||
resolved = rp.resolve_input_path("urls.txt", create_parent=True)
|
||
norm = os.path.normcase(os.path.normpath(resolved))
|
||
self.assertIn(os.path.normcase("imports"), norm)
|
||
self.assertTrue(norm.startswith(os.path.normcase(os.path.normpath(tmp))))
|
||
|
||
def test_list_resolved_data_paths_keys(self) -> None:
|
||
with IsolatedDataRoot():
|
||
import importlib
|
||
|
||
import util.runtime_paths as rp
|
||
|
||
importlib.reload(rp)
|
||
paths = rp.list_resolved_data_paths()
|
||
for key in (
|
||
"skill_data_dir",
|
||
"downloads_dir",
|
||
"imports_dir",
|
||
"exports_dir",
|
||
):
|
||
self.assertIn(key, paths)
|
||
self.assertTrue(paths[key])
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|