416 lines
No EOL
14 KiB
Python
416 lines
No EOL
14 KiB
Python
"""End-to-end validation tests for Ghost pages functionality."""
|
|
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from .conftest import BaseE2ETest
|
|
|
|
|
|
@pytest.mark.e2e
|
|
@pytest.mark.admin
|
|
class TestPageValidationE2E(BaseE2ETest):
|
|
"""Test page validation functionality end-to-end."""
|
|
|
|
async def test_validate_title_required(self, mcp_server):
|
|
"""Test that page title is required."""
|
|
result = await self.call_mcp_tool(mcp_server, "create_page", title="")
|
|
response = json.loads(result)
|
|
|
|
assert "error" in response
|
|
assert "title" in response["error"].lower()
|
|
assert "required" in response["error"].lower() or "empty" in response["error"].lower()
|
|
|
|
async def test_validate_title_too_long(self, mcp_server):
|
|
"""Test that page title length is validated."""
|
|
long_title = "A" * 256 # Exceeds 255 character limit
|
|
|
|
result = await self.call_mcp_tool(mcp_server, "create_page", title=long_title)
|
|
response = json.loads(result)
|
|
|
|
assert "error" in response
|
|
assert "too long" in response["error"].lower()
|
|
assert "255" in response["error"]
|
|
|
|
async def test_validate_invalid_status(self, mcp_server):
|
|
"""Test validation of invalid page status."""
|
|
result = await self.call_mcp_tool(
|
|
mcp_server, "create_page",
|
|
title="Test Page",
|
|
status="invalid_status"
|
|
)
|
|
response = json.loads(result)
|
|
|
|
assert "error" in response
|
|
assert "status" in response["error"].lower()
|
|
assert "draft" in response["context"] or "published" in response["context"]
|
|
|
|
async def test_validate_scheduled_without_date(self, mcp_server):
|
|
"""Test that scheduled pages require published_at date."""
|
|
result = await self.call_mcp_tool(
|
|
mcp_server, "create_page",
|
|
title="Test Scheduled Page",
|
|
status="scheduled"
|
|
)
|
|
response = json.loads(result)
|
|
|
|
assert "error" in response
|
|
assert "scheduled" in response["error"].lower()
|
|
assert "published_at" in response["error"].lower()
|
|
|
|
async def test_validate_invalid_published_at(self, mcp_server):
|
|
"""Test validation of invalid published_at format."""
|
|
result = await self.call_mcp_tool(
|
|
mcp_server, "create_page",
|
|
title="Test Scheduled Page",
|
|
status="scheduled",
|
|
published_at="invalid-date-format"
|
|
)
|
|
response = json.loads(result)
|
|
|
|
assert "error" in response
|
|
assert "datetime" in response["error"].lower() or "format" in response["error"].lower()
|
|
|
|
async def test_validate_invalid_content_format(self, mcp_server):
|
|
"""Test validation of invalid content format."""
|
|
result = await self.call_mcp_tool(
|
|
mcp_server, "create_page",
|
|
title="Test Page",
|
|
content="Some content",
|
|
content_format="invalid_format"
|
|
)
|
|
response = json.loads(result)
|
|
|
|
assert "error" in response
|
|
assert "content format" in response["error"].lower()
|
|
assert "lexical" in response["context"] or "html" in response["context"]
|
|
|
|
async def test_validate_invalid_lexical_json(self, mcp_server):
|
|
"""Test validation of malformed Lexical JSON."""
|
|
invalid_lexical = "{invalid json"
|
|
|
|
result = await self.call_mcp_tool(
|
|
mcp_server, "create_page",
|
|
title="Test Page",
|
|
content=invalid_lexical,
|
|
content_format="lexical"
|
|
)
|
|
response = json.loads(result)
|
|
|
|
assert "error" in response
|
|
assert "json" in response["error"].lower()
|
|
|
|
async def test_validate_lexical_missing_root(self, mcp_server):
|
|
"""Test validation of Lexical content missing root."""
|
|
invalid_lexical = json.dumps({"no_root": {}})
|
|
|
|
result = await self.call_mcp_tool(
|
|
mcp_server, "create_page",
|
|
title="Test Page",
|
|
content=invalid_lexical,
|
|
content_format="lexical"
|
|
)
|
|
response = json.loads(result)
|
|
|
|
assert "error" in response
|
|
assert "root" in response["error"].lower()
|
|
|
|
async def test_validate_lexical_missing_required_props(self, mcp_server):
|
|
"""Test validation of Lexical content missing required properties."""
|
|
invalid_lexical = json.dumps({
|
|
"root": {
|
|
"children": [] # Missing other required properties
|
|
}
|
|
})
|
|
|
|
result = await self.call_mcp_tool(
|
|
mcp_server, "create_page",
|
|
title="Test Page",
|
|
content=invalid_lexical,
|
|
content_format="lexical"
|
|
)
|
|
response = json.loads(result)
|
|
|
|
assert "error" in response
|
|
assert "missing" in response["error"].lower()
|
|
|
|
async def test_validate_lexical_invalid_node_type(self, mcp_server):
|
|
"""Test validation of Lexical content with invalid node type."""
|
|
invalid_lexical = json.dumps({
|
|
"root": {
|
|
"children": [
|
|
{
|
|
"type": "invalid_node_type",
|
|
"version": 1
|
|
}
|
|
],
|
|
"direction": "ltr",
|
|
"format": "",
|
|
"indent": 0,
|
|
"type": "root",
|
|
"version": 1
|
|
}
|
|
})
|
|
|
|
result = await self.call_mcp_tool(
|
|
mcp_server, "create_page",
|
|
title="Test Page",
|
|
content=invalid_lexical,
|
|
content_format="lexical"
|
|
)
|
|
response = json.loads(result)
|
|
|
|
assert "error" in response
|
|
assert "node type" in response["error"].lower()
|
|
|
|
async def test_validate_lexical_heading_without_tag(self, mcp_server):
|
|
"""Test validation of Lexical heading node without tag property."""
|
|
invalid_lexical = json.dumps({
|
|
"root": {
|
|
"children": [
|
|
{
|
|
"type": "heading",
|
|
"version": 1,
|
|
"children": []
|
|
# Missing "tag" property
|
|
}
|
|
],
|
|
"direction": "ltr",
|
|
"format": "",
|
|
"indent": 0,
|
|
"type": "root",
|
|
"version": 1
|
|
}
|
|
})
|
|
|
|
result = await self.call_mcp_tool(
|
|
mcp_server, "create_page",
|
|
title="Test Page",
|
|
content=invalid_lexical,
|
|
content_format="lexical"
|
|
)
|
|
response = json.loads(result)
|
|
|
|
assert "error" in response
|
|
assert "heading" in response["error"].lower()
|
|
assert "tag" in response["error"].lower()
|
|
|
|
async def test_validate_lexical_link_without_url(self, mcp_server):
|
|
"""Test validation of Lexical link node without URL property."""
|
|
invalid_lexical = json.dumps({
|
|
"root": {
|
|
"children": [
|
|
{
|
|
"children": [
|
|
{
|
|
"type": "link",
|
|
"version": 1,
|
|
"text": "Link text"
|
|
# Missing "url" property
|
|
}
|
|
],
|
|
"type": "paragraph",
|
|
"version": 1
|
|
}
|
|
],
|
|
"direction": "ltr",
|
|
"format": "",
|
|
"indent": 0,
|
|
"type": "root",
|
|
"version": 1
|
|
}
|
|
})
|
|
|
|
result = await self.call_mcp_tool(
|
|
mcp_server, "create_page",
|
|
title="Test Page",
|
|
content=invalid_lexical,
|
|
content_format="lexical"
|
|
)
|
|
response = json.loads(result)
|
|
|
|
assert "error" in response
|
|
assert "link" in response["error"].lower()
|
|
assert "url" in response["error"].lower()
|
|
|
|
async def test_validate_html_malformed(self, mcp_server):
|
|
"""Test validation of malformed HTML content."""
|
|
malformed_html = "<div><p>Unclosed div and paragraph tags"
|
|
|
|
result = await self.call_mcp_tool(
|
|
mcp_server, "create_page",
|
|
title="Test Page",
|
|
content=malformed_html,
|
|
content_format="html"
|
|
)
|
|
response = json.loads(result)
|
|
|
|
assert "error" in response
|
|
assert "html" in response["error"].lower()
|
|
assert "unclosed" in response["error"].lower() or "validation" in response["error"].lower()
|
|
|
|
async def test_validate_html_invalid_tags(self, mcp_server):
|
|
"""Test validation of HTML with invalid tags."""
|
|
invalid_html = "<script>alert('xss')</script><p>Valid paragraph</p>"
|
|
|
|
result = await self.call_mcp_tool(
|
|
mcp_server, "create_page",
|
|
title="Test Page",
|
|
content=invalid_html,
|
|
content_format="html"
|
|
)
|
|
response = json.loads(result)
|
|
|
|
assert "error" in response
|
|
assert "html" in response["error"].lower()
|
|
assert "invalid" in response["error"].lower() or "tag" in response["error"].lower()
|
|
|
|
async def test_validate_meta_title_too_long(self, mcp_server):
|
|
"""Test validation of meta title length."""
|
|
long_meta_title = "A" * 301 # Exceeds 300 character limit
|
|
|
|
result = await self.call_mcp_tool(
|
|
mcp_server, "create_page",
|
|
title="Test Page",
|
|
meta_title=long_meta_title
|
|
)
|
|
response = json.loads(result)
|
|
|
|
assert "error" in response
|
|
assert "meta title" in response["error"].lower()
|
|
assert "300" in response["error"]
|
|
|
|
async def test_validate_meta_description_too_long(self, mcp_server):
|
|
"""Test validation of meta description length."""
|
|
long_meta_description = "A" * 501 # Exceeds 500 character limit
|
|
|
|
result = await self.call_mcp_tool(
|
|
mcp_server, "create_page",
|
|
title="Test Page",
|
|
meta_description=long_meta_description
|
|
)
|
|
response = json.loads(result)
|
|
|
|
assert "error" in response
|
|
assert "meta description" in response["error"].lower()
|
|
assert "500" in response["error"]
|
|
|
|
async def test_validate_tag_name_too_long(self, mcp_server):
|
|
"""Test validation of tag name length."""
|
|
long_tag = "A" * 192 # Exceeds 191 character limit
|
|
|
|
result = await self.call_mcp_tool(
|
|
mcp_server, "create_page",
|
|
title="Test Page",
|
|
tags=f"valid_tag,{long_tag}"
|
|
)
|
|
response = json.loads(result)
|
|
|
|
assert "error" in response
|
|
assert "tag name" in response["error"].lower()
|
|
assert "191" in response["error"]
|
|
|
|
async def test_validate_successful_creation_with_valid_data(self, mcp_server, cleanup_test_content):
|
|
"""Test that validation passes with all valid data."""
|
|
valid_lexical = json.dumps({
|
|
"root": {
|
|
"children": [
|
|
{
|
|
"children": [
|
|
{
|
|
"detail": 0,
|
|
"format": 0,
|
|
"mode": "normal",
|
|
"style": "",
|
|
"text": "Valid page content",
|
|
"type": "text",
|
|
"version": 1
|
|
}
|
|
],
|
|
"direction": "ltr",
|
|
"format": "",
|
|
"indent": 0,
|
|
"type": "paragraph",
|
|
"version": 1
|
|
}
|
|
],
|
|
"direction": "ltr",
|
|
"format": "",
|
|
"indent": 0,
|
|
"type": "root",
|
|
"version": 1
|
|
}
|
|
})
|
|
|
|
result = await self.call_mcp_tool(
|
|
mcp_server, "create_page",
|
|
title="Valid Test Page",
|
|
content=valid_lexical,
|
|
content_format="lexical",
|
|
status="draft",
|
|
excerpt="A test page excerpt",
|
|
featured=True,
|
|
tags="test,validation",
|
|
meta_title="SEO Page Title",
|
|
meta_description="SEO page description"
|
|
)
|
|
response = json.loads(result)
|
|
|
|
# Should not contain error
|
|
assert "error" not in response
|
|
assert "pages" in response
|
|
page = response["pages"][0]
|
|
assert page["title"] == "Valid Test Page"
|
|
assert page["status"] == "draft"
|
|
|
|
# Track for cleanup
|
|
cleanup_test_content["track_page"](page["id"])
|
|
|
|
async def test_validate_update_page_validation(self, mcp_server, cleanup_test_content):
|
|
"""Test validation during page updates."""
|
|
# First create a valid page
|
|
result = await self.call_mcp_tool(
|
|
mcp_server, "create_page",
|
|
title="Original Page",
|
|
status="draft"
|
|
)
|
|
response = json.loads(result)
|
|
page_id = response["pages"][0]["id"]
|
|
|
|
# Track for cleanup
|
|
cleanup_test_content["track_page"](page_id)
|
|
|
|
# Try to update with invalid data
|
|
update_result = await self.call_mcp_tool(
|
|
mcp_server, "update_page",
|
|
page_id=page_id,
|
|
title="A" * 256, # Too long
|
|
)
|
|
update_response = json.loads(update_result)
|
|
|
|
assert "error" in update_response
|
|
assert "too long" in update_response["error"].lower()
|
|
|
|
# Try valid update
|
|
valid_update_result = await self.call_mcp_tool(
|
|
mcp_server, "update_page",
|
|
page_id=page_id,
|
|
title="Updated Valid Page"
|
|
)
|
|
valid_update_response = json.loads(valid_update_result)
|
|
|
|
assert "error" not in valid_update_response
|
|
assert valid_update_response["pages"][0]["title"] == "Updated Valid Page"
|
|
|
|
async def test_validate_error_response_structure(self, mcp_server):
|
|
"""Test that validation errors have consistent response structure."""
|
|
result = await self.call_mcp_tool(mcp_server, "create_page", title="")
|
|
response = json.loads(result)
|
|
|
|
# All validation errors should have error and context fields
|
|
assert "error" in response
|
|
assert "context" in response
|
|
assert isinstance(response["error"], str)
|
|
assert isinstance(response["context"], str)
|
|
assert len(response["error"]) > 0
|
|
assert len(response["context"]) > 0 |