281 lines
No EOL
8.2 KiB
Python
281 lines
No EOL
8.2 KiB
Python
"""Fixtures for end-to-end tests."""
|
|
|
|
import asyncio
|
|
import json
|
|
import os
|
|
import uuid
|
|
from typing import AsyncGenerator, Dict, List, Any
|
|
|
|
import pytest
|
|
import httpx
|
|
|
|
from ghost_mcp.server import mcp
|
|
from ghost_mcp.client import GhostClient
|
|
from ghost_mcp.config import config
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def ensure_ghost_running():
|
|
"""Ensure Ghost container is running before tests."""
|
|
import subprocess
|
|
|
|
try:
|
|
# Check if Ghost is accessible
|
|
response = httpx.get("http://localhost:2368", timeout=5)
|
|
if response.status_code != 200:
|
|
raise Exception("Ghost not accessible")
|
|
except Exception:
|
|
# Try to start Ghost containers
|
|
result = subprocess.run(
|
|
["docker", "compose", "ps", "-q", "ghost"],
|
|
capture_output=True,
|
|
text=True,
|
|
cwd="/var/home/luiz/projects/thenets/ghost-mcp"
|
|
)
|
|
|
|
if not result.stdout.strip():
|
|
pytest.skip("Ghost container not running. Run 'make start-ghost' first.")
|
|
|
|
# Verify environment configuration
|
|
if not os.getenv("GHOST_CONTENT_API_KEY") or not os.getenv("GHOST_ADMIN_API_KEY"):
|
|
pytest.skip("Ghost API keys not configured. Run 'make setup-tokens' first.")
|
|
|
|
|
|
@pytest.fixture
|
|
async def ghost_client() -> AsyncGenerator[GhostClient, None]:
|
|
"""Provide a Ghost client for tests."""
|
|
async with GhostClient() as client:
|
|
yield client
|
|
|
|
|
|
@pytest.fixture
|
|
def mcp_server():
|
|
"""Provide the MCP server instance."""
|
|
return mcp
|
|
|
|
|
|
@pytest.fixture
|
|
async def test_post_data() -> Dict[str, Any]:
|
|
"""Provide test data for creating posts."""
|
|
unique_id = str(uuid.uuid4())[:8]
|
|
return {
|
|
"title": f"Test Post {unique_id}",
|
|
"content": json.dumps({
|
|
"root": {
|
|
"children": [
|
|
{
|
|
"children": [
|
|
{
|
|
"detail": 0,
|
|
"format": 0,
|
|
"mode": "normal",
|
|
"style": "",
|
|
"text": f"This is a test post content for e2e testing. ID: {unique_id}",
|
|
"type": "text",
|
|
"version": 1
|
|
}
|
|
],
|
|
"direction": "ltr",
|
|
"format": "",
|
|
"indent": 0,
|
|
"type": "paragraph",
|
|
"version": 1
|
|
}
|
|
],
|
|
"direction": "ltr",
|
|
"format": "",
|
|
"indent": 0,
|
|
"type": "root",
|
|
"version": 1
|
|
}
|
|
}),
|
|
"content_format": "lexical",
|
|
"status": "draft"
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
async def test_page_data() -> Dict[str, Any]:
|
|
"""Provide test data for creating pages."""
|
|
unique_id = str(uuid.uuid4())[:8]
|
|
return {
|
|
"title": f"Test Page {unique_id}",
|
|
"content": json.dumps({
|
|
"root": {
|
|
"children": [
|
|
{
|
|
"children": [
|
|
{
|
|
"detail": 0,
|
|
"format": 0,
|
|
"mode": "normal",
|
|
"style": "",
|
|
"text": f"This is a test page content for e2e testing. ID: {unique_id}",
|
|
"type": "text",
|
|
"version": 1
|
|
}
|
|
],
|
|
"direction": "ltr",
|
|
"format": "",
|
|
"indent": 0,
|
|
"type": "paragraph",
|
|
"version": 1
|
|
}
|
|
],
|
|
"direction": "ltr",
|
|
"format": "",
|
|
"indent": 0,
|
|
"type": "root",
|
|
"version": 1
|
|
}
|
|
}),
|
|
"content_format": "lexical",
|
|
"status": "draft"
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
async def test_tag_data() -> Dict[str, str]:
|
|
"""Provide test data for creating tags."""
|
|
unique_id = str(uuid.uuid4())[:8]
|
|
return {
|
|
"name": f"test-tag-{unique_id}",
|
|
"description": f"Test tag for e2e testing {unique_id}"
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
async def cleanup_test_content(ghost_client: GhostClient):
|
|
"""Clean up test content after each test."""
|
|
created_posts = []
|
|
created_pages = []
|
|
created_tags = []
|
|
|
|
def track_post(post_id: str):
|
|
created_posts.append(post_id)
|
|
|
|
def track_page(page_id: str):
|
|
created_pages.append(page_id)
|
|
|
|
def track_tag(tag_id: str):
|
|
created_tags.append(tag_id)
|
|
|
|
# Provide tracking functions
|
|
yield {
|
|
"track_post": track_post,
|
|
"track_page": track_page,
|
|
"track_tag": track_tag
|
|
}
|
|
|
|
# Cleanup after test
|
|
for post_id in created_posts:
|
|
try:
|
|
await ghost_client._make_request("DELETE", f"posts/{post_id}/", api_type="admin")
|
|
except Exception:
|
|
pass # Ignore cleanup errors
|
|
|
|
for page_id in created_pages:
|
|
try:
|
|
await ghost_client._make_request("DELETE", f"pages/{page_id}/", api_type="admin")
|
|
except Exception:
|
|
pass # Ignore cleanup errors
|
|
|
|
for tag_id in created_tags:
|
|
try:
|
|
await ghost_client._make_request("DELETE", f"tags/{tag_id}/", api_type="admin")
|
|
except Exception:
|
|
pass # Ignore cleanup errors
|
|
|
|
|
|
@pytest.fixture
|
|
async def sample_post(ghost_client: GhostClient, test_post_data: Dict[str, Any], cleanup_test_content):
|
|
"""Create a sample post for testing."""
|
|
# Create a post
|
|
response = await ghost_client._make_request(
|
|
"POST",
|
|
"posts/",
|
|
api_type="admin",
|
|
json_data={"posts": [test_post_data]}
|
|
)
|
|
|
|
post_data = response["posts"][0]
|
|
cleanup_test_content["track_post"](post_data["id"])
|
|
|
|
return post_data
|
|
|
|
|
|
@pytest.fixture
|
|
async def sample_published_post(ghost_client: GhostClient, test_post_data: Dict[str, Any], cleanup_test_content):
|
|
"""Create a sample published post for testing."""
|
|
# Modify data for published post
|
|
test_post_data["status"] = "published"
|
|
|
|
# Create a published post
|
|
response = await ghost_client._make_request(
|
|
"POST",
|
|
"posts/",
|
|
api_type="admin",
|
|
json_data={"posts": [test_post_data]}
|
|
)
|
|
|
|
post_data = response["posts"][0]
|
|
cleanup_test_content["track_post"](post_data["id"])
|
|
|
|
return post_data
|
|
|
|
|
|
@pytest.fixture
|
|
async def sample_page(ghost_client: GhostClient, test_page_data: Dict[str, Any], cleanup_test_content):
|
|
"""Create a sample page for testing."""
|
|
# Create a page
|
|
response = await ghost_client._make_request(
|
|
"POST",
|
|
"pages/",
|
|
api_type="admin",
|
|
json_data={"pages": [test_page_data]}
|
|
)
|
|
|
|
page_data = response["pages"][0]
|
|
cleanup_test_content["track_page"](page_data["id"])
|
|
|
|
return page_data
|
|
|
|
|
|
@pytest.fixture
|
|
async def sample_tag(ghost_client: GhostClient, test_tag_data: Dict[str, str], cleanup_test_content):
|
|
"""Create a sample tag for testing."""
|
|
# Create a tag
|
|
response = await ghost_client._make_request(
|
|
"POST",
|
|
"tags/",
|
|
api_type="admin",
|
|
json_data={"tags": [test_tag_data]}
|
|
)
|
|
|
|
tag_data = response["tags"][0]
|
|
cleanup_test_content["track_tag"](tag_data["id"])
|
|
|
|
return tag_data
|
|
|
|
|
|
@pytest.mark.e2e
|
|
class BaseE2ETest:
|
|
"""Base class for e2e tests."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup_test(self, ensure_ghost_running):
|
|
"""Auto-use the Ghost running check."""
|
|
pass
|
|
|
|
def get_mcp_tool(self, mcp_server, tool_name: str):
|
|
"""Get an MCP tool by name from the server."""
|
|
tools = {tool.name: tool for tool in mcp_server.tools}
|
|
if tool_name not in tools:
|
|
available_tools = list(tools.keys())
|
|
raise ValueError(f"Tool '{tool_name}' not found. Available tools: {available_tools}")
|
|
return tools[tool_name]
|
|
|
|
async def call_mcp_tool(self, mcp_server, tool_name: str, **kwargs):
|
|
"""Call an MCP tool with the given arguments."""
|
|
tool = self.get_mcp_tool(mcp_server, tool_name)
|
|
return await tool.func(**kwargs) |