8f11340b38
Per post_module_taxonomy_de_cruft_20260627 Phase 2 (FR7). Each
'from src.models import X' for a moved class is rewritten to
'from src.<destination> import X':
Ticket, Track, WorkerContext, TrackState, TrackMetadata,
ThinkingSegment, EMPTY_TRACK_STATE -> src.mma
ProjectContext, ProjectMeta, ProjectOutput, ProjectFiles,
ProjectScreenshots, ProjectDiscussion, EMPTY_PROJECT_CONTEXT -> src.project
FileItem, Preset, ContextPreset, ContextFileEntry,
NamedViewPreset -> src.project_files
Tool, ToolPreset -> src.tool_presets
BiasProfile -> src.tool_bias
TextEditorConfig, ExternalEditorConfig,
EMPTY_TEXT_EDITOR_CONFIG -> src.external_editor
Persona -> src.personas
WorkspaceProfile -> src.workspace_manager
MCPServerConfig, MCPConfiguration, VectorStoreConfig,
RAGConfig, load_mcp_config -> src.mcp_client
NOT touched (kept on src.models; Phase 3 or Phase 4 will move them):
GenerateRequest, ConfirmRequest, DEFAULT_TOOL_CATEGORIES, Metadata, PROVIDERS
Migration was performed by the one-time script
scripts/tier2/artifacts/post_module_taxonomy_de_cruft_20260627/migrate_imports.py
which uses a class-to-module map and re.sub() to rewrite each
'from src.models import X' line.
Total: 85 import lines rewritten across 71 files.
Note: this commit depends on the v2 SHIPPED work
(origin/tier2/module_taxonomy_refactor_20260627) being merged into
this branch NEXT. On master (without the v2 SHIPPED commits), the
destination modules do not exist and these imports would fail.
133 lines
4.3 KiB
Python
133 lines
4.3 KiB
Python
"""Tests for external editor integration."""
|
|
import pytest
|
|
from unittest.mock import patch, MagicMock
|
|
from src.external_editor import TextEditorConfig, ExternalEditorConfig
|
|
from src.external_editor import (
|
|
ExternalEditorLauncher,
|
|
get_default_launcher,
|
|
create_temp_modified_file,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def vscode_editor():
|
|
return TextEditorConfig(name="vscode", path="C:\\path\\to\\code.exe", diff_args=["--diff"])
|
|
|
|
|
|
@pytest.fixture
|
|
def notepadpp_editor():
|
|
return TextEditorConfig(name="notepad++", path="C:\\path\\to\\notepad++.exe", diff_args=["-multiInst", "-nosession"])
|
|
|
|
|
|
@pytest.fixture
|
|
def ext_config(vscode_editor, notepadpp_editor):
|
|
return ExternalEditorConfig(
|
|
editors={"vscode": vscode_editor, "notepad++": notepadpp_editor},
|
|
default_editor="vscode",
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def launcher(ext_config):
|
|
return ExternalEditorLauncher(ext_config)
|
|
|
|
|
|
class TestTextEditorConfig:
|
|
def test_from_dict_with_diff_args(self):
|
|
data = {"name": "vscode", "path": "C:\\code.exe", "diff_args": ["--diff"]}
|
|
editor = TextEditorConfig.from_dict(data)
|
|
assert editor.name == "vscode"
|
|
assert editor.path == "C:\\code.exe"
|
|
assert editor.diff_args == ["--diff"]
|
|
|
|
def test_from_dict_without_diff_args(self):
|
|
data = {"name": "vscode", "path": "C:\\code.exe"}
|
|
editor = TextEditorConfig.from_dict(data)
|
|
assert editor.diff_args == []
|
|
|
|
def test_to_dict(self, vscode_editor):
|
|
result = vscode_editor.to_dict()
|
|
assert result["name"] == "vscode"
|
|
assert result["path"] == "C:\\path\\to\\code.exe"
|
|
assert result["diff_args"] == ["--diff"]
|
|
|
|
|
|
class TestExternalEditorConfig:
|
|
def test_from_dict_with_string_editors(self):
|
|
data = {"editors": {"vscode": "C:\\code.exe"}, "default_editor": "vscode"}
|
|
config = ExternalEditorConfig.from_dict(data)
|
|
assert "vscode" in config.editors
|
|
assert config.editors["vscode"].path == "C:\\code.exe"
|
|
|
|
def test_from_dict_with_dict_editors(self, vscode_editor):
|
|
data = {"editors": {"vscode": {"name": "vscode", "path": "C:\\code.exe", "diff_args": ["--diff"]}}}
|
|
config = ExternalEditorConfig.from_dict(data)
|
|
assert config.editors["vscode"].diff_args == ["--diff"]
|
|
|
|
def test_get_default_returns_configured(self, ext_config):
|
|
result = ext_config.get_default()
|
|
assert result.name == "vscode"
|
|
|
|
def test_get_default_fallback_to_first(self):
|
|
config = ExternalEditorConfig(editors={"notepad++": TextEditorConfig(name="notepad++", path="C:\\npp.exe")})
|
|
result = config.get_default()
|
|
assert result.name == "notepad++"
|
|
|
|
def test_get_default_returns_none_when_empty(self):
|
|
config = ExternalEditorConfig(editors={})
|
|
assert config.get_default().name == ""
|
|
|
|
def test_to_dict(self, ext_config):
|
|
result = ext_config.to_dict()
|
|
assert result["default_editor"] == "vscode"
|
|
assert "vscode" in result["editors"]
|
|
|
|
|
|
class TestExternalEditorLauncher:
|
|
def test_get_editor_by_name(self, launcher):
|
|
editor = launcher.get_editor("notepad++")
|
|
assert editor.name == "notepad++"
|
|
|
|
def test_get_editor_returns_default(self, launcher):
|
|
editor = launcher.get_editor()
|
|
assert editor.name == "vscode"
|
|
|
|
def test_get_editor_unknown_name(self, launcher):
|
|
editor = launcher.get_editor("unknown")
|
|
assert editor.name == ""
|
|
|
|
def test_build_diff_command(self, launcher, vscode_editor):
|
|
cmd = launcher.build_diff_command(vscode_editor, "orig.txt", "mod.txt")
|
|
assert cmd == ["C:\\path\\to\\code.exe", "--diff", "orig.txt", "mod.txt"]
|
|
|
|
def test_launch_diff_missing_editor(self, launcher):
|
|
result = launcher.launch_diff_result("nonexistent", "orig.txt", "mod.txt")
|
|
assert not result.ok
|
|
assert result.data is None
|
|
|
|
@patch("subprocess.Popen")
|
|
def test_launch_diff_success(self, mock_popen, launcher):
|
|
mock_popen.return_value = MagicMock()
|
|
result = launcher.launch_diff_result("vscode", "orig.txt", "mod.txt")
|
|
assert result.ok
|
|
assert result.data is not None
|
|
mock_popen.assert_called_once()
|
|
|
|
@patch("subprocess.Popen")
|
|
def test_launch_diff_file_not_found(self, mock_popen, launcher):
|
|
mock_popen.side_effect = FileNotFoundError()
|
|
result = launcher.launch_diff_result("vscode", "orig.txt", "mod.txt")
|
|
assert not result.ok
|
|
assert result.data is None
|
|
|
|
|
|
class TestHelperFunctions:
|
|
def test_create_temp_modified_file(self):
|
|
content = "test content"
|
|
path = create_temp_modified_file(content)
|
|
assert path.endswith("_modified")
|
|
with open(path, encoding="utf-8") as f:
|
|
assert f.read() == content
|
|
import os
|
|
os.unlink(path)
|