development-hub/tests/test_settings.py

536 lines
18 KiB
Python

"""Tests for settings and session serialization."""
import json
import pytest
from pathlib import Path
from development_hub.settings import Settings
class TestSettingsPersistence:
"""Test settings persistence and round-trips."""
@pytest.fixture
def isolated_settings(self, tmp_path, monkeypatch):
"""Create an isolated Settings instance with temp files."""
# Reset singleton
Settings._instance = None
# Point to temp directory
settings_file = tmp_path / "settings.json"
session_file = tmp_path / "session.json"
monkeypatch.setattr(Settings, "_settings_file", settings_file)
monkeypatch.setattr(Settings, "_session_file", session_file)
settings = Settings()
yield settings
# Clean up singleton
Settings._instance = None
def test_default_values(self, isolated_settings):
"""Settings have sensible defaults."""
settings = isolated_settings
assert settings.deploy_docs_after_creation == True
assert settings.preferred_editor == "auto"
assert settings.auto_start_docs_server == True
assert isinstance(settings.project_search_paths, list)
assert isinstance(settings.project_ignore_folders, list)
def test_set_and_get(self, isolated_settings):
"""Values can be set and retrieved."""
settings = isolated_settings
settings.preferred_editor = "pycharm"
assert settings.preferred_editor == "pycharm"
settings.deploy_docs_after_creation = False
assert settings.deploy_docs_after_creation == False
def test_persistence_round_trip(self, isolated_settings, tmp_path):
"""Settings persist across instances."""
settings = isolated_settings
# Modify settings
settings.preferred_editor = "code"
settings.auto_start_docs_server = False
settings.set("custom_key", "custom_value")
# Reset singleton and create new instance
Settings._instance = None
new_settings = Settings()
assert new_settings.preferred_editor == "code"
assert new_settings.auto_start_docs_server == False
assert new_settings.get("custom_key") == "custom_value"
def test_file_created_on_save(self, isolated_settings, tmp_path):
"""Settings file is created when saving."""
settings = isolated_settings
settings.preferred_editor = "xed"
# Check file exists
assert Settings._settings_file.exists()
def test_invalid_json_handled(self, isolated_settings, tmp_path):
"""Invalid JSON in settings file doesn't crash."""
# Write invalid JSON
Settings._settings_file.parent.mkdir(parents=True, exist_ok=True)
Settings._settings_file.write_text("{ invalid json }")
# Reset and reload
Settings._instance = None
settings = Settings()
# Should fall back to defaults
assert settings.preferred_editor == "auto"
class TestSessionSerialization:
"""Test session state serialization."""
@pytest.fixture
def isolated_settings(self, tmp_path, monkeypatch):
"""Create an isolated Settings instance with temp files."""
Settings._instance = None
settings_file = tmp_path / "settings.json"
session_file = tmp_path / "session.json"
monkeypatch.setattr(Settings, "_settings_file", settings_file)
monkeypatch.setattr(Settings, "_session_file", session_file)
settings = Settings()
yield settings
Settings._instance = None
def test_save_load_session_round_trip(self, isolated_settings):
"""Session state round-trips correctly."""
settings = isolated_settings
state = {
"window_geometry": {"x": 100, "y": 200, "width": 800, "height": 600},
"splitter_sizes": [300, 500],
"panes": [
{
"tabs": [
{"type": "terminal", "cwd": "/home/user/project", "title": "Terminal 1"},
{"type": "dashboard", "project_key": "my-project"},
],
"active_tab": 0,
}
],
"active_pane": 0,
}
settings.save_session(state)
loaded = settings.load_session()
assert loaded == state
def test_save_session_creates_directory(self, isolated_settings, tmp_path):
"""Session save creates config directory if needed."""
settings = isolated_settings
# Ensure parent doesn't exist
if Settings._session_file.parent.exists():
import shutil
shutil.rmtree(Settings._session_file.parent)
state = {"test": True}
settings.save_session(state)
assert Settings._session_file.exists()
def test_load_empty_session(self, isolated_settings):
"""Loading non-existent session returns empty dict."""
settings = isolated_settings
loaded = settings.load_session()
assert loaded == {}
def test_load_invalid_session(self, isolated_settings):
"""Invalid JSON in session returns empty dict."""
settings = isolated_settings
Settings._session_file.parent.mkdir(parents=True, exist_ok=True)
Settings._session_file.write_text("not valid json")
loaded = settings.load_session()
assert loaded == {}
def test_session_preserves_types(self, isolated_settings):
"""Session preserves various data types."""
settings = isolated_settings
state = {
"string": "hello",
"number": 42,
"float": 3.14,
"bool": True,
"none": None,
"list": [1, 2, 3],
"nested": {"a": {"b": "c"}},
}
settings.save_session(state)
loaded = settings.load_session()
assert loaded["string"] == "hello"
assert loaded["number"] == 42
assert loaded["float"] == 3.14
assert loaded["bool"] == True
assert loaded["none"] is None
assert loaded["list"] == [1, 2, 3]
assert loaded["nested"]["a"]["b"] == "c"
def test_session_handles_unicode(self, isolated_settings):
"""Session handles unicode characters."""
settings = isolated_settings
state = {
"emoji": "Test",
"japanese": "Hello",
"symbols": "angle bracket < > quotes \" '",
}
settings.save_session(state)
loaded = settings.load_session()
assert loaded == state
class TestGitHostSettings:
"""Test git hosting configuration."""
@pytest.fixture
def isolated_settings(self, tmp_path, monkeypatch):
"""Create an isolated Settings instance."""
Settings._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
def test_git_not_configured_by_default(self, isolated_settings):
"""Git is not configured initially."""
settings = isolated_settings
assert settings.is_git_configured == False
def test_git_configured_when_all_set(self, isolated_settings):
"""Git is configured when all required fields are set."""
settings = isolated_settings
settings.git_host_type = "gitea"
settings.git_host_url = "https://gitea.example.com"
settings.git_host_owner = "myuser"
assert settings.is_git_configured == True
def test_git_not_configured_if_missing_field(self, isolated_settings):
"""Git not configured if any required field is missing."""
settings = isolated_settings
settings.git_host_type = "github"
settings.git_host_url = "https://github.com"
# Missing: git_host_owner
assert settings.is_git_configured == False
class TestDocsModeSettings:
"""Test documentation mode configuration."""
@pytest.fixture
def isolated_settings(self, tmp_path, monkeypatch):
"""Create an isolated Settings instance."""
Settings._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
def test_default_docs_mode(self, isolated_settings):
"""Default docs mode is auto."""
settings = isolated_settings
assert settings.docs_mode == "auto"
def test_set_docs_mode(self, isolated_settings):
"""Can set docs mode."""
settings = isolated_settings
settings.docs_mode = "standalone"
assert settings.docs_mode == "standalone"
settings.docs_mode = "project-docs"
assert settings.docs_mode == "project-docs"
def test_effective_docs_mode_standalone_when_no_project_docs(self, isolated_settings, tmp_path):
"""Effective mode is standalone when project-docs doesn't exist."""
settings = isolated_settings
settings.project_search_paths = [str(tmp_path)]
# No project-docs folder exists
assert settings.effective_docs_mode == "standalone"
def test_effective_docs_mode_project_docs_when_exists(self, isolated_settings, tmp_path):
"""Effective mode is project-docs when folder exists."""
settings = isolated_settings
settings.project_search_paths = [str(tmp_path)]
# Create project-docs folder
(tmp_path / "project-docs").mkdir()
assert settings.effective_docs_mode == "project-docs"
def test_explicit_mode_overrides_auto(self, isolated_settings, tmp_path):
"""Explicit mode setting overrides auto-detection."""
settings = isolated_settings
settings.project_search_paths = [str(tmp_path)]
# Create project-docs (would trigger project-docs mode in auto)
(tmp_path / "project-docs").mkdir()
# But explicitly set to standalone
settings.docs_mode = "standalone"
assert settings.effective_docs_mode == "standalone"
def test_docs_root_standalone_mode(self, isolated_settings):
"""Docs root in standalone mode uses local share."""
settings = isolated_settings
settings.docs_mode = "standalone"
docs_root = settings.docs_root
assert ".local/share/development-hub" in str(docs_root)
def test_docusaurus_path_property(self, isolated_settings, tmp_path):
"""Can set and get docusaurus path."""
settings = isolated_settings
settings.docusaurus_path = tmp_path / "my-docs"
assert settings.docusaurus_path == tmp_path / "my-docs"
def test_pages_url_property(self, isolated_settings):
"""Can set and get pages URL."""
settings = isolated_settings
settings.pages_url = "https://pages.example.com"
assert settings.pages_url == "https://pages.example.com"
def test_pages_url_derived_from_gitea(self, isolated_settings):
"""Pages URL can be derived from gitea URL."""
settings = isolated_settings
settings.git_host_type = "gitea"
settings.git_host_url = "https://gitea.example.com"
# When not explicitly set, should derive
assert "pages.example.com" in settings.pages_url
def test_is_docs_enabled_standalone(self, isolated_settings):
"""Docs are always enabled in standalone mode."""
settings = isolated_settings
settings.docs_mode = "standalone"
assert settings.is_docs_enabled == True
def test_cmdforge_path_property(self, isolated_settings, tmp_path):
"""Can set and get cmdforge path."""
settings = isolated_settings
settings.cmdforge_path = tmp_path / "CmdForge"
assert settings.cmdforge_path == tmp_path / "CmdForge"
def test_progress_dir_property(self, isolated_settings, tmp_path):
"""Can set and get progress directory."""
settings = isolated_settings
settings.progress_dir = tmp_path / "progress"
assert settings.progress_dir == tmp_path / "progress"
class TestWorkspaceExportImport:
"""Test workspace file export/import."""
@pytest.fixture
def isolated_settings(self, tmp_path, monkeypatch):
"""Create an isolated Settings instance."""
Settings._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
def test_export_workspace_creates_file(self, isolated_settings, tmp_path):
"""Export creates a YAML workspace file."""
settings = isolated_settings
workspace_path = tmp_path / "workspace.yaml"
settings.export_workspace(workspace_path)
assert workspace_path.exists()
def test_export_workspace_contains_required_fields(self, isolated_settings, tmp_path):
"""Exported workspace contains required fields."""
import yaml
settings = isolated_settings
settings.project_search_paths = [str(tmp_path / "projects")]
workspace_path = tmp_path / "workspace.yaml"
settings.export_workspace(workspace_path)
with open(workspace_path) as f:
workspace = yaml.safe_load(f)
assert "name" in workspace
assert "version" in workspace
assert workspace["version"] == 1
assert "paths" in workspace
assert "projects_root" in workspace["paths"]
assert "documentation" in workspace
assert "features" in workspace
def test_export_includes_git_hosting_when_configured(self, isolated_settings, tmp_path):
"""Exported workspace includes git hosting when configured."""
import yaml
settings = isolated_settings
settings.git_host_type = "github"
settings.git_host_url = "https://github.com"
settings.git_host_owner = "testuser"
workspace_path = tmp_path / "workspace.yaml"
settings.export_workspace(workspace_path)
with open(workspace_path) as f:
workspace = yaml.safe_load(f)
assert "git_hosting" in workspace
assert workspace["git_hosting"]["type"] == "github"
assert workspace["git_hosting"]["url"] == "https://github.com"
assert workspace["git_hosting"]["owner"] == "testuser"
def test_import_workspace_sets_values(self, isolated_settings, tmp_path):
"""Import workspace sets settings values."""
import yaml
settings = isolated_settings
workspace = {
"name": "Test Workspace",
"version": 1,
"paths": {
"projects_root": str(tmp_path / "my-projects")
},
"documentation": {
"mode": "standalone"
}
}
workspace_path = tmp_path / "workspace.yaml"
with open(workspace_path, "w") as f:
yaml.dump(workspace, f)
results = settings.import_workspace(workspace_path)
assert "projects_root" in results["imported"]
assert settings.project_search_paths == [str(tmp_path / "my-projects")]
assert settings.docs_mode == "standalone"
def test_import_workspace_sets_git_hosting(self, isolated_settings, tmp_path):
"""Import workspace sets git hosting values."""
import yaml
settings = isolated_settings
workspace = {
"name": "Test",
"version": 1,
"paths": {"projects_root": str(tmp_path)},
"git_hosting": {
"type": "gitea",
"url": "https://git.example.com",
"owner": "myorg",
"pages_url": "https://pages.example.com"
}
}
workspace_path = tmp_path / "workspace.yaml"
with open(workspace_path, "w") as f:
yaml.dump(workspace, f)
settings.import_workspace(workspace_path)
assert settings.git_host_type == "gitea"
assert settings.git_host_url == "https://git.example.com"
assert settings.git_host_owner == "myorg"
assert settings.pages_url == "https://pages.example.com"
def test_import_marks_setup_completed(self, isolated_settings, tmp_path):
"""Import workspace marks setup as completed."""
import yaml
settings = isolated_settings
workspace = {
"name": "Test",
"version": 1,
"paths": {"projects_root": str(tmp_path)}
}
workspace_path = tmp_path / "workspace.yaml"
with open(workspace_path, "w") as f:
yaml.dump(workspace, f)
settings.import_workspace(workspace_path)
assert settings.get("setup_completed") == True
def test_export_import_round_trip(self, isolated_settings, tmp_path):
"""Export and import preserves settings."""
settings = isolated_settings
# Configure settings
settings.project_search_paths = [str(tmp_path / "projects")]
settings.docs_mode = "project-docs"
settings.git_host_type = "gitlab"
settings.git_host_url = "https://gitlab.com"
settings.git_host_owner = "myuser"
# Export
workspace_path = tmp_path / "workspace.yaml"
settings.export_workspace(workspace_path)
# Reset settings
Settings._instance = None
new_settings = Settings()
# Import
new_settings.import_workspace(workspace_path)
assert new_settings.project_search_paths == [str(tmp_path / "projects")]
assert new_settings.docs_mode == "project-docs"
assert new_settings.git_host_type == "gitlab"
assert new_settings.git_host_url == "https://gitlab.com"
assert new_settings.git_host_owner == "myuser"
if __name__ == "__main__":
pytest.main([__file__, "-v"])