feat: add all domain layers (dotfiles, packages, remote, containers)

- Dotfiles: models, module resolution, path resolution, link planning
- Packages: models, catalog parsing, resolution, install/remove planning
- Remote: target parsing, SSH command building
- Containers: image refs, mount resolution, container specs

All domain code is pure functions + frozen dataclasses. 88 tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-16 04:54:06 +02:00
parent 6bb41aa001
commit 31d7583b9a
26 changed files with 1894 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
"""Tests for dotfiles module resolution -- the core bug fix."""
from pathlib import Path
import pytest
from flow.core.errors import ConfigError
from flow.domain.dotfiles.modules import (
compute_mount_path,
module_cache_dir,
normalize_source,
parse_module_ref,
)
class TestComputeMountPath:
def test_nested_module(self):
"""_shared/nvim/.config/nvim/_module.yaml -> .config/nvim"""
result = compute_mount_path(
module_yaml=Path("/dots/_shared/nvim/.config/nvim/_module.yaml"),
package_dir=Path("/dots/_shared/nvim"),
)
assert result == Path(".config/nvim")
def test_root_level_module(self):
"""_shared/nvim/_module.yaml -> Path('.')"""
result = compute_mount_path(
module_yaml=Path("/dots/_shared/nvim/_module.yaml"),
package_dir=Path("/dots/_shared/nvim"),
)
assert result == Path(".")
def test_deeply_nested(self):
result = compute_mount_path(
module_yaml=Path("/dots/_shared/pkg/.config/a/b/c/_module.yaml"),
package_dir=Path("/dots/_shared/pkg"),
)
assert result == Path(".config/a/b/c")
class TestModuleCacheDir:
def test_simple_name(self):
result = module_cache_dir("_shared/nvim", Path("/home/x/.local/share/flow/modules"))
assert result == Path("/home/x/.local/share/flow/modules/_shared--nvim")
def test_profile_name(self):
result = module_cache_dir("linux-work/nvim", Path("/m"))
assert result == Path("/m/linux-work--nvim")
class TestNormalizeSource:
def test_github_shorthand(self):
assert normalize_source("github:org/repo") == "https://github.com/org/repo.git"
def test_full_url_passthrough(self):
assert normalize_source("https://example.com/repo.git") == "https://example.com/repo.git"
def test_ssh_passthrough(self):
assert normalize_source("git@github.com:org/repo.git") == "git@github.com:org/repo.git"
class TestParseModuleRef:
def test_branch_ref(self):
raw = {"source": "github:org/nvim-config", "ref": {"branch": "main"}}
ref = parse_module_ref(
raw, package_id="_shared/nvim",
mount_path=Path(".config/nvim"),
modules_base=Path("/modules"),
)
assert ref.source == "https://github.com/org/nvim-config.git"
assert ref.ref_type == "branch"
assert ref.ref_value == "main"
assert ref.mount_path == Path(".config/nvim")
assert ref.cache_dir == Path("/modules/_shared--nvim")
def test_tag_ref(self):
raw = {"source": "github:org/repo", "ref": {"tag": "v1.0"}}
ref = parse_module_ref(raw, "p/x", Path("."), Path("/m"))
assert ref.ref_type == "tag"
assert ref.ref_value == "v1.0"
def test_missing_source_raises(self):
with pytest.raises(ConfigError):
parse_module_ref({}, "p/x", Path("."), Path("/m"))
def test_missing_ref_raises(self):
raw = {"source": "github:org/repo"}
with pytest.raises(ConfigError):
parse_module_ref(raw, "p/x", Path("."), Path("/m"))
def test_ref_not_dict_raises(self):
raw = {"source": "github:org/repo", "ref": "main"}
with pytest.raises(ConfigError):
parse_module_ref(raw, "p/x", Path("."), Path("/m"))
def test_ambiguous_ref_raises(self):
raw = {"source": "github:org/repo", "ref": {"branch": "main", "tag": "v1"}}
with pytest.raises(ConfigError):
parse_module_ref(raw, "p/x", Path("."), Path("/m"))