105 lines
4.4 KiB
Python
105 lines
4.4 KiB
Python
"""Tests for focus_nudge utility — should_nudge() logic and nudge_unity_focus() gating."""
|
|
|
|
import time
|
|
from unittest.mock import patch, AsyncMock
|
|
|
|
import pytest
|
|
|
|
from utils.focus_nudge import (
|
|
should_nudge,
|
|
reset_nudge_backoff,
|
|
nudge_unity_focus,
|
|
_is_available,
|
|
)
|
|
|
|
|
|
class TestShouldNudge:
|
|
"""Tests for should_nudge() decision logic."""
|
|
|
|
def test_returns_false_when_not_running(self):
|
|
assert should_nudge(status="succeeded", editor_is_focused=False, last_update_unix_ms=0, current_time_ms=99999) is False
|
|
|
|
def test_returns_false_when_focused(self):
|
|
assert should_nudge(status="running", editor_is_focused=True, last_update_unix_ms=0, current_time_ms=99999) is False
|
|
|
|
def test_returns_true_when_stalled_and_unfocused(self):
|
|
now_ms = int(time.time() * 1000)
|
|
stale_ms = now_ms - 5000 # 5s ago
|
|
assert should_nudge(status="running", editor_is_focused=False, last_update_unix_ms=stale_ms, current_time_ms=now_ms) is True
|
|
|
|
def test_returns_false_when_recently_updated(self):
|
|
now_ms = int(time.time() * 1000)
|
|
recent_ms = now_ms - 1000 # 1s ago (within 3s threshold)
|
|
assert should_nudge(status="running", editor_is_focused=False, last_update_unix_ms=recent_ms, current_time_ms=now_ms) is False
|
|
|
|
def test_returns_true_when_no_updates_yet(self):
|
|
"""No last_update_unix_ms means tests might be stuck at start."""
|
|
assert should_nudge(status="running", editor_is_focused=False, last_update_unix_ms=None) is True
|
|
|
|
def test_custom_stall_threshold(self):
|
|
now_ms = int(time.time() * 1000)
|
|
stale_ms = now_ms - 2000 # 2s ago
|
|
# Default threshold (3s) — not stale yet
|
|
assert should_nudge(status="running", editor_is_focused=False, last_update_unix_ms=stale_ms, current_time_ms=now_ms) is False
|
|
# Custom threshold (1s) — stale
|
|
assert should_nudge(status="running", editor_is_focused=False, last_update_unix_ms=stale_ms, current_time_ms=now_ms, stall_threshold_ms=1000) is True
|
|
|
|
def test_returns_false_for_failed_status(self):
|
|
assert should_nudge(status="failed", editor_is_focused=False, last_update_unix_ms=0, current_time_ms=99999) is False
|
|
|
|
def test_returns_false_for_cancelled_status(self):
|
|
assert should_nudge(status="cancelled", editor_is_focused=False, last_update_unix_ms=0, current_time_ms=99999) is False
|
|
|
|
|
|
class TestResetNudgeBackoff:
|
|
"""Tests for reset_nudge_backoff() state management."""
|
|
|
|
def test_resets_consecutive_nudges(self):
|
|
import utils.focus_nudge as fn
|
|
fn._consecutive_nudges = 5
|
|
reset_nudge_backoff()
|
|
assert fn._consecutive_nudges == 0
|
|
|
|
def test_updates_last_progress_time(self):
|
|
import utils.focus_nudge as fn
|
|
old_time = fn._last_progress_time
|
|
reset_nudge_backoff()
|
|
assert fn._last_progress_time >= old_time
|
|
|
|
|
|
class TestNudgeUnityFocus:
|
|
"""Tests for nudge_unity_focus() gating logic."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_skips_when_not_available(self):
|
|
with patch("utils.focus_nudge._is_available", return_value=False):
|
|
result = await nudge_unity_focus(force=True)
|
|
assert result is False
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_skips_when_unity_already_focused(self):
|
|
from utils.focus_nudge import _FrontmostAppInfo
|
|
with patch("utils.focus_nudge._is_available", return_value=True), \
|
|
patch("utils.focus_nudge._get_frontmost_app", return_value=_FrontmostAppInfo(name="Unity")):
|
|
result = await nudge_unity_focus(force=True)
|
|
assert result is False
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_skips_when_frontmost_app_unknown(self):
|
|
with patch("utils.focus_nudge._is_available", return_value=True), \
|
|
patch("utils.focus_nudge._get_frontmost_app", return_value=None):
|
|
result = await nudge_unity_focus(force=True)
|
|
assert result is False
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_rate_limited_by_backoff(self):
|
|
import utils.focus_nudge as fn
|
|
from utils.focus_nudge import _FrontmostAppInfo
|
|
# Simulate a very recent nudge
|
|
fn._last_nudge_time = time.monotonic()
|
|
fn._consecutive_nudges = 0
|
|
with patch("utils.focus_nudge._is_available", return_value=True), \
|
|
patch("utils.focus_nudge._get_frontmost_app", return_value=_FrontmostAppInfo(name="Terminal")):
|
|
result = await nudge_unity_focus(force=False)
|
|
assert result is False
|