from pathlib import Path import pytest from promote import promote, PromoteError, main as promote_main def _stage_proposed_skill(proposed_root: Path, kind: str, name: str, body: str = "---\nname: s\ndescription: d\n---\n# x") -> Path: skill_dir = proposed_root / kind / name skill_dir.mkdir(parents=True) (skill_dir / "SKILL.md").write_text(body) return skill_dir def test_promotes_new_skill_into_skills_dir(tmp_path: Path): proposed = tmp_path / "_proposed" / "2026-04-17T09-00" _stage_proposed_skill(proposed, "new-skills", "zotero-pubmed-helper") skills_dir = tmp_path / "skills" skills_dir.mkdir() target = promote(proposed, skills_dir, "zotero-pubmed-helper") assert target == skills_dir / "zotero-pubmed-helper" assert (target / "SKILL.md").exists() assert not (proposed / "new-skills" / "zotero-pubmed-helper").exists() def test_promotes_composition_recipe_into_skills_dir(tmp_path: Path): proposed = tmp_path / "_proposed" / "2026-04-17T09-00" _stage_proposed_skill(proposed, "composition-recipes", "lit-review-flow") skills_dir = tmp_path / "skills" skills_dir.mkdir() target = promote(proposed, skills_dir, "lit-review-flow") assert (target / "SKILL.md").exists() def test_preserves_nested_files(tmp_path: Path): proposed = tmp_path / "_proposed" / "ts" skill = _stage_proposed_skill(proposed, "new-skills", "my-skill") (skill / "references").mkdir() (skill / "references" / "notes.md").write_text("note") skills_dir = tmp_path / "skills" skills_dir.mkdir() target = promote(proposed, skills_dir, "my-skill") assert (target / "references" / "notes.md").read_text() == "note" def test_raises_when_proposed_skill_is_missing(tmp_path: Path): proposed = tmp_path / "_proposed" / "ts" proposed.mkdir(parents=True) skills_dir = tmp_path / "skills" skills_dir.mkdir() with pytest.raises(PromoteError, match="not found"): promote(proposed, skills_dir, "ghost") def test_cli_promotes_successfully(tmp_path: Path, capsys): proposed = tmp_path / "_proposed" / "2026-04-17T09-00" _stage_proposed_skill(proposed, "new-skills", "my-new-skill") skills_dir = tmp_path / "skills" skills_dir.mkdir() rc = promote_main([ "--proposed", str(proposed), "--skills-dir", str(skills_dir), "--name", "my-new-skill", ]) assert rc == 0 assert (skills_dir / "my-new-skill" / "SKILL.md").exists() out = capsys.readouterr().out + capsys.readouterr().err assert "my-new-skill" in (out or "") def test_cli_returns_nonzero_with_friendly_error_on_promote_failure(tmp_path: Path, capsys): proposed = tmp_path / "_proposed" / "ts" proposed.mkdir(parents=True) skills_dir = tmp_path / "skills" skills_dir.mkdir() rc = promote_main([ "--proposed", str(proposed), "--skills-dir", str(skills_dir), "--name", "ghost", ]) assert rc != 0 captured = capsys.readouterr() assert "ghost" in captured.err # Should be a friendly message, not a stacktrace. assert "Traceback" not in captured.err def test_raises_when_target_already_exists(tmp_path: Path): proposed = tmp_path / "_proposed" / "ts" _stage_proposed_skill(proposed, "new-skills", "existing") skills_dir = tmp_path / "skills" skills_dir.mkdir() (skills_dir / "existing").mkdir() with pytest.raises(PromoteError, match="already exists"): promote(proposed, skills_dir, "existing")