216 lines
7.2 KiB
Python
216 lines
7.2 KiB
Python
"""Tests for self-hosting flow config from dotfiles repository."""
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
import yaml
|
|
|
|
from flow.core import paths as paths_module
|
|
from flow.core.config import load_config, load_manifest
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_paths(tmp_path, monkeypatch):
|
|
"""Mock path constants for testing."""
|
|
config_dir = tmp_path / "config"
|
|
dotfiles_dir = tmp_path / "dotfiles"
|
|
|
|
config_dir.mkdir()
|
|
dotfiles_dir.mkdir()
|
|
|
|
test_paths = {
|
|
"config_dir": config_dir,
|
|
"dotfiles_dir": dotfiles_dir,
|
|
"local_config": config_dir / "config",
|
|
"local_manifest": config_dir / "manifest.yaml",
|
|
"dotfiles_config": dotfiles_dir / "flow" / ".config" / "flow" / "config",
|
|
"dotfiles_manifest": dotfiles_dir / "flow" / ".config" / "flow" / "manifest.yaml",
|
|
}
|
|
|
|
# Patch at the paths module level
|
|
monkeypatch.setattr(paths_module, "CONFIG_FILE", test_paths["local_config"])
|
|
monkeypatch.setattr(paths_module, "MANIFEST_FILE", test_paths["local_manifest"])
|
|
monkeypatch.setattr(paths_module, "DOTFILES_CONFIG", test_paths["dotfiles_config"])
|
|
monkeypatch.setattr(paths_module, "DOTFILES_MANIFEST", test_paths["dotfiles_manifest"])
|
|
|
|
return test_paths
|
|
|
|
|
|
def test_load_manifest_priority_dotfiles_first(mock_paths):
|
|
"""Test that dotfiles manifest takes priority over local."""
|
|
# Create both manifests
|
|
local_manifest = mock_paths["local_manifest"]
|
|
dotfiles_manifest = mock_paths["dotfiles_manifest"]
|
|
|
|
local_manifest.write_text("profiles:\n local:\n os: linux")
|
|
|
|
dotfiles_manifest.parent.mkdir(parents=True)
|
|
dotfiles_manifest.write_text("profiles:\n dotfiles:\n os: macos")
|
|
|
|
# Should load from dotfiles
|
|
manifest = load_manifest()
|
|
assert "dotfiles" in manifest.get("profiles", {})
|
|
assert "local" not in manifest.get("profiles", {})
|
|
|
|
|
|
def test_load_manifest_fallback_to_local(mock_paths):
|
|
"""Test fallback to local manifest when dotfiles doesn't exist."""
|
|
local_manifest = mock_paths["local_manifest"]
|
|
local_manifest.write_text("profiles:\n local:\n os: linux")
|
|
|
|
# Dotfiles manifest doesn't exist
|
|
manifest = load_manifest()
|
|
assert "local" in manifest.get("profiles", {})
|
|
|
|
|
|
def test_load_manifest_empty_when_none_exist(mock_paths):
|
|
"""Test empty dict returned when no manifests exist."""
|
|
manifest = load_manifest()
|
|
assert manifest == {}
|
|
|
|
|
|
def test_load_config_priority_dotfiles_first(mock_paths):
|
|
"""Test that dotfiles config takes priority over local."""
|
|
local_config = mock_paths["local_config"]
|
|
dotfiles_config = mock_paths["dotfiles_config"]
|
|
|
|
# Create local config
|
|
local_config.write_text(
|
|
"[repository]\n"
|
|
"dotfiles_url = https://github.com/user/dotfiles-local.git\n"
|
|
)
|
|
|
|
# Create dotfiles config
|
|
dotfiles_config.parent.mkdir(parents=True)
|
|
dotfiles_config.write_text(
|
|
"[repository]\n"
|
|
"dotfiles_url = https://github.com/user/dotfiles-from-repo.git\n"
|
|
)
|
|
|
|
# Should load from dotfiles
|
|
config = load_config()
|
|
assert "dotfiles-from-repo" in config.dotfiles_url
|
|
|
|
|
|
def test_load_config_fallback_to_local(mock_paths):
|
|
"""Test fallback to local config when dotfiles doesn't exist."""
|
|
local_config = mock_paths["local_config"]
|
|
local_config.write_text(
|
|
"[repository]\n"
|
|
"dotfiles_url = https://github.com/user/dotfiles-local.git\n"
|
|
)
|
|
|
|
# Dotfiles config doesn't exist
|
|
config = load_config()
|
|
assert "dotfiles-local" in config.dotfiles_url
|
|
|
|
|
|
def test_load_config_empty_when_none_exist(mock_paths):
|
|
"""Test default config returned when no configs exist."""
|
|
config = load_config()
|
|
assert config.dotfiles_url == ""
|
|
assert config.dotfiles_branch == "main"
|
|
|
|
|
|
def test_self_hosting_workflow(tmp_path, monkeypatch):
|
|
"""Test complete self-hosting workflow.
|
|
|
|
Simulates:
|
|
1. User has dotfiles repo with flow config
|
|
2. Flow links its own config from dotfiles
|
|
3. Flow reads from self-hosted location
|
|
"""
|
|
# Setup paths
|
|
home = tmp_path / "home"
|
|
dotfiles = tmp_path / "dotfiles"
|
|
home.mkdir()
|
|
dotfiles.mkdir()
|
|
|
|
# Create flow package in dotfiles
|
|
flow_pkg = dotfiles / "flow" / ".config" / "flow"
|
|
flow_pkg.mkdir(parents=True)
|
|
|
|
# Create manifest in dotfiles
|
|
manifest_content = {
|
|
"profiles": {
|
|
"test-env": {
|
|
"os": "linux",
|
|
"packages": {"standard": ["git", "vim"]},
|
|
}
|
|
}
|
|
}
|
|
(flow_pkg / "manifest.yaml").write_text(yaml.dump(manifest_content))
|
|
|
|
# Create config in dotfiles
|
|
(flow_pkg / "config").write_text(
|
|
"[repository]\n"
|
|
"dotfiles_url = https://github.com/user/dotfiles.git\n"
|
|
)
|
|
|
|
# Mock paths to use our temp directories
|
|
monkeypatch.setattr(paths_module, "DOTFILES_MANIFEST", flow_pkg / "manifest.yaml")
|
|
monkeypatch.setattr(paths_module, "DOTFILES_CONFIG", flow_pkg / "config")
|
|
monkeypatch.setattr(paths_module, "MANIFEST_FILE", home / ".config" / "devflow" / "manifest.yaml")
|
|
monkeypatch.setattr(paths_module, "CONFIG_FILE", home / ".config" / "devflow" / "config")
|
|
|
|
# Load config and manifest - should come from dotfiles
|
|
manifest = load_manifest()
|
|
config = load_config()
|
|
|
|
assert "test-env" in manifest.get("profiles", {})
|
|
assert "github.com/user/dotfiles.git" in config.dotfiles_url
|
|
|
|
|
|
def test_manifest_cascade_with_symlink(tmp_path, monkeypatch):
|
|
"""Test that loading works correctly when symlink is used."""
|
|
# Setup
|
|
dotfiles = tmp_path / "dotfiles"
|
|
home_config = tmp_path / "home" / ".config" / "flow"
|
|
flow_pkg = dotfiles / "flow" / ".config" / "flow"
|
|
|
|
flow_pkg.mkdir(parents=True)
|
|
home_config.mkdir(parents=True)
|
|
|
|
# Create manifest in dotfiles
|
|
manifest_content = {"profiles": {"from-dotfiles": {"os": "linux"}}}
|
|
(flow_pkg / "manifest.yaml").write_text(yaml.dump(manifest_content))
|
|
|
|
# Create symlink from home config to dotfiles
|
|
manifest_link = home_config / "manifest.yaml"
|
|
manifest_link.symlink_to(flow_pkg / "manifest.yaml")
|
|
|
|
# Mock paths
|
|
monkeypatch.setattr(paths_module, "DOTFILES_MANIFEST", flow_pkg / "manifest.yaml")
|
|
monkeypatch.setattr(paths_module, "MANIFEST_FILE", manifest_link)
|
|
|
|
# Load - should work through symlink
|
|
manifest = load_manifest()
|
|
assert "from-dotfiles" in manifest.get("profiles", {})
|
|
|
|
|
|
def test_config_priority_documentation(mock_paths):
|
|
"""Document the config loading priority for users."""
|
|
# This test serves as documentation of the cascade behavior
|
|
|
|
# Priority 1: Dotfiles repo (self-hosted)
|
|
dotfiles_manifest = mock_paths["dotfiles_manifest"]
|
|
dotfiles_manifest.parent.mkdir(parents=True)
|
|
dotfiles_manifest.write_text("profiles:\n priority-1: {}")
|
|
|
|
manifest = load_manifest()
|
|
assert "priority-1" in manifest.get("profiles", {})
|
|
|
|
# If we remove dotfiles, falls back to Priority 2: Local override
|
|
dotfiles_manifest.unlink()
|
|
local_manifest = mock_paths["local_manifest"]
|
|
local_manifest.write_text("profiles:\n priority-2: {}")
|
|
|
|
manifest = load_manifest()
|
|
assert "priority-2" in manifest.get("profiles", {})
|
|
|
|
# If neither exists, Priority 3: Empty fallback
|
|
local_manifest.unlink()
|
|
manifest = load_manifest()
|
|
assert manifest == {}
|