Files
smart-support/backend/tests/unit/test_interrupt_cleanup.py
Yaojia Wang f0699436c5 refactor: engineering improvements -- API versioning, structured logging, Alembic, error standardization, test coverage
- API versioning: all REST endpoints prefixed with /api/v1/
- Structured logging: replaced stdlib logging with structlog (console/JSON modes)
- Alembic migrations: versioned DB schema with initial migration
- Error standardization: global exception handlers for consistent envelope format
- Interrupt cleanup: asyncio background task for expired interrupt removal
- Integration tests: +30 tests (analytics, replay, openapi, error, session APIs)
- Frontend tests: +57 tests (all components, pages, useWebSocket hook)
- Backend: 557 tests, 89.75% coverage | Frontend: 80 tests, 16 test files
2026-04-06 23:19:29 +02:00

87 lines
2.6 KiB
Python

"""Tests for the interrupt cleanup background loop in main.py."""
from __future__ import annotations
import asyncio
import logging
from unittest.mock import MagicMock, patch
import pytest
from app.main import _interrupt_cleanup_loop
@pytest.mark.unit
@pytest.mark.asyncio
async def test_cleanup_loop_calls_cleanup_expired() -> None:
"""The loop should call cleanup_expired after each sleep interval."""
manager = MagicMock()
manager.cleanup_expired.return_value = ()
call_count = 0
original_sleep = asyncio.sleep
async def _fake_sleep(seconds: float) -> None:
nonlocal call_count
call_count += 1
if call_count >= 2:
raise asyncio.CancelledError
await original_sleep(0)
with patch("app.main.asyncio.sleep", side_effect=_fake_sleep):
with pytest.raises(asyncio.CancelledError):
await _interrupt_cleanup_loop(manager, interval=60)
assert manager.cleanup_expired.call_count >= 1
@pytest.mark.unit
@pytest.mark.asyncio
async def test_cleanup_loop_survives_exceptions() -> None:
"""The loop should not die when cleanup_expired raises an exception."""
manager = MagicMock()
manager.cleanup_expired.side_effect = [RuntimeError("db gone"), ()]
call_count = 0
original_sleep = asyncio.sleep
async def _fake_sleep(seconds: float) -> None:
nonlocal call_count
call_count += 1
if call_count >= 3:
raise asyncio.CancelledError
await original_sleep(0)
with patch("app.main.asyncio.sleep", side_effect=_fake_sleep):
with pytest.raises(asyncio.CancelledError):
await _interrupt_cleanup_loop(manager, interval=60)
# Should have been called twice: once raising, once returning ()
assert manager.cleanup_expired.call_count == 2
@pytest.mark.unit
@pytest.mark.asyncio
async def test_cleanup_loop_logs_expired_count(capsys: pytest.CaptureFixture[str]) -> None:
"""The loop should log when expired interrupts are found."""
fake_record = MagicMock()
manager = MagicMock()
manager.cleanup_expired.return_value = (fake_record, fake_record)
call_count = 0
original_sleep = asyncio.sleep
async def _fake_sleep(seconds: float) -> None:
nonlocal call_count
call_count += 1
if call_count >= 2:
raise asyncio.CancelledError
await original_sleep(0)
with patch("app.main.asyncio.sleep", side_effect=_fake_sleep):
with pytest.raises(asyncio.CancelledError):
await _interrupt_cleanup_loop(manager, interval=60)
captured = capsys.readouterr()
assert "2 expired interrupt" in captured.out