"""Tests for flow.commands.dotfiles discovery and path resolution.""" import pytest from flow.commands.dotfiles import _collect_home_specs, _discover_packages, _resolve_edit_target, _walk_package from flow.core.config import AppConfig, FlowContext from flow.core.console import ConsoleLogger from flow.core.platform import PlatformInfo def _make_tree(tmp_path): flow_root = tmp_path shared = flow_root / "_shared" (shared / "zsh").mkdir(parents=True) (shared / "zsh" / ".zshrc").write_text("# zsh") (shared / "tmux").mkdir(parents=True) (shared / "tmux" / ".tmux.conf").write_text("# tmux") profile = flow_root / "work" (profile / "git").mkdir(parents=True) (profile / "git" / ".gitconfig").write_text("[user]\nname = Work") return tmp_path def _ctx() -> FlowContext: return FlowContext( config=AppConfig(), manifest={"profiles": {"work": {"os": "linux", "configs": {"skip": []}}}}, platform=PlatformInfo(os="linux", arch="x64", platform="linux-x64"), console=ConsoleLogger(), ) def test_discover_packages_shared_only(tmp_path): tree = _make_tree(tmp_path) packages = _discover_packages(tree) assert "zsh" in packages assert "tmux" in packages assert "git" not in packages def test_discover_packages_with_profile(tmp_path): tree = _make_tree(tmp_path) packages = _discover_packages(tree, profile="work") assert "zsh" in packages assert "tmux" in packages assert "git" in packages def test_discover_packages_profile_overrides_shared(tmp_path): tree = _make_tree(tmp_path) profile_zsh = tree / "work" / "zsh" profile_zsh.mkdir(parents=True) (profile_zsh / ".zshrc").write_text("# work zsh") with pytest.raises(RuntimeError, match="Conflicting dotfile targets"): _collect_home_specs(_ctx(), tree, tmp_path / "home", "work", set(), None) def test_walk_package_returns_relative_paths(tmp_path): tree = _make_tree(tmp_path) source = tree / "_shared" / "zsh" pairs = list(_walk_package(source)) assert len(pairs) == 1 src, rel = pairs[0] assert src.name == ".zshrc" assert str(rel) == ".zshrc" def test_resolve_edit_target_package(tmp_path): tree = _make_tree(tmp_path) target = _resolve_edit_target("zsh", dotfiles_dir=tree) assert target == tree / "_shared" / "zsh" def test_resolve_edit_target_repo_path(tmp_path): tree = _make_tree(tmp_path) target = _resolve_edit_target("_shared/zsh/.zshrc", dotfiles_dir=tree) assert target == tree / "_shared" / "zsh" / ".zshrc" def test_resolve_edit_target_rejects_parent_traversal(tmp_path): tree = _make_tree(tmp_path / "repo") outside = tmp_path / "outside.txt" outside.write_text("secret") target = _resolve_edit_target("../outside.txt", dotfiles_dir=tree) assert target is None def test_resolve_edit_target_rejects_nested_repo_escape(tmp_path): tree = _make_tree(tmp_path / "repo") outside = tmp_path / "escape.txt" outside.write_text("secret") target = _resolve_edit_target("_shared/../../escape.txt", dotfiles_dir=tree) assert target is None def test_resolve_edit_target_missing_returns_none(tmp_path): tree = _make_tree(tmp_path) assert _resolve_edit_target("does-not-exist", dotfiles_dir=tree) is None