ghost-mcp/tests/e2e/test_e2e_search.py
2025-09-23 23:16:08 -03:00

290 lines
No EOL
12 KiB
Python

"""End-to-end tests for Ghost search functionality."""
import json
import pytest
from .conftest import BaseE2ETest
@pytest.mark.e2e
class TestSearchE2E(BaseE2ETest):
"""Test search functionality end-to-end."""
async def test_search_posts_basic(self, mcp_server, sample_published_post):
"""Test basic post search functionality."""
from ghost_mcp.tools.content.posts import search_posts
# Extract a searchable term from the test post title
search_term = sample_published_post["title"].split()[0]
# Search for posts
result = await search_posts(search_term)
response = json.loads(result)
# Verify response structure
assert "posts" in response
assert isinstance(response["posts"], list)
# Should find our test post
post_titles = [post["title"] for post in response["posts"]]
matching_posts = [title for title in post_titles if search_term in title]
assert len(matching_posts) > 0, f"Should find posts matching '{search_term}'"
async def test_search_posts_with_limit(self, mcp_server, sample_published_post):
"""Test post search with result limit."""
from ghost_mcp.tools.content.posts import search_posts
# Use a broad search term
search_term = "test"
# Search with limit
result = await search_posts(search_term, limit=3)
response = json.loads(result)
# Verify limit is respected
assert "posts" in response
assert len(response["posts"]) <= 3
async def test_search_posts_case_insensitive(self, mcp_server, sample_published_post):
"""Test that post search is case insensitive."""
from ghost_mcp.tools.content.posts import search_posts
# Get a word from the title in different cases
original_word = sample_published_post["title"].split()[0]
lowercase_search = original_word.lower()
uppercase_search = original_word.upper()
# Search with lowercase
lowercase_result = await search_posts(lowercase_search)
lowercase_response = json.loads(lowercase_result)
# Search with uppercase
uppercase_result = await search_posts(uppercase_search)
uppercase_response = json.loads(uppercase_result)
# Both should return similar results
assert len(lowercase_response["posts"]) > 0
assert len(uppercase_response["posts"]) > 0
# Should find the same post regardless of case
lowercase_titles = [post["title"] for post in lowercase_response["posts"]]
uppercase_titles = [post["title"] for post in uppercase_response["posts"]]
test_post_title = sample_published_post["title"]
if test_post_title in lowercase_titles:
assert test_post_title in uppercase_titles
async def test_search_posts_partial_match(self, mcp_server, sample_published_post):
"""Test that post search supports partial word matching."""
from ghost_mcp.tools.content.posts import search_posts
# Get a partial word from the title
full_word = sample_published_post["title"].split()[0]
if len(full_word) > 3:
partial_word = full_word[:3] # First 3 characters
# Search with partial word
result = await search_posts(partial_word)
response = json.loads(result)
# Should find posts containing the partial match
assert "posts" in response
# Note: Not all Ghost installations support partial matching,
# so we just verify the search doesn't error
async def test_search_posts_special_characters(self, mcp_server):
"""Test post search with special characters."""
from ghost_mcp.tools.content.posts import search_posts
# Test search with special characters
special_searches = ["test!", "test-post", "test_post"]
for search_term in special_searches:
try:
result = await search_posts(search_term)
response = json.loads(result)
# Should return valid response structure
assert "posts" in response
assert isinstance(response["posts"], list)
except Exception as e:
# Some special characters might not be supported,
# but should not cause server errors
assert "500" not in str(e), f"Server error with search term '{search_term}'"
async def test_search_posts_empty_query(self, mcp_server):
"""Test post search with empty query."""
from ghost_mcp.tools.content.posts import search_posts
# Search with empty string
result = await search_posts("")
response = json.loads(result)
# Empty query should return error
if "error" in response:
# Should return proper validation error
assert "required" in response["error"].lower() or "query" in response["error"].lower()
else:
# Or should return empty results or all posts
assert "posts" in response
assert isinstance(response["posts"], list)
async def test_search_posts_nonexistent_term(self, mcp_server):
"""Test post search with non-existent term."""
from ghost_mcp.tools.content.posts import search_posts
# Search for something that shouldn't exist
nonexistent_term = "xyzneverexistingtermabc123"
result = await search_posts(nonexistent_term)
response = json.loads(result)
# Should return empty results
assert "posts" in response
assert len(response["posts"]) == 0
async def test_search_posts_common_words(self, mcp_server):
"""Test post search with common words."""
from ghost_mcp.tools.content.posts import search_posts
# Test with common words
common_words = ["the", "and", "is", "test"]
for word in common_words:
result = await search_posts(word)
response = json.loads(result)
# Should return valid response
assert "posts" in response
assert isinstance(response["posts"], list)
async def test_search_posts_unicode_characters(self, mcp_server):
"""Test post search with unicode characters."""
from ghost_mcp.tools.content.posts import search_posts
# Test with unicode characters
unicode_searches = ["café", "naïve", "résumé", "中文"]
for search_term in unicode_searches:
try:
result = await search_posts(search_term)
response = json.loads(result)
# Should return valid response structure
assert "posts" in response
assert isinstance(response["posts"], list)
except Exception as e:
# Unicode might not be fully supported in all Ghost configurations
# but should not cause server errors
assert "500" not in str(e), f"Server error with unicode search '{search_term}'"
async def test_search_posts_long_query(self, mcp_server):
"""Test post search with very long query."""
from ghost_mcp.tools.content.posts import search_posts
# Very long search query
long_query = "this is a very long search query that tests the system's ability to handle long search terms without breaking or causing performance issues" * 3
result = await search_posts(long_query)
response = json.loads(result)
if "error" in response:
# Long queries might be rejected with proper error
error_msg = response["error"].lower()
assert any(term in error_msg for term in ["length", "long", "request", "understood", "cannot"])
else:
# Or should handle long queries gracefully
assert "posts" in response
assert isinstance(response["posts"], list)
async def test_search_posts_multiple_words(self, mcp_server, sample_published_post):
"""Test post search with multiple words."""
from ghost_mcp.tools.content.posts import search_posts
# Get multiple words from the title
title_words = sample_published_post["title"].split()
if len(title_words) >= 2:
# Search with first two words
multi_word_search = f"{title_words[0]} {title_words[1]}"
result = await search_posts(multi_word_search)
response = json.loads(result)
# Should return valid results
assert "posts" in response
assert isinstance(response["posts"], list)
async def test_search_posts_pagination_metadata(self, mcp_server, sample_published_post):
"""Test that search results include proper pagination metadata."""
from ghost_mcp.tools.content.posts import search_posts
# Search for posts
search_term = "test"
result = await search_posts(search_term, limit=5)
response = json.loads(result)
# Should have posts array
assert "posts" in response
# May or may not have meta depending on implementation,
# but if present should be properly structured
if "meta" in response:
meta = response["meta"]
assert isinstance(meta, dict)
if "pagination" in meta:
pagination = meta["pagination"]
assert isinstance(pagination, dict)
async def test_search_posts_include_fields(self, mcp_server, sample_published_post):
"""Test search with include fields parameter."""
from ghost_mcp.tools.content.posts import search_posts
# Extract search term
search_term = sample_published_post["title"].split()[0]
# Search with include fields
result = await search_posts(search_term, include="tags,authors")
response = json.loads(result)
# Verify include fields work in search
if response["posts"]:
post = response["posts"][0]
assert "tags" in post
assert "authors" in post
async def test_search_posts_response_structure(self, mcp_server, sample_published_post):
"""Test that search results have proper post structure."""
from ghost_mcp.tools.content.posts import search_posts
# Search for posts
search_term = sample_published_post["title"].split()[0]
result = await search_posts(search_term)
response = json.loads(result)
# Verify each post has essential fields
if response["posts"]:
post = response["posts"][0]
essential_fields = ["id", "title", "slug", "created_at", "updated_at", "url"]
for field in essential_fields:
assert field in post, f"Search result should include '{field}'"
async def test_search_only_published_posts(self, mcp_server, sample_post, sample_published_post):
"""Test that search only returns published posts, not drafts."""
from ghost_mcp.tools.content.posts import search_posts
# Search with a term that might match both posts
search_term = "test"
result = await search_posts(search_term)
response = json.loads(result)
# Content API only returns published posts, so all posts in results are published
# (The Content API doesn't include a status field since all posts are published)
assert "posts" in response, "Search should return posts"
# Verify our published post might be in results
published_post_ids = [post["id"] for post in response["posts"]]
# Our draft post should NOT be in search results
assert sample_post["id"] not in published_post_ids, "Draft posts should not appear in search"