From c35bb52a331591fea895f4320fd150a461e5eb31 Mon Sep 17 00:00:00 2001 From: Ayoub Abedrabbo Date: Sat, 11 Jul 2026 14:37:48 -0400 Subject: [PATCH 1/2] Fix sidecar startup health check --- frontend/electron/main.cjs | 15 ++++++++++++++- src/resolve_time_tracker/api.py | 4 ++++ tests/test_api.py | 15 ++++++++++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/frontend/electron/main.cjs b/frontend/electron/main.cjs index 26bbfa4..12599ef 100644 --- a/frontend/electron/main.cjs +++ b/frontend/electron/main.cjs @@ -161,13 +161,26 @@ function stopSidecar() { async function apiIsRunning(timeoutMs = 250) { try { - await fetchStatus(timeoutMs) + await fetchHealth(timeoutMs) return true } catch { return false } } +function fetchHealth(timeoutMs = 1000) { + return new Promise((resolve, reject) => { + const request = http.get(`${apiBase}/health`, (response) => { + response.resume() + response.statusCode === 200 + ? resolve() + : reject(new Error(`status ${response.statusCode}`)) + }) + request.on("error", reject) + request.setTimeout(timeoutMs, () => request.destroy(new Error("timeout"))) + }) +} + function fetchStatus(timeoutMs = 1000) { return new Promise((resolve, reject) => { const request = http.get(`${apiBase}/status`, (response) => { diff --git a/src/resolve_time_tracker/api.py b/src/resolve_time_tracker/api.py index 20a17f0..8a84e36 100644 --- a/src/resolve_time_tracker/api.py +++ b/src/resolve_time_tracker/api.py @@ -382,6 +382,10 @@ def create_app( ) app.state.api = api + @app.get("/health") + def health() -> dict[str, bool]: + return {"ok": True} + @app.get("/status") def status() -> dict[str, Any]: return api.refresh() diff --git a/tests/test_api.py b/tests/test_api.py index 553e0a4..01a440c 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -2,7 +2,7 @@ import unittest from datetime import datetime, timezone from pathlib import Path -from unittest.mock import patch +from unittest.mock import Mock, patch from fastapi.testclient import TestClient @@ -296,6 +296,19 @@ def test_allows_frontend_origin(self): self.assertEqual("*", response.headers["access-control-allow-origin"]) + def test_health_does_not_poll_resolve(self): + with tempfile.TemporaryDirectory() as tmp: + engine = Mock() + with SQLiteStore( + Path(tmp) / "tracker.sqlite3", check_same_thread=False + ) as store: + response = TestClient( + create_app(store, tracking_engine=engine) + ).get("/health") + + self.assertEqual({"ok": True}, response.json()) + engine.poll.assert_not_called() + def test_run_api_starts_tracking_api_with_resolve_bridge(self): with tempfile.TemporaryDirectory() as tmp: with ( From 24f3d92473081bbc51475025bf4ff32d4b3b74c8 Mon Sep 17 00:00:00 2001 From: Ayoub Abedrabbo Date: Sat, 11 Jul 2026 14:48:26 -0400 Subject: [PATCH 2/2] Format health endpoint test --- tests/test_api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 01a440c..b8db41d 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -302,9 +302,9 @@ def test_health_does_not_poll_resolve(self): with SQLiteStore( Path(tmp) / "tracker.sqlite3", check_same_thread=False ) as store: - response = TestClient( - create_app(store, tracking_engine=engine) - ).get("/health") + response = TestClient(create_app(store, tracking_engine=engine)).get( + "/health" + ) self.assertEqual({"ok": True}, response.json()) engine.poll.assert_not_called()