# chuk-mcp-remotion/tests/test_token_tools.py
"""
Tests for token MCP tools.
"""
import json
import pytest
from chuk_mcp_remotion.tools.token_tools import register_token_tools
@pytest.mark.asyncio
class TestTokenTools:
"""Test token MCP tools."""
@pytest.fixture
async def mcp_with_token_tools(self, mock_mcp_server, project_manager, vfs):
"""Register token tools and return MCP server."""
register_token_tools(mock_mcp_server, project_manager, vfs)
return mock_mcp_server
async def test_tools_registered(self, mcp_with_token_tools):
"""Test that all token tools are registered."""
tools = mcp_with_token_tools.tools
expected_tools = [
"remotion_list_color_tokens",
"remotion_get_theme_colors",
"remotion_get_color_value",
"remotion_list_typography_tokens",
"remotion_get_font_families",
"remotion_get_font_sizes",
"remotion_get_text_style",
"remotion_list_motion_tokens",
"remotion_get_spring_configs",
"remotion_get_spring_config",
"remotion_get_easing_curves",
"remotion_get_easing_curve",
"remotion_get_durations",
"remotion_get_duration",
"remotion_get_animation_presets",
"remotion_get_animation_preset",
"remotion_get_youtube_optimizations",
# Import/Export tools
"remotion_export_typography_tokens",
"remotion_import_typography_tokens",
"remotion_export_color_tokens",
"remotion_import_color_tokens",
"remotion_export_motion_tokens",
"remotion_import_motion_tokens",
"remotion_export_all_tokens",
]
for tool in expected_tools:
assert tool in tools, f"Tool {tool} not registered"
class TestColorTokenTools:
"""Test color token tools."""
@pytest.fixture
async def mcp_with_token_tools(self, mock_mcp_server, project_manager, vfs):
"""Register token tools and return MCP server."""
register_token_tools(mock_mcp_server, project_manager, vfs)
return mock_mcp_server
@pytest.mark.asyncio
async def test_list_color_tokens(self, mcp_with_token_tools):
"""Test listing all color tokens."""
tool = mcp_with_token_tools.tools["remotion_list_color_tokens"]
result = await tool()
data = json.loads(result)
assert "tech" in data
assert "finance" in data
assert isinstance(data, dict)
@pytest.mark.asyncio
async def test_get_theme_colors(self, mcp_with_token_tools):
"""Test getting colors for specific theme."""
tool = mcp_with_token_tools.tools["remotion_get_theme_colors"]
result = await tool(theme_name="tech")
data = json.loads(result)
assert data["theme"] == "tech"
assert "colors" in data
assert "primary" in data["colors"]
assert "accent" in data["colors"]
@pytest.mark.asyncio
async def test_get_theme_colors_invalid(self, mcp_with_token_tools):
"""Test getting colors for invalid theme."""
tool = mcp_with_token_tools.tools["remotion_get_theme_colors"]
result = await tool(theme_name="nonexistent")
data = json.loads(result)
assert "error" in data
assert "available_themes" in data
@pytest.mark.asyncio
async def test_get_color_value_array(self, mcp_with_token_tools):
"""Test getting color value from array."""
tool = mcp_with_token_tools.tools["remotion_get_color_value"]
result = await tool(theme_name="tech", color_type="primary", index=0)
data = json.loads(result)
assert "value" in data
assert data["value"].startswith("#")
@pytest.mark.asyncio
async def test_get_color_value_no_index(self, mcp_with_token_tools):
"""Test getting color value without index."""
tool = mcp_with_token_tools.tools["remotion_get_color_value"]
result = await tool(theme_name="tech", color_type="primary")
data = json.loads(result)
assert "values" in data
assert isinstance(data["values"], list)
@pytest.mark.asyncio
async def test_get_color_value_dict(self, mcp_with_token_tools):
"""Test getting color value from dict."""
tool = mcp_with_token_tools.tools["remotion_get_color_value"]
result = await tool(theme_name="tech", color_type="background")
data = json.loads(result)
assert "values" in data
assert "dark" in data["values"]
@pytest.mark.asyncio
async def test_get_color_value_invalid_index(self, mcp_with_token_tools):
"""Test getting color with invalid index."""
tool = mcp_with_token_tools.tools["remotion_get_color_value"]
result = await tool(theme_name="tech", color_type="primary", index=99)
data = json.loads(result)
assert "error" in data
@pytest.mark.asyncio
async def test_get_color_value_invalid_theme(self, mcp_with_token_tools):
"""Test getting color value with invalid theme."""
tool = mcp_with_token_tools.tools["remotion_get_color_value"]
result = await tool(theme_name="invalid_theme", color_type="primary")
data = json.loads(result)
assert "error" in data
@pytest.mark.asyncio
async def test_get_color_value_invalid_color_type(self, mcp_with_token_tools):
"""Test getting color value with invalid color type."""
tool = mcp_with_token_tools.tools["remotion_get_color_value"]
result = await tool(theme_name="tech", color_type="invalid_color")
data = json.loads(result)
assert "error" in data
@pytest.mark.asyncio
async def test_get_color_value_string(self, mcp_with_token_tools):
"""Test getting string color value (gradient)."""
tool = mcp_with_token_tools.tools["remotion_get_color_value"]
result = await tool(theme_name="tech", color_type="gradient")
data = json.loads(result)
assert "value" in data
assert isinstance(data["value"], str)
class TestTypographyTokenTools:
"""Test typography token tools."""
@pytest.fixture
async def mcp_with_token_tools(self, mock_mcp_server, project_manager, vfs):
"""Register token tools and return MCP server."""
register_token_tools(mock_mcp_server, project_manager, vfs)
return mock_mcp_server
@pytest.mark.asyncio
async def test_list_typography_tokens(self, mcp_with_token_tools):
"""Test listing all typography tokens."""
tool = mcp_with_token_tools.tools["remotion_list_typography_tokens"]
result = await tool()
data = json.loads(result)
assert "font_families" in data
assert "font_sizes" in data
assert "font_weights" in data
@pytest.mark.asyncio
async def test_get_font_families(self, mcp_with_token_tools):
"""Test getting font families."""
tool = mcp_with_token_tools.tools["remotion_get_font_families"]
result = await tool()
data = json.loads(result)
assert "font_families" in data
assert "display" in data["font_families"]
assert "body" in data["font_families"]
@pytest.mark.asyncio
async def test_get_font_sizes_1080p(self, mcp_with_token_tools):
"""Test getting font sizes for 1080p."""
tool = mcp_with_token_tools.tools["remotion_get_font_sizes"]
result = await tool(resolution="video_1080p")
data = json.loads(result)
assert data["resolution"] == "video_1080p"
assert "font_sizes" in data
assert "xl" in data["font_sizes"]
@pytest.mark.asyncio
async def test_get_font_sizes_invalid_resolution(self, mcp_with_token_tools):
"""Test getting font sizes for invalid resolution."""
tool = mcp_with_token_tools.tools["remotion_get_font_sizes"]
result = await tool(resolution="invalid_resolution")
data = json.loads(result)
assert "error" in data
assert "available_resolutions" in data
@pytest.mark.asyncio
async def test_get_text_style(self, mcp_with_token_tools):
"""Test getting text style."""
tool = mcp_with_token_tools.tools["remotion_get_text_style"]
result = await tool(style_name="hero_title")
data = json.loads(result)
assert data["style_name"] == "hero_title"
assert "style" in data
assert "fontSize" in data["style"]
@pytest.mark.asyncio
async def test_get_text_style_invalid(self, mcp_with_token_tools):
"""Test getting invalid text style."""
tool = mcp_with_token_tools.tools["remotion_get_text_style"]
result = await tool(style_name="nonexistent_style")
data = json.loads(result)
assert "error" in data
class TestMotionTokenTools:
"""Test motion token tools."""
@pytest.fixture
async def mcp_with_token_tools(self, mock_mcp_server, project_manager, vfs):
"""Register token tools and return MCP server."""
register_token_tools(mock_mcp_server, project_manager, vfs)
return mock_mcp_server
@pytest.mark.asyncio
async def test_list_motion_tokens(self, mcp_with_token_tools):
"""Test listing all motion tokens."""
tool = mcp_with_token_tools.tools["remotion_list_motion_tokens"]
result = await tool()
data = json.loads(result)
assert "spring_configs" in data
assert "easing_curves" in data
assert "durations" in data
assert "animation_presets" in data
@pytest.mark.asyncio
async def test_get_spring_configs(self, mcp_with_token_tools):
"""Test getting all spring configs."""
tool = mcp_with_token_tools.tools["remotion_get_spring_configs"]
result = await tool()
data = json.loads(result)
assert "spring_configs" in data
assert "smooth" in data["spring_configs"]
assert "bouncy" in data["spring_configs"]
@pytest.mark.asyncio
async def test_get_spring_config(self, mcp_with_token_tools):
"""Test getting specific spring config."""
tool = mcp_with_token_tools.tools["remotion_get_spring_config"]
result = await tool(spring_name="bouncy")
data = json.loads(result)
assert data["spring_name"] == "bouncy"
assert "config" in data
assert "damping" in data["config"]["config"]
@pytest.mark.asyncio
async def test_get_spring_config_invalid(self, mcp_with_token_tools):
"""Test getting invalid spring config."""
tool = mcp_with_token_tools.tools["remotion_get_spring_config"]
result = await tool(spring_name="nonexistent")
data = json.loads(result)
assert "error" in data
@pytest.mark.asyncio
async def test_get_easing_curves(self, mcp_with_token_tools):
"""Test getting all easing curves."""
tool = mcp_with_token_tools.tools["remotion_get_easing_curves"]
result = await tool()
data = json.loads(result)
assert "easing_curves" in data
assert "ease_out" in data["easing_curves"]
@pytest.mark.asyncio
async def test_get_easing_curve(self, mcp_with_token_tools):
"""Test getting specific easing curve."""
tool = mcp_with_token_tools.tools["remotion_get_easing_curve"]
result = await tool(easing_name="ease_out_back")
data = json.loads(result)
assert data["easing_name"] == "ease_out_back"
assert "curve" in data
assert "curve" in data["curve"]
@pytest.mark.asyncio
async def test_get_easing_curve_invalid(self, mcp_with_token_tools):
"""Test getting invalid easing curve."""
tool = mcp_with_token_tools.tools["remotion_get_easing_curve"]
result = await tool(easing_name="nonexistent")
data = json.loads(result)
assert "error" in data
@pytest.mark.asyncio
async def test_get_durations(self, mcp_with_token_tools):
"""Test getting all durations."""
tool = mcp_with_token_tools.tools["remotion_get_durations"]
result = await tool()
data = json.loads(result)
assert "durations" in data
assert "normal" in data["durations"]
@pytest.mark.asyncio
async def test_get_duration(self, mcp_with_token_tools):
"""Test getting specific duration."""
tool = mcp_with_token_tools.tools["remotion_get_duration"]
result = await tool(duration_name="normal")
data = json.loads(result)
assert data["duration_name"] == "normal"
assert "duration" in data
assert "frames" in data["duration"]
@pytest.mark.asyncio
async def test_get_duration_invalid(self, mcp_with_token_tools):
"""Test getting invalid duration."""
tool = mcp_with_token_tools.tools["remotion_get_duration"]
result = await tool(duration_name="nonexistent")
data = json.loads(result)
assert "error" in data
@pytest.mark.asyncio
async def test_get_animation_presets(self, mcp_with_token_tools):
"""Test getting all animation presets."""
tool = mcp_with_token_tools.tools["remotion_get_animation_presets"]
result = await tool()
data = json.loads(result)
assert "animation_presets" in data
assert "fade_in" in data["animation_presets"]
@pytest.mark.asyncio
async def test_get_animation_preset(self, mcp_with_token_tools):
"""Test getting specific animation preset."""
tool = mcp_with_token_tools.tools["remotion_get_animation_preset"]
result = await tool(preset_name="fade_in")
data = json.loads(result)
assert data["preset_name"] == "fade_in"
assert "preset" in data
assert "properties" in data["preset"]
@pytest.mark.asyncio
async def test_get_animation_preset_invalid(self, mcp_with_token_tools):
"""Test getting invalid animation preset."""
tool = mcp_with_token_tools.tools["remotion_get_animation_preset"]
result = await tool(preset_name="nonexistent")
data = json.loads(result)
assert "error" in data
@pytest.mark.asyncio
async def test_get_youtube_optimizations(self, mcp_with_token_tools):
"""Test getting YouTube optimizations."""
tool = mcp_with_token_tools.tools["remotion_get_youtube_optimizations"]
result = await tool()
data = json.loads(result)
assert "youtube_optimizations" in data
assert "hook_timing" in data["youtube_optimizations"]
assert "pattern_interrupt" in data["youtube_optimizations"]
class TestTokenImportExportTools:
"""Test token import/export MCP tools."""
@pytest.fixture
async def mcp_with_token_tools(self, mock_mcp_server, project_manager, vfs):
"""Register token tools and return MCP server."""
register_token_tools(mock_mcp_server, project_manager, vfs)
return mock_mcp_server
# Typography Token Export/Import Tests
@pytest.mark.asyncio
async def test_export_typography_tokens(self, mcp_with_token_tools):
"""Test exporting typography tokens via MCP tool."""
tool = mcp_with_token_tools.tools["remotion_export_typography_tokens"]
result = await tool(file_path="test_typo.json", include_all=True)
data = json.loads(result)
assert data["status"] == "success"
assert data["file_path"] == "test_typo.json"
assert "Typography tokens exported successfully" in data["message"]
@pytest.mark.asyncio
async def test_export_typography_tokens_font_families_only(self, mcp_with_token_tools):
"""Test exporting only font families."""
tool = mcp_with_token_tools.tools["remotion_export_typography_tokens"]
result = await tool(file_path="families.json", include_all=False, font_families_only=True)
data = json.loads(result)
assert data["status"] == "success"
assert data["file_path"] == "families.json"
@pytest.mark.asyncio
async def test_export_typography_tokens_text_styles_only(self, mcp_with_token_tools):
"""Test exporting only text styles."""
tool = mcp_with_token_tools.tools["remotion_export_typography_tokens"]
result = await tool(file_path="styles.json", include_all=False, text_styles_only=True)
data = json.loads(result)
assert data["status"] == "success"
assert data["file_path"] == "styles.json"
@pytest.mark.asyncio
async def test_import_typography_tokens(self, mcp_with_token_tools, vfs):
"""Test importing typography tokens via MCP tool."""
# First export to create a file
export_tool = mcp_with_token_tools.tools["remotion_export_typography_tokens"]
await export_tool(file_path="import_test.json")
# Then import it
import_tool = mcp_with_token_tools.tools["remotion_import_typography_tokens"]
result = await import_tool(file_path="import_test.json", merge=True)
data = json.loads(result)
assert data["status"] == "success"
assert "Successfully imported" in data["message"]
@pytest.mark.asyncio
async def test_import_typography_tokens_no_merge(self, mcp_with_token_tools, vfs):
"""Test importing typography tokens without merge."""
# First export to create a file
export_tool = mcp_with_token_tools.tools["remotion_export_typography_tokens"]
await export_tool(file_path="replace_test.json")
# Then import without merging
import_tool = mcp_with_token_tools.tools["remotion_import_typography_tokens"]
result = await import_tool(file_path="replace_test.json", merge=False)
data = json.loads(result)
assert data["status"] == "success"
@pytest.mark.asyncio
async def test_import_typography_tokens_invalid_file(self, mcp_with_token_tools, vfs):
"""Test importing from non-existent file."""
import_tool = mcp_with_token_tools.tools["remotion_import_typography_tokens"]
result = await import_tool(file_path="nonexistent.json")
data = json.loads(result)
assert "error" in data
# Color Token Export/Import Tests
@pytest.mark.asyncio
async def test_export_color_tokens(self, mcp_with_token_tools):
"""Test exporting color tokens via MCP tool."""
tool = mcp_with_token_tools.tools["remotion_export_color_tokens"]
result = await tool(file_path="test_colors.json")
data = json.loads(result)
assert data["status"] == "success"
assert data["file_path"] == "test_colors.json"
assert "Color tokens exported successfully" in data["message"]
@pytest.mark.asyncio
async def test_export_color_tokens_specific_theme(self, mcp_with_token_tools):
"""Test exporting specific theme colors."""
tool = mcp_with_token_tools.tools["remotion_export_color_tokens"]
result = await tool(file_path="tech_colors.json", theme_name="tech")
data = json.loads(result)
assert data["status"] == "success"
assert data["file_path"] == "tech_colors.json"
@pytest.mark.asyncio
async def test_export_color_tokens_invalid_theme(self, mcp_with_token_tools):
"""Test exporting invalid theme returns error."""
tool = mcp_with_token_tools.tools["remotion_export_color_tokens"]
result = await tool(theme_name="nonexistent_theme")
data = json.loads(result)
assert "error" in data
@pytest.mark.asyncio
async def test_import_color_tokens(self, mcp_with_token_tools, vfs):
"""Test importing color tokens via MCP tool."""
# First export to create a file
export_tool = mcp_with_token_tools.tools["remotion_export_color_tokens"]
await export_tool(file_path="import_colors.json")
# Then import it
import_tool = mcp_with_token_tools.tools["remotion_import_color_tokens"]
result = await import_tool(file_path="import_colors.json", merge=True)
data = json.loads(result)
assert data["status"] == "success"
assert "Successfully imported" in data["message"]
@pytest.mark.asyncio
async def test_import_color_tokens_no_merge(self, mcp_with_token_tools, vfs):
"""Test importing color tokens without merge."""
# First export to create a file
export_tool = mcp_with_token_tools.tools["remotion_export_color_tokens"]
await export_tool(file_path="replace_colors.json")
# Then import without merging
import_tool = mcp_with_token_tools.tools["remotion_import_color_tokens"]
result = await import_tool(file_path="replace_colors.json", merge=False)
data = json.loads(result)
assert data["status"] == "success"
@pytest.mark.asyncio
async def test_import_color_tokens_invalid_file(self, mcp_with_token_tools, vfs):
"""Test importing from non-existent color file."""
import_tool = mcp_with_token_tools.tools["remotion_import_color_tokens"]
result = await import_tool(file_path="nonexistent_colors.json")
data = json.loads(result)
assert "error" in data
# Motion Token Export/Import Tests
@pytest.mark.asyncio
async def test_export_motion_tokens(self, mcp_with_token_tools):
"""Test exporting motion tokens via MCP tool."""
tool = mcp_with_token_tools.tools["remotion_export_motion_tokens"]
result = await tool(file_path="test_motion.json")
data = json.loads(result)
assert data["status"] == "success"
assert data["file_path"] == "test_motion.json"
assert "Motion tokens exported successfully" in data["message"]
@pytest.mark.asyncio
async def test_export_motion_tokens_springs_only(self, mcp_with_token_tools):
"""Test exporting only spring configs."""
tool = mcp_with_token_tools.tools["remotion_export_motion_tokens"]
result = await tool(file_path="springs.json", springs_only=True)
data = json.loads(result)
assert data["status"] == "success"
assert data["file_path"] == "springs.json"
@pytest.mark.asyncio
async def test_export_motion_tokens_easings_only(self, mcp_with_token_tools):
"""Test exporting only easing curves."""
tool = mcp_with_token_tools.tools["remotion_export_motion_tokens"]
result = await tool(file_path="easings.json", easings_only=True)
data = json.loads(result)
assert data["status"] == "success"
assert data["file_path"] == "easings.json"
@pytest.mark.asyncio
async def test_export_motion_tokens_presets_only(self, mcp_with_token_tools):
"""Test exporting only animation presets."""
tool = mcp_with_token_tools.tools["remotion_export_motion_tokens"]
result = await tool(file_path="presets.json", presets_only=True)
data = json.loads(result)
assert data["status"] == "success"
assert data["file_path"] == "presets.json"
@pytest.mark.asyncio
async def test_import_motion_tokens(self, mcp_with_token_tools, vfs):
"""Test importing motion tokens via MCP tool."""
# First export to create a file
export_tool = mcp_with_token_tools.tools["remotion_export_motion_tokens"]
await export_tool(file_path="import_motion.json")
# Then import it
import_tool = mcp_with_token_tools.tools["remotion_import_motion_tokens"]
result = await import_tool(file_path="import_motion.json", merge=True)
data = json.loads(result)
assert data["status"] == "success"
assert "Successfully imported" in data["message"]
@pytest.mark.asyncio
async def test_import_motion_tokens_no_merge(self, mcp_with_token_tools, vfs):
"""Test importing motion tokens without merge."""
# First export to create a file
export_tool = mcp_with_token_tools.tools["remotion_export_motion_tokens"]
await export_tool(file_path="replace_motion.json")
# Then import without merging
import_tool = mcp_with_token_tools.tools["remotion_import_motion_tokens"]
result = await import_tool(file_path="replace_motion.json", merge=False)
data = json.loads(result)
assert data["status"] == "success"
@pytest.mark.asyncio
async def test_import_motion_tokens_invalid_file(self, mcp_with_token_tools, vfs):
"""Test importing from non-existent motion file."""
import_tool = mcp_with_token_tools.tools["remotion_import_motion_tokens"]
result = await import_tool(file_path="nonexistent_motion.json")
data = json.loads(result)
assert "error" in data
# Export All Tokens Test
@pytest.mark.asyncio
async def test_export_all_tokens(self, mcp_with_token_tools):
"""Test exporting all token types at once."""
tool = mcp_with_token_tools.tools["remotion_export_all_tokens"]
result = await tool(output_dir="all_tokens")
data = json.loads(result)
assert data["status"] == "success"
assert "files" in data
assert "typography" in data["files"]
assert "colors" in data["files"]
assert "motion" in data["files"]
assert "All tokens exported successfully" in data["message"]
# Error Path Tests
@pytest.mark.asyncio
async def test_export_typography_tokens_error_path(
self, mock_mcp_server, project_manager, mocker
):
"""Test typography export error handling."""
# Create a mock VFS that raises an exception on write_file
mock_vfs = mocker.AsyncMock()
mock_vfs.write_file.side_effect = Exception("VFS write error")
register_token_tools(mock_mcp_server, project_manager, mock_vfs)
tool = mock_mcp_server.tools["remotion_export_typography_tokens"]
result = await tool(file_path="error_test.json")
data = json.loads(result)
assert "error" in data
assert "VFS write error" in data["error"] or "Error" in data["error"]
@pytest.mark.asyncio
async def test_export_motion_tokens_error_path(self, mock_mcp_server, project_manager, mocker):
"""Test motion token export error handling."""
# Create a mock VFS that raises an exception on write_file
mock_vfs = mocker.AsyncMock()
mock_vfs.write_file.side_effect = Exception("VFS write error")
register_token_tools(mock_mcp_server, project_manager, mock_vfs)
tool = mock_mcp_server.tools["remotion_export_motion_tokens"]
result = await tool(file_path="error_test.json")
data = json.loads(result)
assert "error" in data
assert "VFS write error" in data["error"] or "Error" in data["error"]