Skip to main content
Glama

OpenEdu MCP Server

test_dictionary_tools.py28.5 kB
""" Unit tests for Dictionary tools. This module contains comprehensive tests for the Dictionary API integration, including word definitions, vocabulary analysis, and educational features. """ import pytest import asyncio from unittest.mock import AsyncMock, MagicMock, patch from typing import Dict, Any, List import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src")) from tools.dictionary_tools import DictionaryTool from api.dictionary import DictionaryClient from models.definition import Definition from models.base import EducationalMetadata, GradeLevel from config import Config from services.cache_service import CacheService from services.rate_limiting_service import RateLimitingService from services.usage_service import UsageService from exceptions import ToolError, ValidationError, APIError class TestDictionaryTool: """Test cases for DictionaryTool class.""" @pytest.fixture def mock_config(self): """Create a mock configuration.""" config = MagicMock(spec=Config) # Create nested mock objects config.education = MagicMock() config.education.content_filters = MagicMock() config.education.content_filters.min_educational_relevance = 0.5 config.education.content_filters.enable_age_appropriate = True config.education.content_filters.enable_curriculum_alignment = True config.server = MagicMock() config.server.name = "test-server" config.server.version = "1.0.0" return config @pytest.fixture def mock_services(self): """Create mock services.""" cache_service = AsyncMock(spec=CacheService) rate_limiting_service = AsyncMock(spec=RateLimitingService) usage_service = AsyncMock(spec=UsageService) return cache_service, rate_limiting_service, usage_service @pytest.fixture def dictionary_tool(self, mock_config, mock_services): """Create a DictionaryTool instance with mocked dependencies.""" cache_service, rate_limiting_service, usage_service = mock_services with patch('tools.dictionary_tools.DictionaryClient') as mock_client_class: mock_client = AsyncMock(spec=DictionaryClient) mock_client_class.return_value = mock_client tool = DictionaryTool( config=mock_config, cache_service=cache_service, rate_limiting_service=rate_limiting_service, usage_service=usage_service ) tool.client = mock_client return tool @pytest.fixture def sample_dictionary_response(self): """Sample Dictionary API response.""" return { "word": "education", "phonetics": [ { "text": "/ˌɛdʒʊˈkeɪʃən/", "audio": "https://api.dictionaryapi.dev/media/pronunciations/en/education-us.mp3" } ], "meanings": [ { "partOfSpeech": "noun", "definitions": [ { "definition": "The process of receiving or giving systematic instruction, especially at a school or university.", "example": "A new system of public education", "synonyms": ["schooling", "learning", "instruction"], "antonyms": ["ignorance"] }, { "definition": "An enlightening experience.", "example": "Her work in the inner city was a real education", "synonyms": ["enlightenment", "awareness"] } ] } ], "sourceUrls": ["https://en.wiktionary.org/wiki/education"] } @pytest.fixture def sample_definition(self): """Sample Definition model instance.""" return Definition( word="education", definitions=[ "The process of receiving or giving systematic instruction, especially at a school or university.", "An enlightening experience." ], part_of_speech="noun", pronunciation="https://api.dictionaryapi.dev/media/pronunciations/en/education-us.mp3", phonetic="/ˌɛdʒʊˈkeɪʃən/", examples=[ "A new system of public education", "Her work in the inner city was a real education" ], synonyms=["schooling", "learning", "instruction", "enlightenment", "awareness"], antonyms=["ignorance"], educational_metadata=EducationalMetadata( difficulty_level="Intermediate", educational_relevance_score=0.8, grade_levels=[GradeLevel.GRADES_3_5, GradeLevel.GRADES_6_8] ), subject_areas=["education", "social_studies"] ) def test_api_name_property(self, dictionary_tool): """Test that api_name property returns correct value.""" assert dictionary_tool.api_name == "dictionary" @pytest.mark.asyncio async def test_get_word_definition_success(self, dictionary_tool, sample_dictionary_response, sample_definition): """Test successful word definition retrieval.""" # Mock the client method dictionary_tool.client.get_comprehensive_data.return_value = sample_dictionary_response # Mock the execute_with_monitoring method to call the function directly async def mock_execute(method_name, method_func, user_session=None): return await method_func() dictionary_tool.execute_with_monitoring = mock_execute # Mock Definition.from_dictionary_api with patch('tools.dictionary_tools.Definition.from_dictionary_api') as mock_from_api: mock_from_api.return_value = sample_definition result = await dictionary_tool.get_word_definition("education", grade_level="6-8") assert result is not None assert "word" in result assert "definitions" in result assert "vocabulary_analysis" in result assert "educational_recommendations" in result # Verify client was called dictionary_tool.client.get_comprehensive_data.assert_called_once_with("education") @pytest.mark.asyncio async def test_get_word_definition_not_found(self, dictionary_tool): """Test word definition when word is not found.""" # Mock the client to return empty response dictionary_tool.client.get_comprehensive_data.return_value = {} async def mock_execute(method_name, method_func, user_session=None): return await method_func() dictionary_tool.execute_with_monitoring = mock_execute with pytest.raises(ToolError, match="Word not found: nonexistent"): await dictionary_tool.get_word_definition("nonexistent") @pytest.mark.asyncio async def test_get_vocabulary_analysis_success(self, dictionary_tool, sample_dictionary_response, sample_definition): """Test successful vocabulary analysis.""" dictionary_tool.client.get_comprehensive_data.return_value = sample_dictionary_response async def mock_execute(method_name, method_func, user_session=None): return await method_func() dictionary_tool.execute_with_monitoring = mock_execute with patch('tools.dictionary_tools.Definition.from_dictionary_api') as mock_from_api: mock_from_api.return_value = sample_definition result = await dictionary_tool.get_vocabulary_analysis("education") assert result is not None assert "word" in result assert "complexity_score" in result assert "difficulty_level" in result assert "grade_level_recommendations" in result assert "subject_classifications" in result assert "vocabulary_tier" in result assert "learning_objectives" in result assert "semantic_relationships" in result assert "educational_value" in result @pytest.mark.asyncio async def test_get_word_examples_success(self, dictionary_tool): """Test successful word examples retrieval.""" # Mock the client method dictionary_tool.client.get_word_examples.return_value = [ "A new system of public education", "Her work in the inner city was a real education" ] async def mock_execute(method_name, method_func, user_session=None): return await method_func() dictionary_tool.execute_with_monitoring = mock_execute result = await dictionary_tool.get_word_examples("education", grade_level="6-8", subject="social_studies") assert result is not None assert "word" in result assert "examples" in result assert "usage_tips" in result assert "common_mistakes" in result assert result["word"] == "education" assert result["grade_level"] == "6-8" assert result["subject"] == "social_studies" @pytest.mark.asyncio async def test_get_pronunciation_guide_success(self, dictionary_tool): """Test successful pronunciation guide retrieval.""" # Mock the client method dictionary_tool.client.get_phonetics.return_value = { "text": "/ˌɛdʒʊˈkeɪʃən/", "audio": "https://api.dictionaryapi.dev/media/pronunciations/en/education-us.mp3", "source": "" } async def mock_execute(method_name, method_func, user_session=None): return await method_func() dictionary_tool.execute_with_monitoring = mock_execute result = await dictionary_tool.get_pronunciation_guide("education") assert result is not None assert "word" in result assert "phonetic_spelling" in result assert "audio_url" in result assert "pronunciation_tips" in result assert "syllable_breakdown" in result assert "difficulty_level" in result @pytest.mark.asyncio async def test_get_pronunciation_guide_not_available(self, dictionary_tool): """Test pronunciation guide when pronunciation is not available.""" dictionary_tool.client.get_phonetics.return_value = {} async def mock_execute(method_name, method_func, user_session=None): return await method_func() dictionary_tool.execute_with_monitoring = mock_execute with pytest.raises(ToolError, match="Pronunciation not available for: education"): await dictionary_tool.get_pronunciation_guide("education") @pytest.mark.asyncio async def test_get_related_vocabulary_success(self, dictionary_tool, sample_dictionary_response, sample_definition): """Test successful related vocabulary retrieval.""" dictionary_tool.client.get_comprehensive_data.return_value = sample_dictionary_response async def mock_execute(method_name, method_func, user_session=None): return await method_func() dictionary_tool.execute_with_monitoring = mock_execute with patch('tools.dictionary_tools.Definition.from_dictionary_api') as mock_from_api: mock_from_api.return_value = sample_definition result = await dictionary_tool.get_related_vocabulary("education", relationship_type="all") assert result is not None assert "base_word" in result assert "relationships" in result assert "educational_notes" in result assert "learning_activities" in result assert result["base_word"] == "education" @pytest.mark.asyncio async def test_get_related_vocabulary_invalid_type(self, dictionary_tool): """Test related vocabulary with invalid relationship type.""" async def mock_execute(method_name, method_func, user_session=None): return await method_func() dictionary_tool.execute_with_monitoring = mock_execute with pytest.raises(ValidationError, match="Invalid relationship type"): await dictionary_tool.get_related_vocabulary("education", relationship_type="invalid") def test_calculate_educational_relevance(self, dictionary_tool, sample_definition): """Test educational relevance calculation.""" score = dictionary_tool._calculate_educational_relevance(sample_definition, "6-8") assert isinstance(score, float) assert 0.0 <= score <= 1.0 assert score > 0.5 # Should be high for educational word def test_analyze_vocabulary_complexity(self, dictionary_tool, sample_definition): """Test vocabulary complexity analysis.""" analysis = dictionary_tool._analyze_vocabulary_complexity(sample_definition) assert "complexity_score" in analysis assert "difficulty_level" in analysis assert "word_length" in analysis assert "syllable_count" in analysis assert "definition_complexity" in analysis assert "has_multiple_meanings" in analysis assert "technical_indicators" in analysis assert isinstance(analysis["complexity_score"], float) assert analysis["difficulty_level"] in ["Elementary", "Intermediate", "Advanced", "Expert"] def test_determine_appropriate_grade_levels(self, dictionary_tool, sample_definition): """Test grade level determination.""" grade_levels = dictionary_tool._determine_appropriate_grade_levels(sample_definition) assert isinstance(grade_levels, list) assert len(grade_levels) > 0 assert all(isinstance(level, GradeLevel) for level in grade_levels) def test_classify_by_subject(self, dictionary_tool, sample_definition): """Test subject classification.""" subjects = dictionary_tool._classify_by_subject(sample_definition) assert isinstance(subjects, list) # The method should return a list (may be empty for words that don't match subject indicators) # For "education" word, it might not match the specific indicators in subject_indicators # This is expected behavior as the classification is based on specific keywords def test_simplify_for_grade_level(self, dictionary_tool, sample_definition): """Test definition simplification for lower grade levels.""" simplified = dictionary_tool._simplify_for_grade_level(sample_definition, "K-2") assert len(simplified.definitions) <= 2 assert len(simplified.examples) <= 3 assert all(len(def_text.split()) <= 15 for def_text in simplified.definitions) def test_count_syllables(self, dictionary_tool): """Test syllable counting.""" assert dictionary_tool._count_syllables("cat") == 1 assert dictionary_tool._count_syllables("education") >= 3 assert dictionary_tool._count_syllables("beautiful") >= 2 def test_determine_vocabulary_tier(self, dictionary_tool, sample_definition): """Test vocabulary tier determination.""" tier = dictionary_tool._determine_vocabulary_tier(sample_definition) assert tier in ["Tier 1 (Basic)", "Tier 2 (Academic)", "Tier 3 (Domain-specific)"] def test_generate_learning_objectives(self, dictionary_tool, sample_definition): """Test learning objectives generation.""" objectives = dictionary_tool._generate_learning_objectives(sample_definition) assert isinstance(objectives, list) assert len(objectives) > 0 assert all(isinstance(obj, str) for obj in objectives) assert any("understand the meaning" in obj.lower() for obj in objectives) def test_estimate_usage_frequency(self, dictionary_tool, sample_definition): """Test usage frequency estimation.""" frequency = dictionary_tool._estimate_usage_frequency(sample_definition) assert frequency in [ "High frequency (common word)", "Medium frequency (academic word)", "Low frequency (specialized word)", "Very low frequency (technical/rare word)" ] def test_analyze_morphology(self, dictionary_tool): """Test morphological analysis.""" analysis = dictionary_tool._analyze_morphology("education") assert "root" in analysis assert "prefixes" in analysis assert "suffixes" in analysis assert "word_family" in analysis assert isinstance(analysis["prefixes"], list) assert isinstance(analysis["suffixes"], list) def test_generate_educational_examples(self, dictionary_tool): """Test educational example generation.""" examples = dictionary_tool._generate_educational_examples("education", "6-8", "social_studies") assert isinstance(examples, list) assert len(examples) > 0 assert all(isinstance(example, str) for example in examples) def test_enhance_example_for_education(self, dictionary_tool): """Test example enhancement for educational use.""" example = "A new system of public education" enhanced = dictionary_tool._enhance_example_for_education(example, "education", "6-8", "social_studies") assert enhanced is not None assert "sentence" in enhanced assert "target_word" in enhanced assert "context_clues" in enhanced assert "educational_focus" in enhanced assert "difficulty_level" in enhanced def test_identify_context_clues(self, dictionary_tool): """Test context clue identification.""" example = "A new system of public education" clues = dictionary_tool._identify_context_clues(example, "education") assert isinstance(clues, list) assert len(clues) <= 3 def test_generate_pronunciation_tips(self, dictionary_tool): """Test pronunciation tip generation.""" phonetics = {"text": "/ˌɛdʒʊˈkeɪʃən/", "audio": "test.mp3"} tips = dictionary_tool._generate_pronunciation_tips("education", phonetics) assert isinstance(tips, list) assert len(tips) > 0 assert all(isinstance(tip, str) for tip in tips) def test_break_into_syllables(self, dictionary_tool): """Test syllable breaking.""" syllables = dictionary_tool._break_into_syllables("education") assert isinstance(syllables, str) assert "-" in syllables or syllables == "education" # Either broken or single syllable def test_assess_pronunciation_difficulty(self, dictionary_tool): """Test pronunciation difficulty assessment.""" phonetics = {"text": "/ˌɛdʒʊˈkeɪʃən/"} difficulty = dictionary_tool._assess_pronunciation_difficulty("education", phonetics) assert difficulty in ["Easy", "Moderate", "Challenging"] @pytest.mark.asyncio async def test_health_check_success(self, dictionary_tool): """Test successful health check.""" # Mock the client health check dictionary_tool.client.health_check.return_value = { "status": "healthy", "response_time_ms": 100, "api_accessible": True, "timestamp": "2023-01-01T00:00:00" } result = await dictionary_tool.health_check() assert result["tool_name"] == "dictionary" assert result["status"] == "healthy" assert "api_status" in result assert "features" in result assert "timestamp" in result @pytest.mark.asyncio async def test_health_check_failure(self, dictionary_tool): """Test health check when API is unhealthy.""" # Mock the client health check to raise an exception dictionary_tool.client.health_check.side_effect = Exception("API unavailable") result = await dictionary_tool.health_check() assert result["tool_name"] == "dictionary" assert result["status"] == "unhealthy" assert "error" in result assert "timestamp" in result class TestDictionaryClient: """Test cases for DictionaryClient class.""" @pytest.fixture def mock_config(self): """Create a mock configuration.""" config = MagicMock(spec=Config) config.server = MagicMock() config.server.name = "test-server" config.server.version = "1.0.0" config.apis = MagicMock() return config @pytest.fixture def dictionary_client(self, mock_config): """Create a DictionaryClient instance.""" return DictionaryClient(mock_config) def test_validate_word_success(self, dictionary_client): """Test successful word validation.""" assert dictionary_client._validate_word("education") == "education" assert dictionary_client._validate_word("EDUCATION") == "education" assert dictionary_client._validate_word(" education ") == "education" assert dictionary_client._validate_word("mother-in-law") == "mother-in-law" assert dictionary_client._validate_word("don't") == "don't" def test_validate_word_failure(self, dictionary_client): """Test word validation failures.""" with pytest.raises(ValidationError, match="Word cannot be empty"): dictionary_client._validate_word("") with pytest.raises(ValidationError, match="Word cannot be empty"): dictionary_client._validate_word(" ") with pytest.raises(ValidationError, match="Word contains invalid characters"): dictionary_client._validate_word("education123") with pytest.raises(ValidationError, match="Word is too long"): dictionary_client._validate_word("a" * 51) @pytest.mark.asyncio async def test_get_definition_success(self, dictionary_client): """Test successful definition retrieval.""" mock_response = { "word": "education", "meanings": [ { "partOfSpeech": "noun", "definitions": [ {"definition": "The process of learning"} ] } ] } with patch.object(dictionary_client, '_make_request', return_value=[mock_response]): result = await dictionary_client.get_definition("education") assert result == mock_response assert result["word"] == "education" @pytest.mark.asyncio async def test_get_definition_not_found(self, dictionary_client): """Test definition retrieval when word is not found.""" with patch.object(dictionary_client, '_make_request', return_value={}): result = await dictionary_client.get_definition("nonexistent") assert result == {} @pytest.mark.asyncio async def test_get_word_synonyms_success(self, dictionary_client): """Test successful synonym retrieval.""" mock_response = { "meanings": [ { "definitions": [ {"synonyms": ["learning", "schooling"]}, {"synonyms": ["instruction"]} ] } ] } with patch.object(dictionary_client, 'get_definition', return_value=mock_response): result = await dictionary_client.get_word_synonyms("education") assert isinstance(result, list) assert "learning" in result assert "schooling" in result assert "instruction" in result @pytest.mark.asyncio async def test_get_word_examples_success(self, dictionary_client): """Test successful example retrieval.""" mock_response = { "meanings": [ { "definitions": [ {"example": "A new system of public education"}, {"example": "Her work was a real education"} ] } ] } with patch.object(dictionary_client, 'get_definition', return_value=mock_response): result = await dictionary_client.get_word_examples("education") assert isinstance(result, list) assert "A new system of public education" in result assert "Her work was a real education" in result @pytest.mark.asyncio async def test_get_phonetics_success(self, dictionary_client): """Test successful phonetics retrieval.""" mock_response = { "phonetics": [ { "text": "/ˌɛdʒʊˈkeɪʃən/", "audio": "https://api.dictionaryapi.dev/media/pronunciations/en/education-us.mp3" } ] } with patch.object(dictionary_client, 'get_definition', return_value=mock_response): result = await dictionary_client.get_phonetics("education") assert "text" in result assert "audio" in result assert result["text"] == "/ˌɛdʒʊˈkeɪʃən/" @pytest.mark.asyncio async def test_get_comprehensive_data_success(self, dictionary_client): """Test successful comprehensive data retrieval.""" mock_response = { "word": "education", "phonetics": [{"text": "/ˌɛdʒʊˈkeɪʃən/", "audio": "test.mp3"}], "meanings": [ { "partOfSpeech": "noun", "definitions": [ { "definition": "The process of learning", "example": "A new system of education", "synonyms": ["learning"], "antonyms": ["ignorance"] } ] } ] } with patch.object(dictionary_client, 'get_definition', return_value=mock_response): with patch.object(dictionary_client, 'get_phonetics', return_value={"text": "/ˌɛdʒʊˈkeɪʃən/", "audio": "test.mp3"}): result = await dictionary_client.get_comprehensive_data("education") assert "word" in result assert "phonetics" in result assert "meanings" in result assert "definitions" in result assert "synonyms" in result assert "antonyms" in result @pytest.mark.asyncio async def test_health_check_success(self, dictionary_client): """Test successful health check.""" mock_response = { "word": "test", "meanings": [{"partOfSpeech": "noun", "definitions": [{"definition": "A test"}]}] } with patch.object(dictionary_client, 'get_definition', return_value=mock_response): result = await dictionary_client.health_check() assert result["status"] == "healthy" assert result["api_accessible"] is True assert "response_time_ms" in result assert "timestamp" in result @pytest.mark.asyncio async def test_health_check_failure(self, dictionary_client): """Test health check when API fails.""" with patch.object(dictionary_client, 'get_definition', side_effect=Exception("API error")): result = await dictionary_client.health_check() assert result["status"] == "unhealthy" assert result["api_accessible"] is False assert "error" in result assert "timestamp" in result if __name__ == "__main__": pytest.main([__file__])

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/Cicatriiz/openedu-mcp'

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