From 82ec323aaafc8e9c9de8e7702f92576b8c3b2013 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Sat, 28 Mar 2026 02:47:18 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20address=20pre-merge=20review=20findings?= =?UTF-8?q?=20=E2=80=94=20stale=20identity=20strings,=20pyright=20error,?= =?UTF-8?q?=20fragile=20test=20path,=20formatting,=20README=20accuracy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Required fixes: - main.py: update module docstring, startup comment, and FastAPI title from 'tmux-web coordinator' / 'coordinator service' → 'muxplex' (4 locations) - test_api.py: replace hasattr/BaseRoute pattern with isinstance(r, (APIRoute, APIWebSocketRoute)) to satisfy pyright (union-attr error eliminated) Recommendations applied: - test_api.py + test_cli.py: auto-formatted with ruff (blank-line normalisation) - test_cli.py: replace exec(Path('muxplex/__main__.py').read_text()) with importlib.util.find_spec() to obtain the absolute path — no longer assumes pytest is invoked from a specific working directory - README.md: fix architecture table — WebSocket proxy moved from ttyd.py description to main.py; ttyd.py now correctly described as lifecycle only All 178 tests pass; python_check clean (0 errors, 0 warnings). --- README.md | 4 ++-- muxplex/main.py | 8 ++++---- muxplex/tests/test_api.py | 18 +++++++++--------- muxplex/tests/test_cli.py | 15 +++++++-------- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 2e5e31a..8676f43 100644 --- a/README.md +++ b/README.md @@ -171,10 +171,10 @@ muxplex/ ├── __init__.py ├── __main__.py # `python -m muxplex` entry point ├── cli.py # CLI argument parsing and `install-service` subcommand - ├── main.py # FastAPI app factory and route registration + ├── main.py # FastAPI app: session API, bell hooks, WebSocket proxy to ttyd, static frontend ├── sessions.py # tmux session discovery and snapshot capture ├── bells.py # Bell/activity notification tracking - ├── ttyd.py # ttyd process lifecycle management and WebSocket proxy + ├── ttyd.py # ttyd process lifecycle management (spawn, kill, PID tracking) ├── state.py # Shared in-process state (sessions, bells, ttyd) ├── frontend/ # Static frontend assets (served as package data) │ ├── index.html diff --git a/muxplex/main.py b/muxplex/main.py index 717952d..9ce87d6 100644 --- a/muxplex/main.py +++ b/muxplex/main.py @@ -1,7 +1,7 @@ """ -FastAPI coordinator application for tmux-web. +muxplex — FastAPI application for the tmux session dashboard. -Entry point for the coordinator service. Exposes: +Entry point for the muxplex server. Exposes: GET /health → {"status": "ok"} Background poll loop reconciles tmux session state every POLL_INTERVAL seconds. @@ -138,7 +138,7 @@ async def _poll_loop() -> None: async def lifespan(app: FastAPI): global _poll_task - # Startup: kill any orphaned ttyd from a previous coordinator run, then + # Startup: kill any orphaned ttyd from a previous muxplex run, then # start the background poll loop. await kill_orphan_ttyd() _poll_task = asyncio.create_task(_poll_loop()) @@ -170,7 +170,7 @@ async def lifespan(app: FastAPI): # App # --------------------------------------------------------------------------- -app = FastAPI(title="tmux-web coordinator", version="0.1.0", lifespan=lifespan) +app = FastAPI(title="muxplex", version="0.1.0", lifespan=lifespan) # --------------------------------------------------------------------------- diff --git a/muxplex/tests/test_api.py b/muxplex/tests/test_api.py index 7e10e5e..ce82027 100644 --- a/muxplex/tests/test_api.py +++ b/muxplex/tests/test_api.py @@ -1,5 +1,5 @@ """ -Tests for coordinator/main.py — FastAPI skeleton, lifespan, /health endpoint. +Tests for muxplex/main.py — FastAPI skeleton, lifespan, /health endpoint. """ import pytest @@ -149,9 +149,7 @@ def test_patch_state_ignores_unknown_fields(client): def test_get_sessions_returns_list(client, monkeypatch): """GET /api/sessions must return a JSON list.""" monkeypatch.setattr("muxplex.main.get_session_list", lambda: ["alpha"]) - monkeypatch.setattr( - "muxplex.main.get_snapshots", lambda: {"alpha": "some text"} - ) + monkeypatch.setattr("muxplex.main.get_snapshots", lambda: {"alpha": "some text"}) response = client.get("/api/sessions") assert response.status_code == 200 @@ -210,9 +208,7 @@ def test_get_sessions_includes_bell_state(client, monkeypatch): from muxplex.state import save_state monkeypatch.setattr("muxplex.main.get_session_list", lambda: ["delta"]) - monkeypatch.setattr( - "muxplex.main.get_snapshots", lambda: {"delta": "pane text"} - ) + monkeypatch.setattr("muxplex.main.get_snapshots", lambda: {"delta": "pane text"}) save_state( { "active_session": None, @@ -647,9 +643,13 @@ def test_api_routes_not_shadowed(client): def test_terminal_ws_route_exists(): """The app must have a WebSocket route registered at /terminal/ws.""" + from fastapi.routing import APIRoute, APIWebSocketRoute + from muxplex.main import app + ws_routes = [ - r for r in app.routes - if hasattr(r, "path") and r.path == "/terminal/ws" + r + for r in app.routes + if isinstance(r, (APIRoute, APIWebSocketRoute)) and r.path == "/terminal/ws" ] assert len(ws_routes) == 1, "Expected exactly one /terminal/ws route" diff --git a/muxplex/tests/test_cli.py b/muxplex/tests/test_cli.py index f603f30..5e5be38 100644 --- a/muxplex/tests/test_cli.py +++ b/muxplex/tests/test_cli.py @@ -4,7 +4,6 @@ from pathlib import Path from unittest.mock import patch - def test_cli_module_importable(): """muxplex.cli must be importable.""" from muxplex.cli import main # noqa: F401 @@ -87,12 +86,12 @@ def test_install_service_system_mode_target(tmp_path, monkeypatch): def test_dunder_main_calls_main(): """python -m muxplex must call cli.main().""" - with patch("muxplex.cli.main") as mock_main: - # Simulate `python -m muxplex` by exec'ing __main__.py - import muxplex.__main__ # noqa: F401 + import importlib.util - # The import itself calls main() at module level - # Re-exec to test: - mock_main.reset_mock() - exec(Path("muxplex/__main__.py").read_text()) + # Locate __main__.py without executing it (find_spec does not import) + spec = importlib.util.find_spec("muxplex.__main__") + assert spec is not None and spec.origin is not None + + with patch("muxplex.cli.main") as mock_main: + exec(Path(spec.origin).read_text()) # noqa: S102 mock_main.assert_called_once()