update
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
"""Tests for DotfilesService."""
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
@@ -8,19 +7,10 @@ import yaml
|
||||
from flow.core.config import AppConfig, FlowContext
|
||||
from flow.core.console import Console
|
||||
from flow.core.platform import PlatformInfo
|
||||
from flow.core.runtime import CommandRunner, SystemRuntime
|
||||
from flow.core.runtime import SystemRuntime
|
||||
from flow.core import paths
|
||||
from flow.services.dotfiles import DotfilesService
|
||||
|
||||
|
||||
class FakeRunner(CommandRunner):
|
||||
def __init__(self):
|
||||
self.calls: list[list[str]] = []
|
||||
|
||||
def run(self, argv, *, cwd=None, env=None, capture_output=True, check=False, timeout=None):
|
||||
command = [str(part) for part in argv]
|
||||
self.calls.append(command)
|
||||
return subprocess.CompletedProcess(command, 0, stdout="", stderr="")
|
||||
from tests.fakes import FakeRunner
|
||||
|
||||
|
||||
def _make_ctx(tmp_path, console=None):
|
||||
@@ -206,7 +196,69 @@ class TestDotfilesServiceLink:
|
||||
assert target.read_text() == "user managed file"
|
||||
assert not target.is_symlink()
|
||||
|
||||
def test_sync_modules_includes_profile_layers(self, tmp_path, monkeypatch):
|
||||
def test_status_shows_module_info(self, tmp_path, monkeypatch, capsys):
|
||||
home = tmp_path / "home"
|
||||
home.mkdir()
|
||||
|
||||
dotfiles = tmp_path / "dotfiles"
|
||||
modules = tmp_path / "modules"
|
||||
|
||||
# Set up package with _module.yaml
|
||||
pkg_dir = dotfiles / "_shared" / "nvim"
|
||||
config_dir = pkg_dir / ".config" / "nvim"
|
||||
config_dir.mkdir(parents=True)
|
||||
(config_dir / "_module.yaml").write_text(yaml.dump({
|
||||
"source": "github:test/nvim-config",
|
||||
"ref": {"branch": "main"},
|
||||
}))
|
||||
|
||||
# Set up cloned module
|
||||
module_dir = modules / "_shared--nvim"
|
||||
module_dir.mkdir(parents=True)
|
||||
(module_dir / "init.lua").write_text("-- init")
|
||||
|
||||
monkeypatch.setattr(paths, "HOME", home)
|
||||
monkeypatch.setattr(paths, "DOTFILES_DIR", dotfiles)
|
||||
monkeypatch.setattr(paths, "MODULES_DIR", modules)
|
||||
monkeypatch.setattr(paths, "LINKED_STATE", tmp_path / "state" / "linked.json")
|
||||
|
||||
ctx = _make_ctx(tmp_path)
|
||||
svc = DotfilesService(ctx)
|
||||
svc.link()
|
||||
svc.status()
|
||||
output = capsys.readouterr().out
|
||||
assert "nvim" in output
|
||||
assert "branch:main" in output
|
||||
|
||||
def test_repos_list_shows_dotfiles_and_modules(self, tmp_path, monkeypatch, capsys):
|
||||
home = tmp_path / "home"
|
||||
home.mkdir()
|
||||
|
||||
dotfiles = tmp_path / "dotfiles"
|
||||
modules = tmp_path / "modules"
|
||||
|
||||
pkg_dir = dotfiles / "_shared" / "nvim"
|
||||
config_dir = pkg_dir / ".config" / "nvim"
|
||||
config_dir.mkdir(parents=True)
|
||||
(config_dir / "_module.yaml").write_text(yaml.dump({
|
||||
"source": "github:test/nvim-config",
|
||||
"ref": {"branch": "main"},
|
||||
}))
|
||||
|
||||
monkeypatch.setattr(paths, "HOME", home)
|
||||
monkeypatch.setattr(paths, "DOTFILES_DIR", dotfiles)
|
||||
monkeypatch.setattr(paths, "MODULES_DIR", modules)
|
||||
monkeypatch.setattr(paths, "LINKED_STATE", tmp_path / "state" / "linked.json")
|
||||
|
||||
ctx = _make_ctx(tmp_path)
|
||||
svc = DotfilesService(ctx)
|
||||
svc.repos_list()
|
||||
output = capsys.readouterr().out
|
||||
assert "dotfiles" in output
|
||||
assert "nvim" in output
|
||||
assert "module" in output
|
||||
|
||||
def test_repos_pull_includes_profile_module_repos(self, tmp_path, monkeypatch):
|
||||
home = tmp_path / "home"
|
||||
home.mkdir()
|
||||
dotfiles = tmp_path / "dotfiles"
|
||||
@@ -234,5 +286,150 @@ class TestDotfilesServiceLink:
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
DotfilesService(ctx).sync_modules()
|
||||
DotfilesService(ctx).repos_pull()
|
||||
assert any("linux-work--nvim" in " ".join(call) for call in runner.calls)
|
||||
|
||||
def test_repos_status_shows_repo_names(self, tmp_path, monkeypatch, capsys):
|
||||
home = tmp_path / "home"
|
||||
home.mkdir()
|
||||
|
||||
dotfiles = _setup_dotfiles(tmp_path, {
|
||||
"zsh": {".zshrc": "# zsh"},
|
||||
})
|
||||
|
||||
monkeypatch.setattr(paths, "HOME", home)
|
||||
monkeypatch.setattr(paths, "DOTFILES_DIR", dotfiles)
|
||||
monkeypatch.setattr(paths, "MODULES_DIR", tmp_path / "modules")
|
||||
monkeypatch.setattr(paths, "LINKED_STATE", tmp_path / "state" / "linked.json")
|
||||
|
||||
# Make dotfiles dir look like a git repo for status
|
||||
(dotfiles / ".git").mkdir()
|
||||
|
||||
runtime = SystemRuntime()
|
||||
runner = FakeRunner()
|
||||
runtime.runner = runner
|
||||
runtime.git.runner = runner
|
||||
ctx = FlowContext(
|
||||
config=AppConfig(),
|
||||
manifest={},
|
||||
platform=PlatformInfo(),
|
||||
console=Console(color=False),
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
DotfilesService(ctx).repos_status()
|
||||
output = capsys.readouterr().out
|
||||
assert "dotfiles" in output
|
||||
|
||||
def test_repos_push_calls_git_push(self, tmp_path, monkeypatch):
|
||||
home = tmp_path / "home"
|
||||
home.mkdir()
|
||||
|
||||
dotfiles = _setup_dotfiles(tmp_path, {
|
||||
"zsh": {".zshrc": "# zsh"},
|
||||
})
|
||||
|
||||
monkeypatch.setattr(paths, "HOME", home)
|
||||
monkeypatch.setattr(paths, "DOTFILES_DIR", dotfiles)
|
||||
monkeypatch.setattr(paths, "MODULES_DIR", tmp_path / "modules")
|
||||
monkeypatch.setattr(paths, "LINKED_STATE", tmp_path / "state" / "linked.json")
|
||||
|
||||
runtime = SystemRuntime()
|
||||
runner = FakeRunner()
|
||||
runtime.runner = runner
|
||||
runtime.git.runner = runner
|
||||
ctx = FlowContext(
|
||||
config=AppConfig(),
|
||||
manifest={},
|
||||
platform=PlatformInfo(),
|
||||
console=Console(color=False),
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
DotfilesService(ctx).repos_push()
|
||||
assert any("push" in " ".join(call) for call in runner.calls)
|
||||
|
||||
def test_repos_pull_dry_run_no_calls(self, tmp_path, monkeypatch, capsys):
|
||||
home = tmp_path / "home"
|
||||
home.mkdir()
|
||||
|
||||
dotfiles = _setup_dotfiles(tmp_path, {
|
||||
"zsh": {".zshrc": "# zsh"},
|
||||
})
|
||||
|
||||
monkeypatch.setattr(paths, "HOME", home)
|
||||
monkeypatch.setattr(paths, "DOTFILES_DIR", dotfiles)
|
||||
monkeypatch.setattr(paths, "MODULES_DIR", tmp_path / "modules")
|
||||
monkeypatch.setattr(paths, "LINKED_STATE", tmp_path / "state" / "linked.json")
|
||||
|
||||
runtime = SystemRuntime()
|
||||
runner = FakeRunner()
|
||||
runtime.runner = runner
|
||||
runtime.git.runner = runner
|
||||
ctx = FlowContext(
|
||||
config=AppConfig(),
|
||||
manifest={},
|
||||
platform=PlatformInfo(),
|
||||
console=Console(color=False),
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
DotfilesService(ctx).repos_pull(dry_run=True)
|
||||
output = capsys.readouterr().out
|
||||
assert "Would" in output
|
||||
# No git calls should be made in dry run
|
||||
assert not runner.calls
|
||||
|
||||
def test_status_filter_by_package(self, tmp_path, monkeypatch, capsys):
|
||||
home = tmp_path / "home"
|
||||
home.mkdir()
|
||||
|
||||
dotfiles = _setup_dotfiles(tmp_path, {
|
||||
"zsh": {".zshrc": "# zsh"},
|
||||
"git": {".gitconfig": "[user]"},
|
||||
})
|
||||
|
||||
monkeypatch.setattr(paths, "HOME", home)
|
||||
monkeypatch.setattr(paths, "DOTFILES_DIR", dotfiles)
|
||||
monkeypatch.setattr(paths, "MODULES_DIR", tmp_path / "modules")
|
||||
monkeypatch.setattr(paths, "LINKED_STATE", tmp_path / "state" / "linked.json")
|
||||
|
||||
ctx = _make_ctx(tmp_path)
|
||||
svc = DotfilesService(ctx)
|
||||
svc.link()
|
||||
capsys.readouterr() # discard link output
|
||||
svc.status(package_filter=["zsh"])
|
||||
output = capsys.readouterr().out
|
||||
assert "zsh" in output
|
||||
# Only zsh should appear, not git
|
||||
assert "_shared/git" not in output
|
||||
|
||||
def test_link_repairs_broken_symlinks(self, tmp_path, monkeypatch):
|
||||
home = tmp_path / "home"
|
||||
home.mkdir()
|
||||
|
||||
dotfiles = _setup_dotfiles(tmp_path, {
|
||||
"zsh": {".zshrc": "# zsh config"},
|
||||
})
|
||||
|
||||
monkeypatch.setattr(paths, "HOME", home)
|
||||
monkeypatch.setattr(paths, "DOTFILES_DIR", dotfiles)
|
||||
monkeypatch.setattr(paths, "MODULES_DIR", tmp_path / "modules")
|
||||
monkeypatch.setattr(paths, "LINKED_STATE", tmp_path / "state" / "linked.json")
|
||||
|
||||
ctx = _make_ctx(tmp_path)
|
||||
svc = DotfilesService(ctx)
|
||||
|
||||
# Link normally
|
||||
svc.link()
|
||||
assert (home / ".zshrc").is_symlink()
|
||||
|
||||
# Break the symlink by removing its target
|
||||
real_target = (home / ".zshrc").resolve()
|
||||
(home / ".zshrc").unlink()
|
||||
(home / ".zshrc").symlink_to("/nonexistent/path")
|
||||
|
||||
# Re-link should repair the broken symlink
|
||||
svc.link()
|
||||
assert (home / ".zshrc").is_symlink()
|
||||
assert (home / ".zshrc").resolve() == real_target.resolve()
|
||||
|
||||
Reference in New Issue
Block a user