peijun1's picture
Deploy AI Studio Proxy API to Hugging Face Spaces
a5784e9
Raw
History Blame Contribute Delete
10.2 kB
"""
Tests for browser_utils/models/startup.py
Covers the log cleanup and standardization changes:
- [State] tagged logs
- Condensed verbose messages
- Silent success pattern
"""
import json
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
@pytest.fixture
def mock_page():
"""Create a mock Playwright page with proper sync/async methods."""
page = MagicMock()
page.url = "https://aistudio.google.com/prompts/new_chat"
page.evaluate = AsyncMock()
page.goto = AsyncMock()
# locator() is sync, returns sync Locator - use MagicMock
# but locator.first.inner_text() is async
return page
class TestSetModelFromPageDisplay:
"""Tests for _set_model_from_page_display function."""
@pytest.mark.asyncio
@patch("api_utils.server_state.state")
async def test_reads_model_from_page(self, mock_state_obj, mock_page):
"""Test reading model display from page."""
from browser_utils.models.startup import _set_model_from_page_display
mock_state_obj.current_ai_studio_model_id = None
mock_state_obj.parsed_model_list = []
mock_state_obj.model_list_fetch_event = None
# Mock the model name locator - correctly chain .first.inner_text
first_locator = MagicMock()
first_locator.inner_text = AsyncMock(return_value="gemini-2.0-flash")
model_locator = MagicMock()
model_locator.first = first_locator
mock_page.locator.return_value = model_locator
await _set_model_from_page_display(mock_page, set_storage=False)
assert mock_state_obj.current_ai_studio_model_id == "gemini-2.0-flash"
@pytest.mark.asyncio
@patch("api_utils.server_state.state")
async def test_no_log_when_model_unchanged(self, mock_state_obj, mock_page):
"""Test silent success when model ID is unchanged."""
from browser_utils.models.startup import _set_model_from_page_display
mock_state_obj.current_ai_studio_model_id = "gemini-2.0-flash"
mock_state_obj.parsed_model_list = []
mock_state_obj.model_list_fetch_event = None
first_locator = MagicMock()
first_locator.inner_text = AsyncMock(return_value="gemini-2.0-flash")
model_locator = MagicMock()
model_locator.first = first_locator
mock_page.locator.return_value = model_locator
await _set_model_from_page_display(mock_page, set_storage=False)
# Should remain unchanged
assert mock_state_obj.current_ai_studio_model_id == "gemini-2.0-flash"
@pytest.mark.asyncio
@patch("api_utils.server_state.state")
@patch("browser_utils.models.startup._verify_and_apply_ui_state")
async def test_set_storage_updates_local_storage(
self, mock_verify_ui, mock_state_obj, mock_page
):
"""Test localStorage update when set_storage=True."""
from browser_utils.models.startup import _set_model_from_page_display
mock_state_obj.current_ai_studio_model_id = None
mock_state_obj.parsed_model_list = []
mock_state_obj.model_list_fetch_event = None
mock_verify_ui.return_value = True
first_locator = MagicMock()
first_locator.inner_text = AsyncMock(return_value="gemini-pro")
model_locator = MagicMock()
model_locator.first = first_locator
mock_page.locator.return_value = model_locator
mock_page.evaluate.return_value = None # No existing localStorage
await _set_model_from_page_display(mock_page, set_storage=True)
# Verify localStorage was written
assert mock_page.evaluate.call_count >= 2 # read + write
class TestHandleInitialModelStateAndStorage:
"""Tests for _handle_initial_model_state_and_storage function."""
@pytest.mark.asyncio
@patch("api_utils.server_state.state")
@patch("browser_utils.models.startup._verify_ui_state_settings")
async def test_no_refresh_when_storage_valid(
self, mock_verify_ui, mock_state_obj, mock_page
):
"""Test no page refresh when localStorage is valid."""
from browser_utils.models.startup import _handle_initial_model_state_and_storage
mock_state_obj.current_ai_studio_model_id = None
mock_state_obj.parsed_model_list = []
mock_state_obj.model_list_fetch_event = None
# Valid localStorage with all required fields
valid_prefs = json.dumps(
{
"promptModel": "models/gemini-2.0-flash",
"isAdvancedOpen": True,
"areToolsOpen": True,
}
)
mock_page.evaluate.return_value = valid_prefs
mock_verify_ui.return_value = {"needsUpdate": False}
await _handle_initial_model_state_and_storage(mock_page)
# Model should be set from localStorage
assert mock_state_obj.current_ai_studio_model_id == "gemini-2.0-flash"
@pytest.mark.asyncio
@patch("api_utils.server_state.state")
async def test_refresh_when_storage_missing(self, mock_state_obj, mock_page):
"""Test page refresh when localStorage is missing."""
from browser_utils.models.startup import _handle_initial_model_state_and_storage
mock_state_obj.current_ai_studio_model_id = None
mock_state_obj.parsed_model_list = []
mock_state_obj.model_list_fetch_event = None
# No localStorage
mock_page.evaluate.return_value = None
# Mock the locator for model name - properly chain .first.inner_text
first_locator = MagicMock()
first_locator.inner_text = AsyncMock(return_value="gemini-pro")
model_locator = MagicMock()
model_locator.first = first_locator
mock_page.locator.return_value = model_locator
# Mock goto and navigation
mock_page.goto = AsyncMock()
with (
patch("browser_utils.models.startup.expect_async") as mock_expect,
patch(
"browser_utils.models.startup._verify_and_apply_ui_state"
) as mock_verify,
):
mock_expect.return_value.to_be_visible = AsyncMock()
mock_verify.return_value = True
await _handle_initial_model_state_and_storage(mock_page)
# Should have attempted page reload
assert mock_page.goto.called
@pytest.mark.asyncio
@patch("api_utils.server_state.state")
async def test_handles_json_decode_error(self, mock_state_obj, mock_page):
"""Test handling of invalid JSON in localStorage."""
from browser_utils.models.startup import _handle_initial_model_state_and_storage
mock_state_obj.current_ai_studio_model_id = None
mock_state_obj.parsed_model_list = []
mock_state_obj.model_list_fetch_event = None
# Invalid JSON
mock_page.evaluate.return_value = "invalid json {"
first_locator = MagicMock()
first_locator.inner_text = AsyncMock(return_value="gemini-pro")
model_locator = MagicMock()
model_locator.first = first_locator
mock_page.locator.return_value = model_locator
mock_page.goto = AsyncMock()
with (
patch("browser_utils.models.startup.expect_async") as mock_expect,
patch(
"browser_utils.models.startup._verify_and_apply_ui_state"
) as mock_verify,
):
mock_expect.return_value.to_be_visible = AsyncMock()
mock_verify.return_value = True
# Should handle error gracefully
await _handle_initial_model_state_and_storage(mock_page)
class TestStateTaggedLogging:
"""Tests to verify [State] tagged logging patterns."""
@pytest.mark.asyncio
@patch("api_utils.server_state.state")
@patch("browser_utils.models.startup.logger")
async def test_state_tag_on_refresh_needed(
self, mock_logger, mock_state_obj, mock_page
):
"""Verify [State] tag in log when refresh is needed."""
from browser_utils.models.startup import _handle_initial_model_state_and_storage
mock_state_obj.current_ai_studio_model_id = None
mock_state_obj.parsed_model_list = []
mock_state_obj.model_list_fetch_event = None
mock_page.evaluate.return_value = None # No localStorage
first_locator = MagicMock()
first_locator.inner_text = AsyncMock(return_value="gemini-pro")
model_locator = MagicMock()
model_locator.first = first_locator
mock_page.locator.return_value = model_locator
mock_page.goto = AsyncMock()
with (
patch("browser_utils.models.startup.expect_async") as mock_expect,
patch(
"browser_utils.models.startup._verify_and_apply_ui_state"
) as mock_verify,
):
mock_expect.return_value.to_be_visible = AsyncMock()
mock_verify.return_value = True
await _handle_initial_model_state_and_storage(mock_page)
# Verify [State] tagged debug log was called
debug_calls = [str(call) for call in mock_logger.debug.call_args_list]
assert any("[State]" in call for call in debug_calls)
@pytest.mark.asyncio
@patch("api_utils.server_state.state")
@patch("browser_utils.models.startup.logger")
async def test_model_tag_on_page_display(
self, mock_logger, mock_state_obj, mock_page
):
"""Verify [Model] tag when reading from page display."""
from browser_utils.models.startup import _set_model_from_page_display
mock_state_obj.current_ai_studio_model_id = None
mock_state_obj.parsed_model_list = []
mock_state_obj.model_list_fetch_event = None
first_locator = MagicMock()
first_locator.inner_text = AsyncMock(return_value="gemini-flash")
model_locator = MagicMock()
model_locator.first = first_locator
mock_page.locator.return_value = model_locator
await _set_model_from_page_display(mock_page, set_storage=False)
# Verify [Model] tagged debug log was called
debug_calls = [str(call) for call in mock_logger.debug.call_args_list]
assert any("[Model]" in call for call in debug_calls)