"""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"])