feat(foreground): track topmost process + browser page title
Lint & Test / test (push) Failing after 8s
Lint & Test / test (push) Failing after 8s
Adds cross-platform foreground-window tracking and exposes it over REST (/api/foreground) and the existing WebSocket feed. - foreground_service.py: Windows probe via ctypes (HANDLE-correct argtypes to avoid 64-bit handle truncation); macOS via AppKit; Linux via Xlib (Wayland returns unavailable). TTL cache + per-platform fallback. - browser_url_service.py: when foreground is a recognised browser, extract the page title from the window title (browser-name suffix stripped) and surface `is_browser` + `browser_page_title`. Optional UIA-based URL extraction behind MEDIA_SERVER_BROWSER_UIA env flag (off by default — Chromium browsers keep their accessibility tree dormant otherwise). - websocket_manager: poll foreground every 1s inside the existing status loop, broadcast `foreground` on connect and `foreground_update` on change. Diff only on user-visible fields to avoid geometry spam. - WebUI: new editorial card rendered under the monitor list on the Display tab — process name, window title, fullscreen/minimized/monitor chips, browser block when applicable, exe path, PID, started-ago, geometry, platform. 16px inter-section gap matches Settings cadence. - i18n: 25 new keys added to both en.json and ru.json. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
"""Smoke tests for the foreground tracker.
|
||||
|
||||
The OS-specific probe code is hard to mock end-to-end inside a CI container,
|
||||
so these tests focus on the platform-agnostic surface: the dataclass shape,
|
||||
TTL caching, and graceful fallback when the platform probe raises. The
|
||||
Windows/Linux/macOS probes themselves are exercised through manual runs.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
from media_server.services import foreground_service as fg
|
||||
|
||||
|
||||
def setup_function(_):
|
||||
fg.reset_cache()
|
||||
|
||||
|
||||
def test_unavailable_default_shape():
|
||||
info = fg.ForegroundInfo(available=False)
|
||||
d = info.to_dict()
|
||||
assert d["available"] is False
|
||||
assert d["pid"] is None
|
||||
assert d["process_name"] is None
|
||||
assert d["is_fullscreen"] is False
|
||||
assert "platform" in d
|
||||
|
||||
|
||||
def test_cache_returns_same_instance(monkeypatch):
|
||||
calls = {"n": 0}
|
||||
|
||||
def fake_probe():
|
||||
calls["n"] += 1
|
||||
return fg.ForegroundInfo(available=True, pid=42, process_name="x.exe")
|
||||
|
||||
monkeypatch.setattr(fg, "_probe", fake_probe)
|
||||
|
||||
a = fg.get_foreground_info()
|
||||
b = fg.get_foreground_info()
|
||||
assert a is b
|
||||
assert calls["n"] == 1
|
||||
|
||||
|
||||
def test_cache_force_refresh(monkeypatch):
|
||||
calls = {"n": 0}
|
||||
|
||||
def fake_probe():
|
||||
calls["n"] += 1
|
||||
return fg.ForegroundInfo(available=True, pid=calls["n"])
|
||||
|
||||
monkeypatch.setattr(fg, "_probe", fake_probe)
|
||||
|
||||
fg.get_foreground_info()
|
||||
fg.get_foreground_info(force_refresh=True)
|
||||
assert calls["n"] == 2
|
||||
|
||||
|
||||
def test_cache_ttl_expiry(monkeypatch):
|
||||
calls = {"n": 0}
|
||||
|
||||
def fake_probe():
|
||||
calls["n"] += 1
|
||||
return fg.ForegroundInfo(available=True, pid=calls["n"])
|
||||
|
||||
monkeypatch.setattr(fg, "_probe", fake_probe)
|
||||
monkeypatch.setattr(fg, "_CACHE_TTL", 0.0)
|
||||
# Re-bind the cache's TTL by exercising it twice with TTL 0.
|
||||
fg.get_foreground_info()
|
||||
fg.get_foreground_info()
|
||||
assert calls["n"] == 2
|
||||
|
||||
|
||||
def test_probe_crash_returns_unavailable(monkeypatch):
|
||||
def boom():
|
||||
raise RuntimeError("kaboom")
|
||||
|
||||
# Force every platform branch to call our crashing probe.
|
||||
monkeypatch.setattr(fg, "_probe_windows", boom)
|
||||
monkeypatch.setattr(fg, "_probe_linux", boom)
|
||||
monkeypatch.setattr(fg, "_probe_macos", boom)
|
||||
|
||||
info = fg._probe()
|
||||
assert info.available is False
|
||||
assert info.error and "kaboom" in info.error
|
||||
Reference in New Issue
Block a user