Add initial web editor app, CLI scripts, and test scaffolding.

This introduces the FastAPI editor implementation and related project setup so the app can be run and validated locally.

Made-with: Cursor
This commit is contained in:
2026-04-11 02:14:26 +12:00
parent fb5f55cda7
commit f9bf119af6
33 changed files with 4846 additions and 0 deletions

237
tests/test_internal.py Normal file
View File

@@ -0,0 +1,237 @@
import builtins
import importlib
import subprocess
from pathlib import Path
import pytest
from fastapi import HTTPException
def test_load_env_file_sets_missing_keys_only(tmp_path, monkeypatch):
import editor_app.config as config
env_file = tmp_path / ".env"
env_file.write_text(
"# comment\nFOO=bar\nBAZ='quoted'\nEXISTING=from_file\n", encoding="utf-8"
)
monkeypatch.setenv("EXISTING", "kept")
monkeypatch.delenv("FOO", raising=False)
monkeypatch.delenv("BAZ", raising=False)
config.load_env_file(env_file)
assert __import__("os").environ["FOO"] == "bar"
assert __import__("os").environ["BAZ"] == "quoted"
assert __import__("os").environ["EXISTING"] == "kept"
def test_load_env_file_ignores_missing_file(tmp_path):
import editor_app.config as config
config.load_env_file(tmp_path / "missing.env")
class _ProcNoStdout:
stdout = None
class _ProcWithLines:
def __init__(self):
self.stdout = iter(["line1\n", "line2\n"])
class _ProcWait:
def __init__(self, code=0):
self.code = code
def wait(self, timeout=None):
return self.code
class _ProcStop:
def __init__(self):
self.returncode = 0
self.terminated = False
self.killed = False
self.wait_calls = 0
def terminate(self):
self.terminated = True
def wait(self, timeout=None):
self.wait_calls += 1
if self.wait_calls == 1:
raise subprocess.TimeoutExpired("cmd", timeout)
return self.returncode
def kill(self):
self.killed = True
def test_python_runner_stream_helpers_and_wait():
import editor_app.services.python_runner as runner
with runner.python_runner.lock:
runner.python_runner.output_lines = []
runner.stream_process_output(_ProcNoStdout())
runner.stream_process_output(_ProcWithLines())
with runner.python_runner.lock:
assert runner.python_runner.output_lines[-2:] == ["line1\n", "line2\n"]
proc = _ProcWait(code=7)
runner.wait_for_process(proc)
with runner.python_runner.lock:
assert runner.python_runner.return_code == 7
assert runner.python_runner.running is False
assert runner.python_runner.process is None
def test_python_runner_run_failure_raises_http(monkeypatch, tmp_path):
import editor_app.config as config
import editor_app.services.python_runner as runner
config.WORKSPACE_ROOT = tmp_path
def _boom(*args, **kwargs):
raise RuntimeError("boom")
monkeypatch.setattr(runner.subprocess, "Popen", _boom)
with pytest.raises(HTTPException) as exc:
runner.run_python_file(tmp_path / "x.py", "x.py")
assert exc.value.status_code == 500
def test_python_runner_stop_kill_path():
import editor_app.services.python_runner as runner
proc = _ProcStop()
with runner.python_runner.lock:
runner.python_runner.process = proc
runner.python_runner.output_lines = []
runner.python_runner.running = True
message = runner.stop_python_process()
assert message == "Python process stopped"
assert proc.terminated is True
assert proc.killed is True
def test_completions_import_error_path(tmp_path, monkeypatch):
import editor_app.config as config
import editor_app.main as main
from fastapi.testclient import TestClient
config.WORKSPACE_ROOT = tmp_path
importlib.reload(main)
client = TestClient(main.app)
real_import = builtins.__import__
def fake_import(name, *args, **kwargs):
if name == "jedi":
raise ImportError("forced")
return real_import(name, *args, **kwargs)
monkeypatch.setattr(builtins, "__import__", fake_import)
response = client.post(
"/api/python/completions",
json={
"file_path": "scratch.py",
"content": "x = 1",
"line": 1,
"column": 1,
"max_results": 10,
},
)
assert response.status_code == 500
def test_websocket_output_status_message(tmp_path):
import editor_app.config as config
import editor_app.main as main
from fastapi.testclient import TestClient
config.WORKSPACE_ROOT = tmp_path
importlib.reload(main)
with TestClient(main.app) as client:
with client.websocket_connect("/api/python/ws/output") as ws:
payload = ws.receive_json()
assert "lines" in payload
assert "running" in payload
assert payload["running"] is False
def test_create_app_startup_creates_lib(tmp_path):
import editor_app.config as config
import editor_app.main as main
from fastapi.testclient import TestClient
config.WORKSPACE_ROOT = tmp_path
importlib.reload(main)
assert not (tmp_path / "lib").exists()
with TestClient(main.app):
pass
assert (tmp_path / "lib").is_dir()
def test_python_runner_helpers_and_startup_paths(tmp_path, monkeypatch):
import editor_app.config as config
import editor_app.services.python_runner as runner
config.WORKSPACE_ROOT = tmp_path
runner.STARTUP_SCRIPT_FILE = tmp_path / ".connectionmachine_startup_script"
(tmp_path / "lib").mkdir(exist_ok=True)
# list_python_scripts should skip hidden paths.
(tmp_path / "code").mkdir(exist_ok=True)
(tmp_path / "code" / "a.py").write_text("print('x')\n", encoding="utf-8")
(tmp_path / ".hidden.py").write_text("print('x')\n", encoding="utf-8")
scripts = runner.list_python_scripts()
assert "code/a.py" in scripts
assert ".hidden.py" not in scripts
# startup script getters/setters.
assert runner.get_startup_script() is None
runner.STARTUP_SCRIPT_FILE.write_text("", encoding="utf-8")
assert runner.get_startup_script() is None
missing = tmp_path / "code" / "missing.py"
with pytest.raises(HTTPException) as missing_exc:
runner.set_startup_script(str(missing.relative_to(tmp_path)))
assert missing_exc.value.status_code == 404
(tmp_path / "code" / "note.txt").write_text("x", encoding="utf-8")
with pytest.raises(HTTPException) as nonpy_exc:
runner.set_startup_script("code/note.txt")
assert nonpy_exc.value.status_code == 400
selected = runner.set_startup_script("code/a.py")
assert selected == "code/a.py"
assert runner.get_startup_script() == "code/a.py"
# run_startup_script_if_configured no-op branches.
runner.STARTUP_SCRIPT_FILE.write_text("code/missing.py", encoding="utf-8")
runner.run_startup_script_if_configured()
runner.STARTUP_SCRIPT_FILE.write_text("code/note.txt", encoding="utf-8")
runner.run_startup_script_if_configured()
# run_startup_script_if_configured should call run_python_file on valid startup script.
called = {"count": 0}
def fake_run(target_path, requested_path):
called["count"] += 1
assert requested_path == "code/a.py"
assert str(target_path).endswith("code/a.py")
monkeypatch.setattr(runner, "run_python_file", fake_run)
runner.STARTUP_SCRIPT_FILE.write_text("code/a.py", encoding="utf-8")
runner.run_startup_script_if_configured()
assert called["count"] == 1