309 lines
11 KiB
Python
309 lines
11 KiB
Python
"""Tests for the PathResolver module."""
|
|
|
|
import pytest
|
|
from pathlib import Path
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
from development_hub.paths import PathResolver, paths
|
|
from development_hub.settings import Settings
|
|
|
|
|
|
class TestPathResolver:
|
|
"""Test PathResolver path resolution."""
|
|
|
|
@pytest.fixture
|
|
def isolated_settings(self, tmp_path, monkeypatch):
|
|
"""Create isolated settings for testing."""
|
|
Settings._instance = None
|
|
PathResolver._instance = None
|
|
|
|
monkeypatch.setattr(Settings, "_settings_file", tmp_path / "settings.json")
|
|
monkeypatch.setattr(Settings, "_session_file", tmp_path / "session.json")
|
|
|
|
settings = Settings()
|
|
yield settings
|
|
|
|
Settings._instance = None
|
|
PathResolver._instance = None
|
|
|
|
def test_singleton_pattern(self):
|
|
"""PathResolver is a singleton."""
|
|
PathResolver._instance = None
|
|
|
|
resolver1 = PathResolver()
|
|
resolver2 = PathResolver()
|
|
|
|
assert resolver1 is resolver2
|
|
|
|
PathResolver._instance = None
|
|
|
|
def test_projects_root_from_settings(self, isolated_settings, tmp_path):
|
|
"""Projects root comes from settings."""
|
|
isolated_settings.project_search_paths = [str(tmp_path / "my-projects")]
|
|
|
|
resolver = PathResolver()
|
|
assert resolver.projects_root == tmp_path / "my-projects"
|
|
|
|
def test_projects_root_default(self, isolated_settings, tmp_path):
|
|
"""Projects root defaults to ~/Projects when not set."""
|
|
isolated_settings.project_search_paths = []
|
|
|
|
resolver = PathResolver()
|
|
assert resolver.projects_root == Path.home() / "Projects"
|
|
|
|
def test_docs_root_from_settings(self, isolated_settings, tmp_path):
|
|
"""Docs root comes from settings."""
|
|
isolated_settings.docs_mode = "standalone"
|
|
|
|
resolver = PathResolver()
|
|
docs_root = resolver.docs_root
|
|
|
|
assert "development-hub" in str(docs_root)
|
|
|
|
def test_project_docs_dir(self, isolated_settings, tmp_path):
|
|
"""Project docs dir returns docusaurus path."""
|
|
project_docs = tmp_path / "project-docs"
|
|
project_docs.mkdir()
|
|
|
|
isolated_settings.project_search_paths = [str(tmp_path)]
|
|
|
|
resolver = PathResolver()
|
|
assert resolver.project_docs_dir == project_docs
|
|
|
|
def test_project_docs_dir_none_when_not_exists(self, isolated_settings, tmp_path):
|
|
"""Project docs dir is None when not configured."""
|
|
isolated_settings.project_search_paths = [str(tmp_path)]
|
|
# No project-docs folder
|
|
|
|
resolver = PathResolver()
|
|
assert resolver.project_docs_dir is None
|
|
|
|
def test_progress_dir(self, isolated_settings):
|
|
"""Progress dir comes from settings."""
|
|
resolver = PathResolver()
|
|
progress = resolver.progress_dir
|
|
|
|
assert isinstance(progress, Path)
|
|
|
|
def test_build_script_when_exists(self, isolated_settings, tmp_path):
|
|
"""Build script returns path when it exists."""
|
|
project_docs = tmp_path / "project-docs"
|
|
scripts_dir = project_docs / "scripts"
|
|
scripts_dir.mkdir(parents=True)
|
|
|
|
build_script = scripts_dir / "build-public-docs.sh"
|
|
build_script.touch()
|
|
|
|
isolated_settings.project_search_paths = [str(tmp_path)]
|
|
|
|
resolver = PathResolver()
|
|
assert resolver.build_script == build_script
|
|
|
|
def test_build_script_none_when_not_exists(self, isolated_settings, tmp_path):
|
|
"""Build script is None when it doesn't exist."""
|
|
isolated_settings.project_search_paths = [str(tmp_path)]
|
|
|
|
resolver = PathResolver()
|
|
assert resolver.build_script is None
|
|
|
|
def test_project_docs_path(self, isolated_settings):
|
|
"""project_docs_path returns correct path for project."""
|
|
resolver = PathResolver()
|
|
|
|
path = resolver.project_docs_path("my-project")
|
|
|
|
assert path.name == "my-project"
|
|
assert "projects" in str(path)
|
|
|
|
def test_git_url_not_configured(self, isolated_settings):
|
|
"""git_url returns empty string when not configured."""
|
|
resolver = PathResolver()
|
|
|
|
assert resolver.git_url() == ""
|
|
assert resolver.git_url("owner", "repo") == ""
|
|
|
|
def test_git_url_configured(self, isolated_settings):
|
|
"""git_url returns correct URL when configured."""
|
|
isolated_settings.git_host_type = "github"
|
|
isolated_settings.git_host_url = "https://github.com"
|
|
isolated_settings.git_host_owner = "testowner"
|
|
|
|
resolver = PathResolver()
|
|
|
|
assert resolver.git_url() == "https://github.com/testowner"
|
|
assert resolver.git_url("testowner", "testrepo") == "https://github.com/testowner/testrepo"
|
|
|
|
def test_git_url_custom_owner(self, isolated_settings):
|
|
"""git_url can use custom owner."""
|
|
isolated_settings.git_host_type = "github"
|
|
isolated_settings.git_host_url = "https://github.com"
|
|
isolated_settings.git_host_owner = "default"
|
|
|
|
resolver = PathResolver()
|
|
|
|
assert resolver.git_url("custom", "repo") == "https://github.com/custom/repo"
|
|
|
|
def test_pages_url_not_configured(self, isolated_settings):
|
|
"""pages_url returns empty string when not configured."""
|
|
resolver = PathResolver()
|
|
|
|
assert resolver.pages_url() == ""
|
|
|
|
def test_pages_url_configured(self, isolated_settings):
|
|
"""pages_url returns correct URL when configured."""
|
|
isolated_settings.git_host_owner = "testowner"
|
|
isolated_settings.pages_url = "https://pages.example.com"
|
|
|
|
resolver = PathResolver()
|
|
|
|
assert "pages.example.com" in resolver.pages_url()
|
|
assert "testowner" in resolver.pages_url()
|
|
|
|
def test_is_docs_enabled(self, isolated_settings):
|
|
"""is_docs_enabled reflects settings."""
|
|
isolated_settings.docs_mode = "standalone"
|
|
|
|
resolver = PathResolver()
|
|
assert resolver.is_docs_enabled == True
|
|
|
|
def test_is_git_configured(self, isolated_settings):
|
|
"""is_git_configured reflects settings."""
|
|
resolver = PathResolver()
|
|
assert resolver.is_git_configured == False
|
|
|
|
isolated_settings.git_host_type = "github"
|
|
isolated_settings.git_host_url = "https://github.com"
|
|
isolated_settings.git_host_owner = "user"
|
|
|
|
assert resolver.is_git_configured == True
|
|
|
|
def test_effective_docs_mode(self, isolated_settings, tmp_path):
|
|
"""effective_docs_mode reflects settings."""
|
|
isolated_settings.project_search_paths = [str(tmp_path)]
|
|
isolated_settings.docs_mode = "auto"
|
|
|
|
resolver = PathResolver()
|
|
|
|
# No project-docs, so standalone
|
|
assert resolver.effective_docs_mode == "standalone"
|
|
|
|
# Create project-docs
|
|
(tmp_path / "project-docs").mkdir()
|
|
assert resolver.effective_docs_mode == "project-docs"
|
|
|
|
|
|
class TestCmdForgeAvailability:
|
|
"""Test CmdForge availability checks."""
|
|
|
|
@pytest.fixture
|
|
def isolated_settings(self, tmp_path, monkeypatch):
|
|
"""Create isolated settings for testing."""
|
|
Settings._instance = None
|
|
PathResolver._instance = None
|
|
|
|
monkeypatch.setattr(Settings, "_settings_file", tmp_path / "settings.json")
|
|
monkeypatch.setattr(Settings, "_session_file", tmp_path / "session.json")
|
|
|
|
settings = Settings()
|
|
# Use isolated paths to prevent finding real CmdForge
|
|
settings.project_search_paths = [str(tmp_path)]
|
|
yield settings
|
|
|
|
Settings._instance = None
|
|
PathResolver._instance = None
|
|
|
|
def test_cmdforge_path_explicit(self, isolated_settings, tmp_path):
|
|
"""CmdForge path from explicit setting."""
|
|
cmdforge_dir = tmp_path / "CmdForge"
|
|
cmdforge_dir.mkdir()
|
|
|
|
isolated_settings.cmdforge_path = cmdforge_dir
|
|
|
|
resolver = PathResolver()
|
|
assert resolver.cmdforge_path == cmdforge_dir
|
|
|
|
def test_cmdforge_executable_from_venv(self, isolated_settings, tmp_path):
|
|
"""CmdForge executable found in venv."""
|
|
cmdforge_dir = tmp_path / "CmdForge"
|
|
venv_bin = cmdforge_dir / ".venv" / "bin"
|
|
venv_bin.mkdir(parents=True)
|
|
|
|
cmdforge_exe = venv_bin / "cmdforge"
|
|
cmdforge_exe.touch()
|
|
|
|
isolated_settings.cmdforge_path = cmdforge_dir
|
|
|
|
resolver = PathResolver()
|
|
assert resolver.cmdforge_executable == cmdforge_exe
|
|
|
|
@patch("development_hub.paths.shutil.which")
|
|
def test_cmdforge_executable_from_path(self, mock_which, isolated_settings, tmp_path):
|
|
"""CmdForge executable found in PATH when not in explicit location."""
|
|
mock_which.return_value = "/usr/local/bin/cmdforge"
|
|
# Ensure no explicit cmdforge path is set
|
|
isolated_settings.cmdforge_path = None
|
|
|
|
resolver = PathResolver()
|
|
exe = resolver.cmdforge_executable
|
|
|
|
assert exe == Path("/usr/local/bin/cmdforge")
|
|
|
|
@patch("development_hub.paths.shutil.which")
|
|
def test_cmdforge_executable_not_found(self, mock_which, isolated_settings, tmp_path):
|
|
"""CmdForge executable None when not found."""
|
|
mock_which.return_value = None
|
|
# Ensure no explicit cmdforge path is set
|
|
isolated_settings.cmdforge_path = None
|
|
|
|
resolver = PathResolver()
|
|
assert resolver.cmdforge_executable is None
|
|
|
|
@patch("development_hub.paths.shutil.which")
|
|
def test_is_cmdforge_available_false(self, mock_which, isolated_settings, tmp_path):
|
|
"""is_cmdforge_available is False when not found."""
|
|
mock_which.return_value = None
|
|
isolated_settings.cmdforge_path = None
|
|
|
|
resolver = PathResolver()
|
|
assert resolver.is_cmdforge_available == False
|
|
|
|
@patch("development_hub.paths.shutil.which")
|
|
def test_is_cmdforge_available_true(self, mock_which, isolated_settings, tmp_path):
|
|
"""is_cmdforge_available is True when found in PATH."""
|
|
mock_which.return_value = "/usr/bin/cmdforge"
|
|
isolated_settings.cmdforge_path = None
|
|
|
|
resolver = PathResolver()
|
|
assert resolver.is_cmdforge_available == True
|
|
|
|
|
|
class TestGlobalPathsInstance:
|
|
"""Test the global paths singleton instance."""
|
|
|
|
def test_paths_is_path_resolver(self):
|
|
"""Global paths is a PathResolver instance."""
|
|
assert isinstance(paths, PathResolver)
|
|
|
|
def test_paths_has_expected_properties(self):
|
|
"""Global paths has expected properties."""
|
|
assert hasattr(paths, "projects_root")
|
|
assert hasattr(paths, "docs_root")
|
|
assert hasattr(paths, "project_docs_dir")
|
|
assert hasattr(paths, "progress_dir")
|
|
assert hasattr(paths, "build_script")
|
|
assert hasattr(paths, "cmdforge_path")
|
|
assert hasattr(paths, "cmdforge_executable")
|
|
assert hasattr(paths, "is_docs_enabled")
|
|
assert hasattr(paths, "is_cmdforge_available")
|
|
assert hasattr(paths, "is_git_configured")
|
|
|
|
def test_paths_has_expected_methods(self):
|
|
"""Global paths has expected methods."""
|
|
assert callable(paths.project_docs_path)
|
|
assert callable(paths.git_url)
|
|
assert callable(paths.pages_url)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"])
|