We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/brianirish/laravel-mcp-companion'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
"""Unit tests for actual MCP tool functions exposed to AI assistants."""
from unittest.mock import patch
import laravel_mcp_companion
# Import the standalone functions from mcp_tools
from mcp_tools import (
list_laravel_docs_impl,
read_laravel_doc_content_impl,
search_laravel_docs_impl,
search_laravel_docs_with_context_impl,
get_doc_structure_impl,
browse_docs_by_category_impl,
verify_laravel_feature_impl,
compare_laravel_versions_impl
)
class TestDocumentationTools:
"""Test documentation-related MCP tool functions."""
@patch('mcp_tools.os.listdir')
@patch('mcp_tools.get_laravel_docs_metadata')
def test_list_laravel_docs_specific_version(self, mock_metadata, mock_listdir, test_docs_dir):
"""Test listing docs for a specific version returns TOON format."""
mock_listdir.return_value = ['installation.md', 'routing.md', 'eloquent.md']
mock_metadata.return_value = {
'version': '12.x',
'sync_time': '2024-01-01T12:00:00Z',
'commit_sha': 'abc123def456'
}
result = list_laravel_docs_impl(test_docs_dir, "12.x")
# TOON format assertions - check for data presence
assert "12.x" in result
assert "installation.md" in result
assert "routing.md" in result
assert "eloquent.md" in result
assert "2024-01-01T12:00:00Z" in result
@patch('mcp_tools.os.listdir')
def test_list_laravel_docs_no_version_specified(self, mock_listdir, test_docs_dir):
"""Test listing docs when no version is specified returns TOON format."""
# Directories already created by fixture, no need to create them
# Set up proper mock for os.listdir and os.path.isfile checks
with patch('mcp_tools.os.path.isfile') as mock_isfile:
mock_isfile.return_value = True # All files exist
mock_listdir.side_effect = [
['installation.md', 'routing.md'], # 11.x files
['installation.md', 'routing.md', 'eloquent.md'], # 12.x files
['installation.md', 'routing.md'], # 11.x files again for formatting
['installation.md', 'routing.md', 'eloquent.md'] # 12.x files again for formatting
]
with patch('mcp_tools.get_laravel_docs_metadata') as mock_metadata:
mock_metadata.return_value = {'sync_time': '2024-01-01T12:00:00Z', 'commit_sha': 'abc123'}
result = list_laravel_docs_impl(test_docs_dir)
# TOON format - check for version data
assert "version" in result or "12.x" in result or "11.x" in result
def test_list_laravel_docs_no_documentation(self, test_docs_dir):
"""Test listing docs when no documentation exists."""
empty_dir = test_docs_dir / "empty"
empty_dir.mkdir()
result = list_laravel_docs_impl(empty_dir, "12.x")
assert "No documentation found for version 12.x" in result
assert "update_laravel_docs()" in result
def test_read_laravel_doc_content_success(self, test_docs_dir):
"""Test reading Laravel documentation content successfully."""
# Use existing version directory from fixture
version_dir = test_docs_dir / "12.x"
test_file = version_dir / "routing.md"
test_content = "# Routing\n\nLaravel routing documentation."
test_file.write_text(test_content)
result = read_laravel_doc_content_impl(test_docs_dir, "routing.md", "12.x")
assert result == test_content
def test_read_laravel_doc_content_file_not_found(self, test_docs_dir):
"""Test reading non-existent documentation file."""
# Directory already exists from fixture
result = read_laravel_doc_content_impl(test_docs_dir, "nonexistent.md", "12.x")
assert "Documentation file not found" in result
def test_read_laravel_doc_content_without_md_extension(self, test_docs_dir):
"""Test reading documentation file without .md extension."""
version_dir = test_docs_dir / "12.x"
test_file = version_dir / "routing.md"
test_content = "# Routing\n\nLaravel routing documentation."
test_file.write_text(test_content)
result = read_laravel_doc_content_impl(test_docs_dir, "routing", "12.x") # No .md extension
assert result == test_content
@patch('mcp_tools.get_file_content_cached')
@patch('mcp_tools.os.listdir')
def test_search_laravel_docs_success(self, mock_listdir, mock_get_content, test_docs_dir):
"""Test searching Laravel documentation successfully returns TOON format."""
# Directory already exists from fixture
# Mock file listing
mock_listdir.return_value = ['routing.md', 'eloquent.md']
# Mock file content
mock_get_content.side_effect = [
"# Routing\n\nLaravel routing is awesome for web applications",
"# Eloquent\n\nEloquent ORM for database routing"
]
with patch('mcp_tools.SUPPORTED_VERSIONS', ['12.x']):
result = search_laravel_docs_impl(test_docs_dir, "routing", "12.x")
# TOON format assertions
assert "routing" in result
assert "12.x/routing.md" in result or "routing.md" in result
assert "12.x/eloquent.md" in result or "eloquent.md" in result
def test_search_laravel_docs_empty_query(self, test_docs_dir):
"""Test searching with empty query."""
result = search_laravel_docs_impl(test_docs_dir, "", "12.x")
assert "Search query cannot be empty" in result
def test_search_laravel_docs_no_results(self, test_docs_dir):
"""Test searching with no matches."""
# Directory already exists from fixture
with patch('mcp_tools.os.listdir', return_value=['routing.md']), \
patch('mcp_tools.get_file_content_cached', return_value="# Routing\n\nBasic routing info"), \
patch('mcp_tools.SUPPORTED_VERSIONS', ['12.x']):
result = search_laravel_docs_impl(test_docs_dir, "nonexistent_term", "12.x")
assert "No results found for 'nonexistent_term'" in result
def test_search_laravel_docs_with_context_success(self, test_docs_dir):
"""Test searching with context snippets."""
test_content = """# Laravel Routing
Laravel routing is simple and powerful. You can define routes in your web.php file.
## Basic Routing
The most basic Laravel routes accept a URI and a closure, providing a very simple and expressive method of defining routes.
```php
Route::get('/', function () {
return view('welcome');
});
```
This is how routing works in Laravel applications."""
# Directory already exists from fixture
with patch('mcp_tools.os.listdir', return_value=['routing.md']), \
patch('mcp_tools.get_file_content_cached', return_value=test_content), \
patch('mcp_tools.SUPPORTED_VERSIONS', ['12.x']):
result = search_laravel_docs_with_context_impl(test_docs_dir, "routing", "12.x", context_length=100)
assert "Search results for 'routing':" in result
assert "12.x/routing.md" in result
assert "**routing**" in result # Highlighted match
def test_get_doc_structure_success(self, test_docs_dir):
"""Test getting document structure returns TOON format."""
test_content = """# Laravel Routing
Laravel routing is simple and powerful.
## Basic Routing
The most basic Laravel routes accept a URI and a closure.
### Route Methods
Laravel provides methods for all HTTP verbs.
## Route Parameters
You can capture segments of the URI within your route.
"""
with patch('mcp_tools.read_laravel_doc_content_impl', return_value=test_content):
result = get_doc_structure_impl(test_docs_dir, "routing.md", "12.x")
# TOON format assertions - check for heading data
assert "routing.md" in result
assert "Laravel Routing" in result
assert "Basic Routing" in result
assert "Route Methods" in result
assert "Route Parameters" in result
def test_browse_docs_by_category_frontend(self, test_docs_dir):
"""Test browsing docs by frontend category returns TOON format."""
version_dir = test_docs_dir / "12.x"
# Create frontend-related files
(version_dir / "vite.md").write_text("# Vite\n\nAsset bundling with Vite.")
(version_dir / "blade.md").write_text("# Blade Templates\n\nTemplating engine.")
(version_dir / "routing.md").write_text("# Routing\n\nNot frontend related.")
with patch('mcp_tools.os.listdir', return_value=['vite.md', 'blade.md', 'routing.md']):
result = browse_docs_by_category_impl(test_docs_dir, "frontend", "12.x")
# TOON format assertions
assert "frontend" in result.lower()
assert "vite.md" in result
assert "blade.md" in result
# routing.md shouldn't be included as it doesn't match frontend keywords
class TestPackageRecommendationTools:
"""Test package recommendation MCP tool functions."""
def test_get_laravel_package_recommendations_success(self, sample_package_catalog):
"""Test getting package recommendations successfully returns TOON format."""
with patch.dict(laravel_mcp_companion.PACKAGE_CATALOG, sample_package_catalog):
from laravel_mcp_companion import get_laravel_package_recommendations
result = get_laravel_package_recommendations("authentication for SPA")
# TOON format assertions
assert "authentication" in result.lower()
assert "Laravel Sanctum" in result or "Sanctum" in result
assert "laravel/sanctum" in result
def test_get_laravel_package_recommendations_no_matches(self, sample_package_catalog):
"""Test getting package recommendations with no matches returns TOON error."""
with patch.dict(laravel_mcp_companion.PACKAGE_CATALOG, sample_package_catalog):
from laravel_mcp_companion import get_laravel_package_recommendations
result = get_laravel_package_recommendations("quantum computing")
# TOON error format
assert "error" in result or "No packages found" in result
def test_get_laravel_package_info_success(self, sample_package_catalog):
"""Test getting specific package info successfully returns TOON format."""
with patch.dict(laravel_mcp_companion.PACKAGE_CATALOG, sample_package_catalog):
from laravel_mcp_companion import get_laravel_package_info
result = get_laravel_package_info("laravel/cashier")
# TOON format assertions
assert "Laravel Cashier" in result or "Cashier" in result
assert "subscription" in result.lower() or "billing" in result.lower()
assert "laravel/cashier" in result
def test_get_laravel_package_info_not_found(self, sample_package_catalog):
"""Test getting info for non-existent package returns TOON error."""
with patch.dict(laravel_mcp_companion.PACKAGE_CATALOG, sample_package_catalog):
from laravel_mcp_companion import get_laravel_package_info
result = get_laravel_package_info("nonexistent/package")
assert "error" in result or "not found" in result.lower()
def test_get_laravel_package_categories_success(self, sample_package_catalog):
"""Test getting packages by category successfully returns TOON format."""
with patch.dict(laravel_mcp_companion.PACKAGE_CATALOG, sample_package_catalog):
from laravel_mcp_companion import get_laravel_package_categories
result = get_laravel_package_categories("authentication")
# TOON format assertions
assert "authentication" in result.lower()
assert "Laravel Sanctum" in result or "Sanctum" in result
assert "laravel/sanctum" in result
def test_get_laravel_package_categories_no_matches(self, sample_package_catalog):
"""Test getting packages by category with no matches returns TOON error."""
with patch.dict(laravel_mcp_companion.PACKAGE_CATALOG, sample_package_catalog):
from laravel_mcp_companion import get_laravel_package_categories
result = get_laravel_package_categories("nonexistent_category")
assert "error" in result or "No packages found" in result
def test_get_features_for_laravel_package_success(self, sample_package_catalog):
"""Test getting features for a package successfully returns TOON format."""
test_features = ["spa-authentication", "api-tokens", "token-abilities"]
with patch.dict(laravel_mcp_companion.PACKAGE_CATALOG, sample_package_catalog), \
patch.dict(laravel_mcp_companion.FEATURE_MAP, {'laravel/sanctum': test_features}):
from laravel_mcp_companion import get_features_for_laravel_package
result = get_features_for_laravel_package("laravel/sanctum")
# TOON format assertions
assert "laravel/sanctum" in result
assert "spa-authentication" in result
assert "api-tokens" in result
assert "token-abilities" in result
def test_get_features_for_laravel_package_not_found(self, sample_package_catalog):
"""Test getting features for non-existent package returns TOON error."""
with patch.dict(laravel_mcp_companion.PACKAGE_CATALOG, sample_package_catalog):
from laravel_mcp_companion import get_features_for_laravel_package
result = get_features_for_laravel_package("nonexistent/package")
assert "error" in result or "not found" in result.lower()
def test_get_features_for_laravel_package_no_features(self, sample_package_catalog):
"""Test getting features for package with no defined features returns TOON format."""
# Patch FEATURE_MAP to be empty for this specific package
empty_feature_map = {}
with patch.dict(laravel_mcp_companion.PACKAGE_CATALOG, sample_package_catalog), \
patch.dict(laravel_mcp_companion.FEATURE_MAP, empty_feature_map, clear=True):
from laravel_mcp_companion import get_features_for_laravel_package
result = get_features_for_laravel_package("laravel/sanctum")
# TOON format - should still have package data with use_cases
assert "laravel/sanctum" in result
class TestMcpToolsEdgeCases:
"""Test edge cases and exception handlers in mcp_tools."""
def test_is_safe_path_exception_handling(self, test_docs_dir):
"""Test that is_safe_path handles exceptions gracefully."""
from mcp_tools import is_safe_path
# Test with paths that cause exceptions (invalid paths)
with patch('mcp_tools.Path.resolve') as mock_resolve:
mock_resolve.side_effect = Exception("Permission denied")
# Should return False when exception occurs, not raise
result = is_safe_path("/some/path", "/base")
assert result is False
def test_get_laravel_docs_metadata_json_error(self, test_docs_dir):
"""Test metadata reading handles malformed JSON."""
from mcp_tools import get_laravel_docs_metadata
# Create malformed metadata file
metadata_dir = test_docs_dir / "12.x" / ".metadata"
metadata_dir.mkdir(parents=True, exist_ok=True)
(metadata_dir / "sync_info.json").write_text("{ invalid json }")
# Should return empty dict on JSON error
result = get_laravel_docs_metadata(test_docs_dir, "12.x")
assert result == {}
def test_list_laravel_docs_no_md_files(self, test_docs_dir):
"""Test listing docs when version exists but has no .md files."""
from mcp_tools import list_laravel_docs_impl
# Create empty version dir (no .md files)
empty_version = test_docs_dir / "13.x"
empty_version.mkdir()
result = list_laravel_docs_impl(test_docs_dir, "13.x")
# Should indicate no files found
assert "13.x" in result or "No documentation" in result
def test_list_laravel_docs_all_versions_empty(self, test_docs_dir):
"""Test listing all docs when no versions have files."""
from mcp_tools import list_laravel_docs_impl
# Create completely empty docs dir
empty_docs = test_docs_dir.parent / "empty_docs"
empty_docs.mkdir()
result = list_laravel_docs_impl(empty_docs)
# Should indicate no documentation found
assert "No documentation files found" in result or "error" in result.lower()
def test_list_laravel_docs_exception_handling(self, test_docs_dir):
"""Test list_laravel_docs handles exceptions gracefully."""
from mcp_tools import list_laravel_docs_impl
with patch('mcp_tools.os.listdir') as mock_listdir:
mock_listdir.side_effect = OSError("Permission denied")
result = list_laravel_docs_impl(test_docs_dir, "12.x")
# Should return error message
assert "Error" in result
def test_browse_docs_by_category_unknown_category(self, test_docs_dir):
"""Test browsing docs with unknown category."""
from mcp_tools import browse_docs_by_category_impl
result = browse_docs_by_category_impl(test_docs_dir, "unknown_category", "12.x")
assert "Unknown category" in result
assert "available_categories" in result.lower() or "frontend" in result.lower()
def test_browse_docs_by_category_version_not_found(self, test_docs_dir):
"""Test browsing docs when version doesn't exist."""
from mcp_tools import browse_docs_by_category_impl
result = browse_docs_by_category_impl(test_docs_dir, "frontend", "99.x")
assert "No documentation found for version 99.x" in result
def test_browse_docs_by_category_no_matching_files(self, test_docs_dir):
"""Test browsing category with no matching files."""
from mcp_tools import browse_docs_by_category_impl
# Create version with files that don't match any category
version_dir = test_docs_dir / "12.x"
# Remove existing files and create non-matching ones
for f in version_dir.glob("*.md"):
f.unlink()
(version_dir / "random_topic.md").write_text("# Random\n\nUnrelated content.")
result = browse_docs_by_category_impl(test_docs_dir, "frontend", "12.x")
assert "No frontend documentation files found" in result
def test_browse_docs_by_category_exception(self, test_docs_dir):
"""Test browse_docs_by_category handles exceptions."""
from mcp_tools import browse_docs_by_category_impl
with patch('mcp_tools.os.listdir') as mock_listdir:
mock_listdir.side_effect = OSError("Disk error")
result = browse_docs_by_category_impl(test_docs_dir, "frontend", "12.x")
assert "Error browsing documentation" in result
def test_search_laravel_docs_exception_handling(self, test_docs_dir):
"""Test search_laravel_docs handles exceptions gracefully."""
from mcp_tools import search_laravel_docs_impl
with patch('mcp_tools.os.listdir') as mock_listdir:
mock_listdir.side_effect = Exception("Unexpected error")
# Need to patch SUPPORTED_VERSIONS to include 12.x
with patch('mcp_tools.SUPPORTED_VERSIONS', ['12.x']):
result = search_laravel_docs_impl(test_docs_dir, "test", "12.x")
# Should handle exception gracefully
# The actual implementation might return error or empty results
assert result is not None
def test_search_with_context_no_results(self, test_docs_dir):
"""Test search with context returns appropriate message when no results."""
from mcp_tools import search_laravel_docs_with_context_impl
with patch('mcp_tools.os.listdir', return_value=['test.md']), \
patch('mcp_tools.get_file_content_cached', return_value="# Test\n\nNo match here"), \
patch('mcp_tools.SUPPORTED_VERSIONS', ['12.x']):
result = search_laravel_docs_with_context_impl(
test_docs_dir, "nonexistent_query_xyz", "12.x"
)
assert "No results found" in result
def test_search_with_context_exception(self, test_docs_dir):
"""Test search with context handles exceptions."""
from mcp_tools import search_laravel_docs_with_context_impl
with patch('mcp_tools.os.listdir') as mock_listdir:
mock_listdir.side_effect = Exception("Search error")
with patch('mcp_tools.SUPPORTED_VERSIONS', ['12.x']):
result = search_laravel_docs_with_context_impl(
test_docs_dir, "test", "12.x"
)
assert "Error" in result
class TestVersionComparisonTools:
"""Test version comparison and feature verification tools."""
def test_verify_laravel_feature_exact_match(self, test_docs_dir):
"""Test verify_laravel_feature with exact filename match."""
# test_docs_dir fixture already has routing.md
with patch('mcp_tools.SUPPORTED_VERSIONS', ['11.x', '12.x']):
result = verify_laravel_feature_impl(test_docs_dir, "routing", "12.x")
# TOON format assertions
assert "routing" in result
assert "12.x" in result
assert "true" in result.lower() or "found" in result
assert "routing.md" in result
def test_verify_laravel_feature_partial_match(self, test_docs_dir):
"""Test verify_laravel_feature with partial matches."""
# Create auth-related files
version_dir = test_docs_dir / "12.x"
(version_dir / "authentication.md").write_text("# Authentication")
(version_dir / "authorization.md").write_text("# Authorization")
with patch('mcp_tools.SUPPORTED_VERSIONS', ['11.x', '12.x']):
result = verify_laravel_feature_impl(test_docs_dir, "auth", "12.x")
# Should find both auth files
assert "auth" in result
assert "authentication.md" in result
assert "authorization.md" in result
assert "true" in result.lower() or "found" in result
def test_verify_laravel_feature_not_found(self, test_docs_dir):
"""Test verify_laravel_feature when feature doesn't exist."""
with patch('mcp_tools.SUPPORTED_VERSIONS', ['11.x', '12.x']):
result = verify_laravel_feature_impl(test_docs_dir, "nonexistent", "12.x")
assert "nonexistent" in result
assert "false" in result.lower() or '"found": false' in result.lower() or "found\nfalse" in result.lower()
def test_verify_laravel_feature_empty_feature(self, test_docs_dir):
"""Test verify_laravel_feature with empty feature name."""
with patch('mcp_tools.SUPPORTED_VERSIONS', ['11.x', '12.x']):
result = verify_laravel_feature_impl(test_docs_dir, "", "12.x")
assert "error" in result.lower() or "cannot be empty" in result
def test_verify_laravel_feature_version_not_found(self, test_docs_dir):
"""Test verify_laravel_feature with non-existent version."""
with patch('mcp_tools.SUPPORTED_VERSIONS', ['11.x', '12.x', '99.x']):
result = verify_laravel_feature_impl(test_docs_dir, "blade", "99.x")
assert "error" in result.lower() or "not found" in result.lower()
def test_compare_laravel_versions_added_files(self, test_docs_dir):
"""Test compare_laravel_versions showing added files."""
# Add a new file only in 12.x
(test_docs_dir / "12.x" / "new-feature.md").write_text("# New Feature")
with patch('mcp_tools.SUPPORTED_VERSIONS', ['11.x', '12.x']):
result = compare_laravel_versions_impl(test_docs_dir, "11.x", "12.x")
# TOON format assertions
assert "11.x" in result
assert "12.x" in result
assert "new-feature.md" in result
assert "added" in result.lower()
def test_compare_laravel_versions_removed_files(self, test_docs_dir):
"""Test compare_laravel_versions showing removed files."""
# Add a file only in 11.x
(test_docs_dir / "11.x" / "deprecated.md").write_text("# Deprecated")
with patch('mcp_tools.SUPPORTED_VERSIONS', ['11.x', '12.x']):
result = compare_laravel_versions_impl(test_docs_dir, "11.x", "12.x")
assert "deprecated.md" in result
assert "removed" in result.lower()
def test_compare_laravel_versions_with_filter(self, test_docs_dir):
"""Test compare_laravel_versions with file filter."""
# Create auth files in both versions
(test_docs_dir / "11.x" / "authentication.md").write_text("# Auth v11")
(test_docs_dir / "12.x" / "authentication.md").write_text("# Auth v12")
(test_docs_dir / "12.x" / "authorization.md").write_text("# Authz v12")
with patch('mcp_tools.SUPPORTED_VERSIONS', ['11.x', '12.x']):
result = compare_laravel_versions_impl(test_docs_dir, "11.x", "12.x", file_filter="auth")
# Should only show auth-related files
assert "auth" in result.lower()
assert "authentication.md" in result
# Should show filter was applied
assert "filter" in result.lower() or "auth" in result
def test_compare_laravel_versions_same_version_error(self, test_docs_dir):
"""Test compare_laravel_versions with same source and target."""
with patch('mcp_tools.SUPPORTED_VERSIONS', ['11.x', '12.x']):
result = compare_laravel_versions_impl(test_docs_dir, "12.x", "12.x")
assert "error" in result.lower() or "cannot be the same" in result.lower()
def test_compare_laravel_versions_invalid_version(self, test_docs_dir):
"""Test compare_laravel_versions with invalid version."""
with patch('mcp_tools.SUPPORTED_VERSIONS', ['11.x', '12.x']):
result = compare_laravel_versions_impl(test_docs_dir, "99.x", "12.x")
assert "error" in result.lower() or "invalid" in result.lower()
def test_compare_laravel_versions_metadata_included(self, test_docs_dir):
"""Test compare_laravel_versions includes metadata from both versions."""
with patch('mcp_tools.SUPPORTED_VERSIONS', ['11.x', '12.x']):
result = compare_laravel_versions_impl(test_docs_dir, "11.x", "12.x")
# Should include commit info from metadata
assert "commit" in result.lower() or "metadata" in result.lower()
# Check for date/sync info (from fixture metadata)
assert "2024" in result or "unknown" in result
def test_verify_laravel_feature_uses_default_version(self, test_docs_dir):
"""Test verify_laravel_feature uses default version when none specified."""
with patch('mcp_tools.SUPPORTED_VERSIONS', ['11.x', '12.x']):
with patch('mcp_tools.DEFAULT_VERSION', '12.x'):
result = verify_laravel_feature_impl(test_docs_dir, "routing", runtime_version="12.x")
assert "routing" in result
assert "12.x" in result