Skip to main content
Glama
by frap129
test_search_character_option.py7.68 kB
"""Tests for character option search tool.""" import importlib import inspect from unittest.mock import AsyncMock, MagicMock import pytest from lorekeeper_mcp.api_clients.exceptions import ApiError, NetworkError from lorekeeper_mcp.tools.search_character_option import search_character_option search_character_option_module = importlib.import_module( "lorekeeper_mcp.tools.search_character_option" ) @pytest.fixture def mock_character_option_repository() -> MagicMock: """Create mock character option repository for testing.""" repo = MagicMock() repo.search = AsyncMock() repo.get_all = AsyncMock() return repo @pytest.mark.asyncio async def test_search_class_with_repository(repository_context): """Test looking up a class using repository context.""" repository_context.search.return_value = [{"name": "Paladin", "hit_dice": "1d10"}] result = await search_character_option(type="class", search="Paladin") assert len(result) == 1 assert result[0]["name"] == "Paladin" # Verify repository.search was called with option_type repository_context.search.assert_awaited_once() call_kwargs = repository_context.search.call_args[1] assert call_kwargs["option_type"] == "class" @pytest.mark.asyncio async def test_search_race_with_repository(repository_context): """Test looking up a race using repository context.""" repository_context.search.return_value = [{"name": "Elf", "speed": 30}] result = await search_character_option(type="race", search="Elf") assert len(result) == 1 assert result[0]["name"] == "Elf" call_kwargs = repository_context.search.call_args[1] assert call_kwargs["option_type"] == "race" @pytest.mark.asyncio async def test_search_background_with_repository(repository_context): """Test looking up a background using repository context.""" repository_context.search.return_value = [{"name": "Acolyte"}] result = await search_character_option(type="background", search="Acolyte") assert len(result) == 1 assert result[0]["name"] == "Acolyte" call_kwargs = repository_context.search.call_args[1] assert call_kwargs["option_type"] == "background" @pytest.mark.asyncio async def test_search_feat_with_repository(repository_context): """Test looking up a feat using repository context.""" repository_context.search.return_value = [{"name": "Sharpshooter"}] result = await search_character_option(type="feat", search="Sharpshooter") assert len(result) == 1 assert result[0]["name"] == "Sharpshooter" call_kwargs = repository_context.search.call_args[1] assert call_kwargs["option_type"] == "feat" @pytest.mark.asyncio async def test_search_invalid_type(): """Test invalid type parameter raises ValueError.""" with pytest.raises(ValueError, match="Invalid type"): await search_character_option(type="invalid-type") # type: ignore[arg-type] @pytest.mark.asyncio async def test_search_character_option_with_limit(repository_context): """Test that limit parameter is passed to repository.""" options = [{"name": f"Option {i}", "id": i} for i in range(5)] repository_context.search.return_value = options result = await search_character_option(type="class", limit=5) assert len(result) == 5 call_kwargs = repository_context.search.call_args[1] assert call_kwargs["limit"] == 5 @pytest.mark.asyncio async def test_search_character_option_empty_results(repository_context): """Test character option search with no results.""" repository_context.search.return_value = [] result = await search_character_option(type="class", search="NonexistentClass") assert result == [] @pytest.mark.asyncio async def test_search_character_option_api_error(repository_context): """Test character option search handles API errors gracefully.""" repository_context.search.side_effect = ApiError("API unavailable") with pytest.raises(ApiError, match="API unavailable"): await search_character_option(type="class") @pytest.mark.asyncio async def test_search_character_option_network_error(repository_context): """Test character option search handles network errors.""" repository_context.search.side_effect = NetworkError("Connection timeout") with pytest.raises(NetworkError, match="Connection timeout"): await search_character_option(type="class") @pytest.mark.asyncio async def test_search_character_option_no_repository_parameter(): """Test that search_character_option no longer accepts repository parameter.""" # This test verifies the function does NOT accept repository parameter # and instead uses context-based injection like other tools sig = inspect.signature(search_character_option) assert "repository" not in sig.parameters @pytest.mark.asyncio async def test_search_character_option_with_documents( repository_context, ) -> None: """Test search_character_option with documents filter.""" repository_context.search.return_value = [{"name": "Barbarian", "document": "srd-5e"}] result = await search_character_option(type="class", documents=["srd-5e"]) assert len(result) == 1 assert result[0]["name"] == "Barbarian" # Verify document parameter is passed to repository call_kwargs = repository_context.search.call_args[1] assert "document" in call_kwargs assert call_kwargs["document"] == ["srd-5e"] @pytest.fixture def repository_context(mock_character_option_repository): """Fixture to inject mock repository via context for tests.""" search_character_option_module._repository_context["repository"] = ( mock_character_option_repository ) yield mock_character_option_repository # Clean up after test if "repository" in search_character_option_module._repository_context: del search_character_option_module._repository_context["repository"] @pytest.mark.asyncio async def test_search_character_option_with_search_param(repository_context): """Test character option search with search parameter.""" repository_context.search.return_value = [{"name": "Fighter", "hit_dice": "1d10"}] result = await search_character_option(type="class", search="martial combat warrior") assert len(result) == 1 assert result[0]["name"] == "Fighter" # Verify repository.search was called with search parameter repository_context.search.assert_awaited_once() call_kwargs = repository_context.search.call_args[1] assert call_kwargs["search"] == "martial combat warrior" @pytest.mark.asyncio async def test_search_character_option_search_param_with_filters(repository_context): """Test character option search combining search with traditional filters.""" repository_context.search.return_value = [{"name": "Acolyte"}] result = await search_character_option( type="background", search="religious servant", documents=["srd-5e"] ) assert len(result) == 1 # Verify all parameters were passed to repository call_kwargs = repository_context.search.call_args[1] assert call_kwargs["search"] == "religious servant" assert call_kwargs["document"] == ["srd-5e"] @pytest.mark.asyncio async def test_search_character_option_search_none_not_passed(repository_context): """Test that search=None is not passed to repository.""" repository_context.search.return_value = [{"name": "Fighter"}] # Call without search (default is None) await search_character_option(type="class") call_kwargs = repository_context.search.call_args[1] # search should not be in the params when None assert "search" not in call_kwargs

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/frap129/lorekeeper-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server