letsbe-mcp-browser/tests/test_session_manager.py

248 lines
7.9 KiB
Python
Raw Permalink Normal View History

"""Tests for session management."""
import asyncio
from datetime import datetime, timedelta, timezone
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from app.session_manager import BrowserSession, SessionManager, utc_now
class TestBrowserSession:
"""Tests for the BrowserSession class."""
def test_touch_updates_last_activity(self):
"""touch() should update last_activity timestamp."""
mock_context = MagicMock()
mock_page = MagicMock()
session = BrowserSession(
session_id="test-123",
allowed_domains=["example.com"],
context=mock_context,
page=mock_page,
)
original_activity = session.last_activity
session.touch()
assert session.last_activity >= original_activity
def test_increment_actions(self):
"""increment_actions() should update counter and touch."""
mock_context = MagicMock()
mock_page = MagicMock()
session = BrowserSession(
session_id="test-123",
allowed_domains=["example.com"],
context=mock_context,
page=mock_page,
)
assert session.actions_used == 0
session.increment_actions()
assert session.actions_used == 1
def test_actions_remaining(self):
"""actions_remaining should calculate correctly."""
mock_context = MagicMock()
mock_page = MagicMock()
session = BrowserSession(
session_id="test-123",
allowed_domains=["example.com"],
context=mock_context,
page=mock_page,
)
initial_remaining = session.actions_remaining
session.increment_actions()
assert session.actions_remaining == initial_remaining - 1
def test_is_expired_idle_timeout(self):
"""Session should expire after idle timeout."""
mock_context = MagicMock()
mock_page = MagicMock()
session = BrowserSession(
session_id="test-123",
allowed_domains=["example.com"],
context=mock_context,
page=mock_page,
)
# Set last_activity to past
session.last_activity = utc_now() - timedelta(seconds=400)
assert session.is_expired
def test_is_expired_max_lifetime(self):
"""Session should expire after max lifetime."""
mock_context = MagicMock()
mock_page = MagicMock()
session = BrowserSession(
session_id="test-123",
allowed_domains=["example.com"],
context=mock_context,
page=mock_page,
)
# Set created_at to past
session.created_at = utc_now() - timedelta(seconds=2000)
session.last_activity = utc_now() # Recent activity
assert session.is_expired
def test_not_expired_fresh_session(self):
"""Fresh session should not be expired."""
mock_context = MagicMock()
mock_page = MagicMock()
session = BrowserSession(
session_id="test-123",
allowed_domains=["example.com"],
context=mock_context,
page=mock_page,
)
assert not session.is_expired
@pytest.mark.asyncio
class TestSessionManager:
"""Tests for the SessionManager class."""
async def test_create_session_success(self):
"""create_session should create a new session."""
mock_browser = AsyncMock()
mock_context = AsyncMock()
mock_page = AsyncMock()
mock_browser.new_context.return_value = mock_context
mock_context.new_page.return_value = mock_page
manager = SessionManager(mock_browser)
session = await manager.create_session(["example.com"])
assert session is not None
assert session.session_id is not None
assert session.allowed_domains == ["example.com"]
assert manager.active_session_count == 1
async def test_create_session_empty_domains_raises(self):
"""create_session with empty domains should raise."""
mock_browser = AsyncMock()
manager = SessionManager(mock_browser)
with pytest.raises(ValueError):
await manager.create_session([])
async def test_create_session_max_limit(self):
"""create_session should fail when max sessions reached."""
mock_browser = AsyncMock()
mock_context = AsyncMock()
mock_page = AsyncMock()
mock_browser.new_context.return_value = mock_context
mock_context.new_page.return_value = mock_page
manager = SessionManager(mock_browser)
# Create max sessions
with patch("app.session_manager.settings") as mock_settings:
mock_settings.max_sessions = 2
mock_settings.max_actions_per_session = 50
mock_settings.idle_timeout_seconds = 300
mock_settings.max_session_lifetime_seconds = 1800
mock_settings.default_timeout_ms = 30000
mock_settings.navigation_timeout_ms = 60000
await manager.create_session(["example.com"])
await manager.create_session(["other.com"])
with pytest.raises(ValueError) as exc_info:
await manager.create_session(["blocked.com"])
assert "Maximum sessions" in str(exc_info.value)
async def test_get_session_returns_session(self):
"""get_session should return existing session."""
mock_browser = AsyncMock()
mock_context = AsyncMock()
mock_page = AsyncMock()
mock_browser.new_context.return_value = mock_context
mock_context.new_page.return_value = mock_page
manager = SessionManager(mock_browser)
session = await manager.create_session(["example.com"])
retrieved = await manager.get_session(session.session_id)
assert retrieved is session
async def test_get_session_returns_none_for_unknown(self):
"""get_session should return None for unknown session."""
mock_browser = AsyncMock()
manager = SessionManager(mock_browser)
retrieved = await manager.get_session("unknown-id")
assert retrieved is None
async def test_close_session_removes_session(self):
"""close_session should remove and close session."""
mock_browser = AsyncMock()
mock_context = AsyncMock()
mock_page = AsyncMock()
mock_browser.new_context.return_value = mock_context
mock_context.new_page.return_value = mock_page
manager = SessionManager(mock_browser)
session = await manager.create_session(["example.com"])
assert manager.active_session_count == 1
result = await manager.close_session(session.session_id)
assert result is True
assert manager.active_session_count == 0
mock_context.close.assert_called_once()
async def test_close_session_idempotent(self):
"""close_session should be idempotent."""
mock_browser = AsyncMock()
mock_context = AsyncMock()
mock_page = AsyncMock()
mock_browser.new_context.return_value = mock_context
mock_context.new_page.return_value = mock_page
manager = SessionManager(mock_browser)
session = await manager.create_session(["example.com"])
await manager.close_session(session.session_id)
result = await manager.close_session(session.session_id)
assert result is False # Already closed
async def test_close_all_sessions(self):
"""close_all_sessions should close all sessions."""
mock_browser = AsyncMock()
mock_context = AsyncMock()
mock_page = AsyncMock()
mock_browser.new_context.return_value = mock_context
mock_context.new_page.return_value = mock_page
manager = SessionManager(mock_browser)
await manager.create_session(["example.com"])
await manager.create_session(["other.com"])
assert manager.active_session_count == 2
await manager.close_all_sessions()
assert manager.active_session_count == 0