mirror of
https://github.com/cexll/myclaude.git
synced 2025-12-24 13:47:58 +08:00
225 lines
6.8 KiB
Python
225 lines
6.8 KiB
Python
import json
|
|
import shutil
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
import install
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SCHEMA_PATH = ROOT / "config.schema.json"
|
|
|
|
|
|
def _write_schema(target_dir: Path) -> None:
|
|
shutil.copy(SCHEMA_PATH, target_dir / "config.schema.json")
|
|
|
|
|
|
def _base_config(install_dir: Path, modules: dict) -> dict:
|
|
return {
|
|
"version": "1.0",
|
|
"install_dir": str(install_dir),
|
|
"log_file": "install.log",
|
|
"modules": modules,
|
|
}
|
|
|
|
|
|
def _prepare_env(tmp_path: Path, modules: dict) -> tuple[Path, Path, Path]:
|
|
"""Create a temp config directory with schema and config.json."""
|
|
|
|
config_dir = tmp_path / "config"
|
|
install_dir = tmp_path / "install"
|
|
config_dir.mkdir()
|
|
_write_schema(config_dir)
|
|
|
|
cfg_path = config_dir / "config.json"
|
|
cfg_path.write_text(
|
|
json.dumps(_base_config(install_dir, modules)), encoding="utf-8"
|
|
)
|
|
return cfg_path, install_dir, config_dir
|
|
|
|
|
|
def _sample_sources(config_dir: Path) -> dict:
|
|
sample_dir = config_dir / "sample_dir"
|
|
sample_dir.mkdir()
|
|
(sample_dir / "nested.txt").write_text("dir-content", encoding="utf-8")
|
|
|
|
sample_file = config_dir / "sample.txt"
|
|
sample_file.write_text("file-content", encoding="utf-8")
|
|
|
|
return {"dir": sample_dir, "file": sample_file}
|
|
|
|
|
|
def _read_status(install_dir: Path) -> dict:
|
|
return json.loads((install_dir / "installed_modules.json").read_text("utf-8"))
|
|
|
|
|
|
def test_single_module_full_flow(tmp_path):
|
|
cfg_path, install_dir, config_dir = _prepare_env(
|
|
tmp_path,
|
|
{
|
|
"solo": {
|
|
"enabled": True,
|
|
"description": "single module",
|
|
"operations": [
|
|
{"type": "copy_dir", "source": "sample_dir", "target": "payload"},
|
|
{
|
|
"type": "copy_file",
|
|
"source": "sample.txt",
|
|
"target": "payload/sample.txt",
|
|
},
|
|
{
|
|
"type": "run_command",
|
|
"command": f"{sys.executable} -c \"from pathlib import Path; Path('run.txt').write_text('ok', encoding='utf-8')\"",
|
|
},
|
|
],
|
|
}
|
|
},
|
|
)
|
|
|
|
_sample_sources(config_dir)
|
|
rc = install.main(["--config", str(cfg_path), "--module", "solo"])
|
|
|
|
assert rc == 0
|
|
assert (install_dir / "payload" / "nested.txt").read_text(encoding="utf-8") == "dir-content"
|
|
assert (install_dir / "payload" / "sample.txt").read_text(encoding="utf-8") == "file-content"
|
|
assert (install_dir / "run.txt").read_text(encoding="utf-8") == "ok"
|
|
|
|
status = _read_status(install_dir)
|
|
assert status["modules"]["solo"]["status"] == "success"
|
|
assert len(status["modules"]["solo"]["operations"]) == 3
|
|
|
|
|
|
def test_multi_module_install_and_status(tmp_path):
|
|
modules = {
|
|
"alpha": {
|
|
"enabled": True,
|
|
"description": "alpha",
|
|
"operations": [
|
|
{
|
|
"type": "copy_file",
|
|
"source": "sample.txt",
|
|
"target": "alpha.txt",
|
|
}
|
|
],
|
|
},
|
|
"beta": {
|
|
"enabled": True,
|
|
"description": "beta",
|
|
"operations": [
|
|
{
|
|
"type": "copy_dir",
|
|
"source": "sample_dir",
|
|
"target": "beta_dir",
|
|
}
|
|
],
|
|
},
|
|
}
|
|
|
|
cfg_path, install_dir, config_dir = _prepare_env(tmp_path, modules)
|
|
_sample_sources(config_dir)
|
|
|
|
rc = install.main(["--config", str(cfg_path)])
|
|
assert rc == 0
|
|
|
|
assert (install_dir / "alpha.txt").read_text(encoding="utf-8") == "file-content"
|
|
assert (install_dir / "beta_dir" / "nested.txt").exists()
|
|
|
|
status = _read_status(install_dir)
|
|
assert set(status["modules"].keys()) == {"alpha", "beta"}
|
|
assert all(mod["status"] == "success" for mod in status["modules"].values())
|
|
|
|
|
|
def test_force_overwrites_existing_files(tmp_path):
|
|
modules = {
|
|
"forcey": {
|
|
"enabled": True,
|
|
"description": "force copy",
|
|
"operations": [
|
|
{
|
|
"type": "copy_file",
|
|
"source": "sample.txt",
|
|
"target": "target.txt",
|
|
}
|
|
],
|
|
}
|
|
}
|
|
|
|
cfg_path, install_dir, config_dir = _prepare_env(tmp_path, modules)
|
|
sources = _sample_sources(config_dir)
|
|
|
|
install.main(["--config", str(cfg_path), "--module", "forcey"])
|
|
assert (install_dir / "target.txt").read_text(encoding="utf-8") == "file-content"
|
|
|
|
sources["file"].write_text("new-content", encoding="utf-8")
|
|
|
|
rc = install.main(["--config", str(cfg_path), "--module", "forcey", "--force"])
|
|
assert rc == 0
|
|
assert (install_dir / "target.txt").read_text(encoding="utf-8") == "new-content"
|
|
|
|
status = _read_status(install_dir)
|
|
assert status["modules"]["forcey"]["status"] == "success"
|
|
|
|
|
|
def test_failure_triggers_rollback_and_restores_status(tmp_path):
|
|
# First successful run to create a known-good status file.
|
|
ok_modules = {
|
|
"stable": {
|
|
"enabled": True,
|
|
"description": "stable",
|
|
"operations": [
|
|
{
|
|
"type": "copy_file",
|
|
"source": "sample.txt",
|
|
"target": "stable.txt",
|
|
}
|
|
],
|
|
}
|
|
}
|
|
|
|
cfg_path, install_dir, config_dir = _prepare_env(tmp_path, ok_modules)
|
|
_sample_sources(config_dir)
|
|
assert install.main(["--config", str(cfg_path)]) == 0
|
|
pre_status = _read_status(install_dir)
|
|
assert "stable" in pre_status["modules"]
|
|
|
|
# Rewrite config to introduce a failing module.
|
|
failing_modules = {
|
|
**ok_modules,
|
|
"broken": {
|
|
"enabled": True,
|
|
"description": "will fail",
|
|
"operations": [
|
|
{
|
|
"type": "copy_file",
|
|
"source": "sample.txt",
|
|
"target": "broken.txt",
|
|
},
|
|
{
|
|
"type": "run_command",
|
|
"command": f"{sys.executable} -c 'import sys; sys.exit(5)'",
|
|
},
|
|
],
|
|
},
|
|
}
|
|
|
|
cfg_path.write_text(
|
|
json.dumps(_base_config(install_dir, failing_modules)), encoding="utf-8"
|
|
)
|
|
|
|
rc = install.main(["--config", str(cfg_path)])
|
|
assert rc == 1
|
|
|
|
# The failed module's file should have been removed by rollback.
|
|
assert not (install_dir / "broken.txt").exists()
|
|
# Previously installed files remain.
|
|
assert (install_dir / "stable.txt").exists()
|
|
|
|
restored_status = _read_status(install_dir)
|
|
assert restored_status == pre_status
|
|
|
|
log_content = (install_dir / "install.log").read_text(encoding="utf-8")
|
|
assert "Rolling back" in log_content
|
|
|