feat(web): delete a template, not just back it up
POST /host/delete-template: same two-step confirm gate, CSRF check, and known-template containment as /host/backup, offloaded to a thread the same way (rmtree on a multi-GB template dir is not instant). No backup-required safety rail -- same confirm-gate as every other destructive action, nothing more; the confirm summary spells out that clones can no longer be made from the template until one is restored. Also cleaned up the per-backup Delete button it sits next to: name+meta grouped into one shrinkable unit so the row wraps as a block instead of the delete button drifting away from a name that wrapped, and each backup is now its own bordered line instead of a run-on flex list.
This commit is contained in:
@@ -22,7 +22,9 @@ from ci_orchestrator.operations.host import (
|
||||
BackupResult,
|
||||
DiskStatus,
|
||||
RunnerStatus,
|
||||
TemplateDeleteResult,
|
||||
backup_template,
|
||||
delete_template,
|
||||
disk_status,
|
||||
runner_status,
|
||||
)
|
||||
@@ -276,3 +278,66 @@ def test_backup_template_never_calls_real_vmrun_or_7z(monkeypatch: pytest.Monkey
|
||||
backup_template(config, "WinBuild2025")
|
||||
|
||||
assert called == [True]
|
||||
|
||||
|
||||
# ── delete_template ─────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_delete_template_missing_template_dir_errors(tmp_path: Path) -> None:
|
||||
templates = tmp_path / "templates"
|
||||
templates.mkdir()
|
||||
config = _make_config(templates)
|
||||
|
||||
result = delete_template(config, "NoSuchTemplate")
|
||||
|
||||
assert result == TemplateDeleteResult(
|
||||
template="NoSuchTemplate", ok=False, error=result.error
|
||||
)
|
||||
assert result.error is not None
|
||||
assert "NoSuchTemplate" in result.error or "not found" in result.error.lower()
|
||||
|
||||
|
||||
def test_delete_template_dry_run_does_not_remove_the_directory(tmp_path: Path) -> None:
|
||||
templates = tmp_path / "templates"
|
||||
tmpl_dir = templates / "WinBuild2025"
|
||||
tmpl_dir.mkdir(parents=True)
|
||||
(tmpl_dir / "WinBuild2025.vmx").write_text("config.version = \"8\"")
|
||||
config = _make_config(templates)
|
||||
|
||||
result = delete_template(config, "WinBuild2025", dry_run=True)
|
||||
|
||||
assert result == TemplateDeleteResult(template="WinBuild2025", ok=True, error=None)
|
||||
assert tmpl_dir.is_dir()
|
||||
|
||||
|
||||
def test_delete_template_removes_the_whole_directory(tmp_path: Path) -> None:
|
||||
templates = tmp_path / "templates"
|
||||
tmpl_dir = templates / "WinBuild2025"
|
||||
tmpl_dir.mkdir(parents=True)
|
||||
(tmpl_dir / "WinBuild2025.vmx").write_text("config.version = \"8\"")
|
||||
config = _make_config(templates)
|
||||
|
||||
result = delete_template(config, "WinBuild2025")
|
||||
|
||||
assert result == TemplateDeleteResult(template="WinBuild2025", ok=True, error=None)
|
||||
assert not tmpl_dir.exists()
|
||||
|
||||
|
||||
def test_delete_template_removal_failure_reported(
|
||||
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||
) -> None:
|
||||
templates = tmp_path / "templates"
|
||||
tmpl_dir = templates / "WinBuild2025"
|
||||
tmpl_dir.mkdir(parents=True)
|
||||
config = _make_config(templates)
|
||||
|
||||
def _boom(_path: Path) -> None:
|
||||
raise OSError("device busy")
|
||||
|
||||
monkeypatch.setattr(shutil, "rmtree", _boom)
|
||||
|
||||
result = delete_template(config, "WinBuild2025")
|
||||
|
||||
assert result.ok is False
|
||||
assert result.error is not None
|
||||
assert tmpl_dir.is_dir()
|
||||
|
||||
Reference in New Issue
Block a user