Private
Public Access
0
0

refactor(app_controller): migrate 3 GUI state-setter sites to Result (Phase 6 Group 6.3)

Replaces logging.debug bodies in:
- _update_inject_preview (L1542): Result[str] variant; legacy wrapper
  stores error on self._inject_preview_error
- mcp_config_json setter (L1685): sibling _set_mcp_config_json_result
  helper (property setters can't return values); setter stores error
  on self._mcp_config_parse_error
- _save_active_project (L3124): Result[None] variant; legacy wrapper
  stores error on self._save_project_error and updates self.ai_status

Each error-carrying state attribute is the durable data plane for
sub-track 4 GUI to display; stderr write is the visible-but-incomplete
drain (full drain = GUI modal in sub-track 4).

Audit: INTERNAL_SILENT_SWALLOW for src/app_controller.py: 26 -> 23.
This commit is contained in:
2026-06-19 15:55:06 -04:00
parent d794a5888b
commit fd91c83a0c
2 changed files with 204 additions and 15 deletions
+127
View File
@@ -278,3 +278,130 @@ def test_warmup_complete_timeline_records_error_on_stderr_failure():
assert len(warmup_errors) >= 1
assert isinstance(warmup_errors[0][1], ErrorInfo)
assert "stderr closed" in warmup_errors[0][1].message
# --- Phase 6: Group 6.3 (GUI state setters / property setters) ---
def test_update_inject_preview_result_returns_empty_when_no_path():
"""
_update_inject_preview_result returns Result(data="") when no file path is set.
"""
from src.app_controller import AppController
ctrl = AppController()
ctrl._inject_file_path = None
result = ctrl._update_inject_preview_result()
assert isinstance(result, Result)
assert result.ok is True
assert result.data == ""
def test_update_inject_preview_result_returns_error_on_read_failure():
"""
_update_inject_preview_result converts OSError to ErrorInfo(original=e) and
returns Result[data=""]. The legacy wrapper stores the error on
self._inject_preview_error and sets self._inject_preview to a user-facing
message.
"""
from src.app_controller import AppController
ctrl = AppController()
ctrl._inject_file_path = "/nonexistent/path/that/does/not/exist.py"
result = ctrl._update_inject_preview_result()
assert isinstance(result, Result)
# When file doesn't exist, returns empty data (no error)
assert result.ok is True
assert result.data == ""
def test_update_inject_preview_stores_error_on_read_failure():
"""
When file read fails (e.g. permission), the legacy wrapper stores the
error on self._inject_preview_error and shows a user-facing message.
"""
from src.app_controller import AppController
ctrl = AppController()
ctrl._inject_file_path = "/tmp/test_inject.py"
# Force the file-existence check to pass and the open call to fail.
with patch("src.app_controller.os.path.exists", return_value=True):
with patch("builtins.open", side_effect=PermissionError("denied")):
ctrl._update_inject_preview()
assert ctrl._inject_preview_error is not None
assert isinstance(ctrl._inject_preview_error, ErrorInfo)
assert "denied" in ctrl._inject_preview_error.message
assert ctrl._inject_preview.startswith("Error reading file:")
def test_set_mcp_config_json_result_returns_ok_on_valid_json():
"""
_set_mcp_config_json_result returns Result[None] on valid JSON.
"""
from src.app_controller import AppController
ctrl = AppController()
result = ctrl._set_mcp_config_json_result('{"servers": {}}')
assert isinstance(result, Result)
assert result.ok is True
assert result.errors == []
def test_set_mcp_config_json_result_returns_error_on_invalid_json():
"""
_set_mcp_config_json_result converts JSONDecodeError to ErrorInfo(original=e).
The legacy setter stores the error on self._mcp_config_parse_error.
"""
from src.app_controller import AppController
ctrl = AppController()
result = ctrl._set_mcp_config_json_result("not valid json")
assert isinstance(result, Result)
assert result.ok is False
assert len(result.errors) == 1
assert isinstance(result.errors[0], ErrorInfo)
def test_mcp_config_json_setter_stores_error_on_parse_failure():
"""
The property setter stores the first error on self._mcp_config_parse_error
when parsing fails.
"""
from src.app_controller import AppController
ctrl = AppController()
ctrl.mcp_config_json = "not valid json"
assert ctrl._mcp_config_parse_error is not None
assert isinstance(ctrl._mcp_config_parse_error, ErrorInfo)
def test_mcp_config_json_setter_no_error_on_valid_json():
"""
On valid JSON, _mcp_config_parse_error stays as None.
"""
from src.app_controller import AppController
ctrl = AppController()
ctrl.mcp_config_json = '{"servers": {}}'
assert ctrl._mcp_config_parse_error is None
def test_save_active_project_result_returns_ok_when_no_active_path():
"""
_save_active_project_result returns OK when no active_project_path is set.
"""
from src.app_controller import AppController
ctrl = AppController()
ctrl.active_project_path = None
result = ctrl._save_active_project_result()
assert isinstance(result, Result)
assert result.ok is True
assert result.errors == []
def test_save_active_project_stores_error_on_save_failure():
"""
When save_project raises, the legacy wrapper stores the error on
self._save_project_error and updates self.ai_status.
"""
from src.app_controller import AppController
ctrl = AppController()
ctrl.active_project_path = "/tmp/test_save.toml"
with patch("src.app_controller.project_manager.save_project", side_effect=PermissionError("denied")):
ctrl._save_active_project()
assert ctrl._save_project_error is not None
assert isinstance(ctrl._save_project_error, ErrorInfo)
assert "denied" in ctrl._save_project_error.message
assert "save error" in ctrl.ai_status