292 lines
No EOL
11 KiB
Python
292 lines
No EOL
11 KiB
Python
"""End-to-end tests for Ghost settings functionality."""
|
|
|
|
import json
|
|
import pytest
|
|
|
|
from .conftest import BaseE2ETest
|
|
|
|
|
|
@pytest.mark.e2e
|
|
class TestSettingsContentAPIE2E(BaseE2ETest):
|
|
"""Test settings Content API functionality end-to-end."""
|
|
|
|
async def test_get_settings(self, mcp_server):
|
|
"""Test getting public settings."""
|
|
from ghost_mcp.tools.content.settings import get_settings
|
|
|
|
# Get settings
|
|
result = await get_settings()
|
|
response = json.loads(result)
|
|
|
|
# Verify response structure
|
|
assert "settings" in response
|
|
assert isinstance(response["settings"], list)
|
|
|
|
# Should have multiple settings
|
|
assert len(response["settings"]) > 0
|
|
|
|
# Verify settings structure
|
|
setting = response["settings"][0]
|
|
essential_fields = ["key", "value"]
|
|
for field in essential_fields:
|
|
assert field in setting
|
|
|
|
async def test_get_settings_essential_keys(self, mcp_server):
|
|
"""Test that essential settings keys are present."""
|
|
from ghost_mcp.tools.content.settings import get_settings
|
|
|
|
# Get settings
|
|
result = await get_settings()
|
|
response = json.loads(result)
|
|
|
|
# Extract all setting keys
|
|
setting_keys = [setting["key"] for setting in response["settings"]]
|
|
|
|
# Essential settings that should be present
|
|
essential_keys = [
|
|
"title",
|
|
"description",
|
|
"url",
|
|
"timezone",
|
|
"locale"
|
|
]
|
|
|
|
# Verify essential keys are present
|
|
for key in essential_keys:
|
|
assert key in setting_keys, f"Essential setting '{key}' not found"
|
|
|
|
async def test_get_settings_data_types(self, mcp_server):
|
|
"""Test that settings have correct data types."""
|
|
from ghost_mcp.tools.content.settings import get_settings
|
|
|
|
# Get settings
|
|
result = await get_settings()
|
|
response = json.loads(result)
|
|
|
|
# Check data types for each setting
|
|
for setting in response["settings"]:
|
|
assert isinstance(setting["key"], str), f"Setting key should be string: {setting}"
|
|
# Value can be string, bool, or null
|
|
assert setting["value"] is None or isinstance(setting["value"], (str, bool, int))
|
|
|
|
async def test_get_settings_site_title(self, mcp_server):
|
|
"""Test that site title setting is accessible."""
|
|
from ghost_mcp.tools.content.settings import get_settings
|
|
|
|
# Get settings
|
|
result = await get_settings()
|
|
response = json.loads(result)
|
|
|
|
# Find title setting
|
|
title_settings = [s for s in response["settings"] if s["key"] == "title"]
|
|
assert len(title_settings) == 1, "Should have exactly one title setting"
|
|
|
|
title_setting = title_settings[0]
|
|
assert isinstance(title_setting["value"], str)
|
|
assert len(title_setting["value"]) > 0, "Site title should not be empty"
|
|
|
|
async def test_get_settings_site_url(self, mcp_server):
|
|
"""Test that site URL setting is accessible and valid."""
|
|
from ghost_mcp.tools.content.settings import get_settings
|
|
|
|
# Get settings
|
|
result = await get_settings()
|
|
response = json.loads(result)
|
|
|
|
# Find url setting
|
|
url_settings = [s for s in response["settings"] if s["key"] == "url"]
|
|
assert len(url_settings) == 1, "Should have exactly one url setting"
|
|
|
|
url_setting = url_settings[0]
|
|
assert isinstance(url_setting["value"], str)
|
|
assert url_setting["value"].startswith("http"), "Site URL should start with http"
|
|
assert "localhost:2368" in url_setting["value"], "Should be localhost test instance"
|
|
|
|
async def test_get_site_info(self, mcp_server):
|
|
"""Test getting basic site information."""
|
|
from ghost_mcp.tools.content.settings import get_site_info
|
|
|
|
# Get site info
|
|
result = await get_site_info()
|
|
response = json.loads(result)
|
|
|
|
# Verify response structure
|
|
assert "site" in response
|
|
site = response["site"]
|
|
|
|
# Verify essential site info fields
|
|
essential_fields = ["title", "url", "version"]
|
|
for field in essential_fields:
|
|
assert field in site, f"Site info should include '{field}'"
|
|
|
|
async def test_get_site_info_title_matches_settings(self, mcp_server):
|
|
"""Test that site info title matches settings title."""
|
|
from ghost_mcp.tools.content.settings import get_site_info, get_settings
|
|
|
|
# Get both site info and settings
|
|
site_info_result = await get_site_info()
|
|
settings_result = await get_settings()
|
|
|
|
site_info_response = json.loads(site_info_result)
|
|
settings_response = json.loads(settings_result)
|
|
|
|
# Extract titles
|
|
site_title = site_info_response["site"]["title"]
|
|
|
|
title_settings = [s for s in settings_response["settings"] if s["key"] == "title"]
|
|
settings_title = title_settings[0]["value"]
|
|
|
|
# Titles should match
|
|
assert site_title == settings_title, "Site info title should match settings title"
|
|
|
|
async def test_get_site_info_url_matches_settings(self, mcp_server):
|
|
"""Test that site info URL matches settings URL."""
|
|
from ghost_mcp.tools.content.settings import get_site_info, get_settings
|
|
|
|
# Get both site info and settings
|
|
site_info_result = await get_site_info()
|
|
settings_result = await get_settings()
|
|
|
|
site_info_response = json.loads(site_info_result)
|
|
settings_response = json.loads(settings_result)
|
|
|
|
# Extract URLs
|
|
site_url = site_info_response["site"]["url"]
|
|
|
|
url_settings = [s for s in settings_response["settings"] if s["key"] == "url"]
|
|
settings_url = url_settings[0]["value"]
|
|
|
|
# URLs should match
|
|
assert site_url == settings_url, "Site info URL should match settings URL"
|
|
|
|
async def test_get_site_info_version_format(self, mcp_server):
|
|
"""Test that site info includes valid Ghost version."""
|
|
from ghost_mcp.tools.content.settings import get_site_info
|
|
|
|
# Get site info
|
|
result = await get_site_info()
|
|
response = json.loads(result)
|
|
|
|
site = response["site"]
|
|
version = site["version"]
|
|
|
|
# Version should be a non-empty string
|
|
assert isinstance(version, str)
|
|
assert len(version) > 0
|
|
|
|
# Should contain a dot (version format like 5.x.x)
|
|
assert "." in version, "Version should be in x.y.z format"
|
|
|
|
async def test_settings_no_sensitive_data(self, mcp_server):
|
|
"""Test that settings don't expose sensitive information."""
|
|
from ghost_mcp.tools.content.settings import get_settings
|
|
|
|
# Get settings
|
|
result = await get_settings()
|
|
response = json.loads(result)
|
|
|
|
# Extract all setting keys
|
|
setting_keys = [setting["key"] for setting in response["settings"]]
|
|
|
|
# Keys that should NOT be present in public settings
|
|
sensitive_keys = [
|
|
"db_password",
|
|
"mailgun_api_key",
|
|
"admin_api_key",
|
|
"content_api_key",
|
|
"smtp_password",
|
|
"oauth_client_secret"
|
|
]
|
|
|
|
# Verify sensitive keys are not exposed
|
|
for sensitive_key in sensitive_keys:
|
|
assert sensitive_key not in setting_keys, f"Sensitive key '{sensitive_key}' should not be exposed"
|
|
|
|
async def test_settings_readonly_access(self, mcp_server):
|
|
"""Test that Content API only provides read access to settings."""
|
|
from ghost_mcp.tools.content.settings import get_settings
|
|
|
|
# This test verifies that we can read settings but not modify them
|
|
# through the Content API (which is read-only)
|
|
|
|
# Get settings should work
|
|
result = await get_settings()
|
|
response = json.loads(result)
|
|
|
|
# Should return valid settings
|
|
assert "settings" in response
|
|
assert len(response["settings"]) > 0
|
|
|
|
# Note: Write operations would be through Admin API, which requires
|
|
# separate authentication and is not typically exposed through MCP tools
|
|
|
|
async def test_get_settings_pagination_metadata(self, mcp_server):
|
|
"""Test that settings include proper metadata structure."""
|
|
from ghost_mcp.tools.content.settings import get_settings
|
|
|
|
# Get settings
|
|
result = await get_settings()
|
|
response = json.loads(result)
|
|
|
|
# Should have settings array
|
|
assert "settings" in response
|
|
|
|
# May or may not have meta depending on Ghost version,
|
|
# but if present should be properly structured
|
|
if "meta" in response:
|
|
meta = response["meta"]
|
|
assert isinstance(meta, dict)
|
|
|
|
async def test_settings_timezone_format(self, mcp_server):
|
|
"""Test that timezone setting is in valid format."""
|
|
from ghost_mcp.tools.content.settings import get_settings
|
|
|
|
# Get settings
|
|
result = await get_settings()
|
|
response = json.loads(result)
|
|
|
|
# Find timezone setting
|
|
timezone_settings = [s for s in response["settings"] if s["key"] == "timezone"]
|
|
|
|
if timezone_settings: # timezone might not always be present
|
|
timezone_setting = timezone_settings[0]
|
|
timezone_value = timezone_setting["value"]
|
|
|
|
# Should be a string
|
|
assert isinstance(timezone_value, str)
|
|
|
|
# Common timezone formats
|
|
valid_formats = [
|
|
timezone_value.startswith("Etc/"),
|
|
timezone_value.startswith("America/"),
|
|
timezone_value.startswith("Europe/"),
|
|
timezone_value.startswith("Asia/"),
|
|
timezone_value == "UTC",
|
|
"/" in timezone_value # General timezone format
|
|
]
|
|
|
|
assert any(valid_formats), f"Invalid timezone format: {timezone_value}"
|
|
|
|
async def test_settings_locale_format(self, mcp_server):
|
|
"""Test that locale setting is in valid format."""
|
|
from ghost_mcp.tools.content.settings import get_settings
|
|
|
|
# Get settings
|
|
result = await get_settings()
|
|
response = json.loads(result)
|
|
|
|
# Find locale setting
|
|
locale_settings = [s for s in response["settings"] if s["key"] == "locale"]
|
|
|
|
if locale_settings: # locale might not always be present
|
|
locale_setting = locale_settings[0]
|
|
locale_value = locale_setting["value"]
|
|
|
|
# Should be a string
|
|
assert isinstance(locale_value, str)
|
|
assert len(locale_value) >= 2, "Locale should be at least 2 characters"
|
|
|
|
# Common locale formats (en, en-US, etc.)
|
|
# Should contain only letters, hyphens, and underscores
|
|
import re
|
|
assert re.match(r'^[a-zA-Z_-]+$', locale_value), f"Invalid locale format: {locale_value}" |