feat: implement GraphQL write interface with mutations (issue #10)
Some checks failed
Test Suite / performance-tests (push) Has been cancelled
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
Some checks failed
Test Suite / performance-tests (push) Has been cancelled
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
Added comprehensive GraphQL mutations for CRUD operations on markdown files and schemas. Key features: - Complete mutation schema with structured payload types - Markdown file mutations: add, update with front matter support - Schema mutations: add, update, delete with JSON validation - CLI integration with `graphql-mutate` command - Comprehensive error handling and validation - Full test coverage with 24 test cases - Updated documentation with mutation examples and API usage Resolves issue #10: Expose a GraphQL Write Interface 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
797
tests/test_issue_10_graphql_mutations.py
Normal file
797
tests/test_issue_10_graphql_mutations.py
Normal file
@@ -0,0 +1,797 @@
|
||||
"""
|
||||
Comprehensive tests for GraphQL mutations (Issue #10).
|
||||
|
||||
Tests all aspects of the GraphQL write interface including:
|
||||
- Mutation schema validation
|
||||
- Markdown file CRUD operations
|
||||
- Schema CRUD operations
|
||||
- Error handling
|
||||
- CLI integration
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import json
|
||||
import sqlite3
|
||||
import tempfile
|
||||
import os
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from markitect.graphql.schema import schema
|
||||
from markitect.database import DatabaseManager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_db_path():
|
||||
"""Create temporary database for testing."""
|
||||
with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f:
|
||||
db_path = f.name
|
||||
|
||||
# Initialize database with test data
|
||||
db_manager = DatabaseManager(db_path)
|
||||
db_manager.initialize_database()
|
||||
|
||||
yield db_path
|
||||
|
||||
# Cleanup
|
||||
os.unlink(db_path)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def populated_db_path():
|
||||
"""Create temporary database with some test data."""
|
||||
with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f:
|
||||
db_path = f.name
|
||||
|
||||
# Initialize database with test data
|
||||
db_manager = DatabaseManager(db_path)
|
||||
db_manager.initialize_database()
|
||||
|
||||
# Add sample data
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Sample markdown file
|
||||
cursor.execute("""
|
||||
INSERT INTO markdown_files (filename, content, front_matter, created_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""", (
|
||||
'existing.md',
|
||||
'# Existing Document\n\nThis document already exists.',
|
||||
'{"title": "Existing Document"}',
|
||||
datetime.now().isoformat()
|
||||
))
|
||||
|
||||
# Sample schema
|
||||
cursor.execute("""
|
||||
INSERT INTO schemas (filename, title, description, schema_content, created_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""", (
|
||||
'existing-schema.json',
|
||||
'Existing Schema',
|
||||
'A schema that already exists',
|
||||
'{"type": "object", "properties": {"name": {"type": "string"}}}',
|
||||
datetime.now().isoformat()
|
||||
))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
yield db_path
|
||||
|
||||
# Cleanup
|
||||
os.unlink(db_path)
|
||||
|
||||
|
||||
class TestGraphQLMutationSchema:
|
||||
"""Test GraphQL mutation schema definition and validation."""
|
||||
|
||||
def test_schema_has_mutations(self):
|
||||
"""Test that the GraphQL schema has mutations."""
|
||||
result = schema.execute('''
|
||||
{
|
||||
__schema {
|
||||
mutationType {
|
||||
name
|
||||
fields {
|
||||
name
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
''')
|
||||
|
||||
assert result.errors is None
|
||||
mutation_type = result.data['__schema']['mutationType']
|
||||
assert mutation_type is not None
|
||||
assert mutation_type['name'] == 'Mutation'
|
||||
|
||||
field_names = [field['name'] for field in mutation_type['fields']]
|
||||
assert 'addMarkdownFile' in field_names
|
||||
assert 'updateMarkdownFile' in field_names
|
||||
assert 'addSchema' in field_names
|
||||
assert 'updateSchema' in field_names
|
||||
assert 'deleteSchema' in field_names
|
||||
|
||||
def test_add_markdown_file_mutation_signature(self):
|
||||
"""Test addMarkdownFile mutation has correct signature."""
|
||||
result = schema.execute('''
|
||||
{
|
||||
__schema {
|
||||
mutationType {
|
||||
fields {
|
||||
name
|
||||
args {
|
||||
name
|
||||
type {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
''')
|
||||
|
||||
mutation_fields = result.data['__schema']['mutationType']['fields']
|
||||
add_file_field = next(f for f in mutation_fields if f['name'] == 'addMarkdownFile')
|
||||
|
||||
arg_names = [arg['name'] for arg in add_file_field['args']]
|
||||
assert 'filename' in arg_names
|
||||
assert 'content' in arg_names
|
||||
|
||||
def test_mutation_payload_types(self):
|
||||
"""Test that mutation payload types have correct structure."""
|
||||
result = schema.execute('''
|
||||
{
|
||||
__schema {
|
||||
types {
|
||||
name
|
||||
fields {
|
||||
name
|
||||
type {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
''')
|
||||
|
||||
types = {t['name']: t for t in result.data['__schema']['types']}
|
||||
|
||||
# Check AddMarkdownFilePayload
|
||||
payload = types.get('AddMarkdownFilePayload')
|
||||
assert payload is not None
|
||||
field_names = [f['name'] for f in payload['fields']]
|
||||
assert 'markdownFile' in field_names
|
||||
assert 'success' in field_names
|
||||
assert 'errors' in field_names
|
||||
|
||||
|
||||
class TestMarkdownFileMutations:
|
||||
"""Test markdown file CRUD mutations."""
|
||||
|
||||
def test_add_markdown_file_success(self, temp_db_path):
|
||||
"""Test successful markdown file creation."""
|
||||
with patch('markitect.graphql.resolvers.get_default_database_path', return_value=temp_db_path):
|
||||
mutation = '''
|
||||
mutation {
|
||||
addMarkdownFile(
|
||||
filename: "new-file.md"
|
||||
content: "# New File\\n\\nThis is new content."
|
||||
) {
|
||||
success
|
||||
markdownFile {
|
||||
id
|
||||
filename
|
||||
content
|
||||
wordCount
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
result = schema.execute(mutation)
|
||||
|
||||
assert result.errors is None
|
||||
data = result.data['addMarkdownFile']
|
||||
assert data['success'] is True
|
||||
assert len(data['errors']) == 0
|
||||
assert data['markdownFile'] is not None
|
||||
assert data['markdownFile']['filename'] == 'new-file.md'
|
||||
assert 'New File' in data['markdownFile']['content']
|
||||
assert data['markdownFile']['wordCount'] > 0
|
||||
|
||||
def test_add_markdown_file_with_front_matter(self, temp_db_path):
|
||||
"""Test markdown file creation with front matter."""
|
||||
with patch('markitect.graphql.resolvers.get_default_database_path', return_value=temp_db_path):
|
||||
content_with_frontmatter = '''---
|
||||
title: "Test Document"
|
||||
author: "Test Author"
|
||||
tags: ["test", "markdown"]
|
||||
---
|
||||
|
||||
# Test Document
|
||||
|
||||
This is a test document with front matter.
|
||||
'''
|
||||
|
||||
mutation = '''
|
||||
mutation {
|
||||
addMarkdownFile(
|
||||
filename: "with-frontmatter.md"
|
||||
content: "%s"
|
||||
) {
|
||||
success
|
||||
markdownFile {
|
||||
id
|
||||
filename
|
||||
hasFrontMatter
|
||||
frontMatter {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
''' % content_with_frontmatter.replace('\n', '\\n').replace('"', '\\"')
|
||||
|
||||
result = schema.execute(mutation)
|
||||
|
||||
assert result.errors is None
|
||||
data = result.data['addMarkdownFile']
|
||||
assert data['success'] is True
|
||||
assert data['markdownFile']['hasFrontMatter'] is True
|
||||
front_matter_keys = [fm['key'] for fm in data['markdownFile']['frontMatter']]
|
||||
assert 'title' in front_matter_keys
|
||||
assert 'author' in front_matter_keys
|
||||
|
||||
def test_add_markdown_file_duplicate_filename(self, populated_db_path):
|
||||
"""Test adding a file with duplicate filename (should succeed as update)."""
|
||||
with patch('markitect.graphql.resolvers.get_default_database_path', return_value=populated_db_path):
|
||||
mutation = '''
|
||||
mutation {
|
||||
addMarkdownFile(
|
||||
filename: "existing.md"
|
||||
content: "# Updated Content\\n\\nThis content replaces the existing."
|
||||
) {
|
||||
success
|
||||
markdownFile {
|
||||
filename
|
||||
content
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
result = schema.execute(mutation)
|
||||
|
||||
assert result.errors is None
|
||||
data = result.data['addMarkdownFile']
|
||||
assert data['success'] is True
|
||||
assert 'Updated Content' in data['markdownFile']['content']
|
||||
|
||||
def test_update_markdown_file_success(self, populated_db_path):
|
||||
"""Test successful markdown file update."""
|
||||
with patch('markitect.graphql.resolvers.get_default_database_path', return_value=populated_db_path):
|
||||
mutation = '''
|
||||
mutation {
|
||||
updateMarkdownFile(
|
||||
id: 1
|
||||
content: "# Updated Title\\n\\nThis content has been updated."
|
||||
) {
|
||||
success
|
||||
markdownFile {
|
||||
id
|
||||
content
|
||||
wordCount
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
result = schema.execute(mutation)
|
||||
|
||||
assert result.errors is None
|
||||
data = result.data['updateMarkdownFile']
|
||||
assert data['success'] is True
|
||||
assert len(data['errors']) == 0
|
||||
assert 'Updated Title' in data['markdownFile']['content']
|
||||
|
||||
def test_update_markdown_file_not_found(self, temp_db_path):
|
||||
"""Test updating non-existent markdown file."""
|
||||
with patch('markitect.graphql.resolvers.get_default_database_path', return_value=temp_db_path):
|
||||
mutation = '''
|
||||
mutation {
|
||||
updateMarkdownFile(
|
||||
id: 999
|
||||
content: "# This should fail"
|
||||
) {
|
||||
success
|
||||
markdownFile {
|
||||
id
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
result = schema.execute(mutation)
|
||||
|
||||
assert result.errors is None
|
||||
data = result.data['updateMarkdownFile']
|
||||
assert data['success'] is False
|
||||
assert data['markdownFile'] is None
|
||||
assert len(data['errors']) > 0
|
||||
assert 'not found' in data['errors'][0].lower()
|
||||
|
||||
def test_update_markdown_file_no_content(self, populated_db_path):
|
||||
"""Test updating markdown file without providing content."""
|
||||
with patch('markitect.graphql.resolvers.get_default_database_path', return_value=populated_db_path):
|
||||
mutation = '''
|
||||
mutation {
|
||||
updateMarkdownFile(id: 1) {
|
||||
success
|
||||
errors
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
result = schema.execute(mutation)
|
||||
|
||||
assert result.errors is None
|
||||
data = result.data['updateMarkdownFile']
|
||||
assert data['success'] is False
|
||||
assert 'required' in data['errors'][0].lower()
|
||||
|
||||
|
||||
class TestSchemaMutations:
|
||||
"""Test JSON schema CRUD mutations."""
|
||||
|
||||
def test_add_schema_success(self, temp_db_path):
|
||||
"""Test successful schema creation."""
|
||||
with patch('markitect.graphql.resolvers.get_default_database_path', return_value=temp_db_path):
|
||||
schema_content = {
|
||||
"type": "object",
|
||||
"title": "User Schema",
|
||||
"description": "Schema for user objects",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer", "minimum": 0}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
|
||||
mutation = '''
|
||||
mutation {
|
||||
addSchema(
|
||||
filename: "user-schema.json"
|
||||
schemaContent: "%s"
|
||||
) {
|
||||
success
|
||||
schema {
|
||||
id
|
||||
filename
|
||||
title
|
||||
description
|
||||
propertyCount
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
''' % json.dumps(schema_content).replace('"', '\\"')
|
||||
|
||||
result = schema.execute(mutation)
|
||||
|
||||
assert result.errors is None
|
||||
data = result.data['addSchema']
|
||||
assert data['success'] is True
|
||||
assert len(data['errors']) == 0
|
||||
assert data['schema']['filename'] == 'user-schema.json'
|
||||
assert data['schema']['title'] == 'User Schema'
|
||||
assert data['schema']['propertyCount'] == 2
|
||||
|
||||
def test_add_schema_invalid_json(self, temp_db_path):
|
||||
"""Test adding schema with invalid JSON."""
|
||||
with patch('markitect.graphql.resolvers.get_default_database_path', return_value=temp_db_path):
|
||||
mutation = '''
|
||||
mutation {
|
||||
addSchema(
|
||||
filename: "invalid-schema.json"
|
||||
schemaContent: "{ invalid json }"
|
||||
) {
|
||||
success
|
||||
schema {
|
||||
id
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
result = schema.execute(mutation)
|
||||
|
||||
# GraphQL should reject invalid JSON at the schema validation level
|
||||
assert result.errors is not None
|
||||
assert len(result.errors) > 0
|
||||
assert "Badly formed JSONString" in str(result.errors[0])
|
||||
|
||||
def test_update_schema_success(self, populated_db_path):
|
||||
"""Test successful schema update."""
|
||||
with patch('markitect.graphql.resolvers.get_default_database_path', return_value=populated_db_path):
|
||||
new_schema = {
|
||||
"type": "object",
|
||||
"title": "Updated Schema",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"email": {"type": "string", "format": "email"}
|
||||
}
|
||||
}
|
||||
|
||||
mutation = '''
|
||||
mutation {
|
||||
updateSchema(
|
||||
id: 1
|
||||
schemaContent: "%s"
|
||||
) {
|
||||
success
|
||||
schema {
|
||||
title
|
||||
propertyCount
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
''' % json.dumps(new_schema).replace('"', '\\"')
|
||||
|
||||
result = schema.execute(mutation)
|
||||
|
||||
assert result.errors is None
|
||||
data = result.data['updateSchema']
|
||||
assert data['success'] is True
|
||||
assert data['schema']['title'] == 'Updated Schema'
|
||||
assert data['schema']['propertyCount'] == 2
|
||||
|
||||
def test_update_schema_not_found(self, temp_db_path):
|
||||
"""Test updating non-existent schema."""
|
||||
with patch('markitect.graphql.resolvers.get_default_database_path', return_value=temp_db_path):
|
||||
mutation = '''
|
||||
mutation {
|
||||
updateSchema(
|
||||
id: 999
|
||||
schemaContent: "{\\"type\\": \\"object\\"}"
|
||||
) {
|
||||
success
|
||||
errors
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
result = schema.execute(mutation)
|
||||
|
||||
assert result.errors is None
|
||||
data = result.data['updateSchema']
|
||||
assert data['success'] is False
|
||||
assert 'not found' in data['errors'][0].lower()
|
||||
|
||||
def test_delete_schema_success(self, populated_db_path):
|
||||
"""Test successful schema deletion."""
|
||||
with patch('markitect.graphql.resolvers.get_default_database_path', return_value=populated_db_path):
|
||||
mutation = '''
|
||||
mutation {
|
||||
deleteSchema(filename: "existing-schema.json") {
|
||||
success
|
||||
deletedFilename
|
||||
errors
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
result = schema.execute(mutation)
|
||||
|
||||
assert result.errors is None
|
||||
data = result.data['deleteSchema']
|
||||
assert data['success'] is True
|
||||
assert data['deletedFilename'] == 'existing-schema.json'
|
||||
assert len(data['errors']) == 0
|
||||
|
||||
def test_delete_schema_not_found(self, temp_db_path):
|
||||
"""Test deleting non-existent schema."""
|
||||
with patch('markitect.graphql.resolvers.get_default_database_path', return_value=temp_db_path):
|
||||
mutation = '''
|
||||
mutation {
|
||||
deleteSchema(filename: "nonexistent.json") {
|
||||
success
|
||||
deletedFilename
|
||||
errors
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
result = schema.execute(mutation)
|
||||
|
||||
assert result.errors is None
|
||||
data = result.data['deleteSchema']
|
||||
assert data['success'] is False
|
||||
assert data['deletedFilename'] is None
|
||||
|
||||
|
||||
class TestMutationErrorHandling:
|
||||
"""Test error handling in mutations."""
|
||||
|
||||
def test_database_error_handling(self, temp_db_path):
|
||||
"""Test mutation behavior when database is unavailable."""
|
||||
# Use a non-existent database path
|
||||
with patch('markitect.graphql.resolvers.get_default_database_path', return_value='/nonexistent/path.db'):
|
||||
mutation = '''
|
||||
mutation {
|
||||
addMarkdownFile(
|
||||
filename: "test.md"
|
||||
content: "# Test"
|
||||
) {
|
||||
success
|
||||
errors
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
result = schema.execute(mutation)
|
||||
|
||||
assert result.errors is None
|
||||
data = result.data['addMarkdownFile']
|
||||
assert data['success'] is False
|
||||
assert len(data['errors']) > 0
|
||||
|
||||
def test_invalid_mutation_syntax(self):
|
||||
"""Test handling of invalid mutation syntax."""
|
||||
mutation = '''
|
||||
mutation {
|
||||
addMarkdownFile(filename: "test.md") {
|
||||
success
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
result = schema.execute(mutation)
|
||||
|
||||
# Should have errors due to missing required 'content' argument
|
||||
assert result.errors is not None
|
||||
|
||||
def test_missing_required_arguments(self):
|
||||
"""Test mutations with missing required arguments."""
|
||||
mutation = '''
|
||||
mutation {
|
||||
addSchema(filename: "test.json") {
|
||||
success
|
||||
errors
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
result = schema.execute(mutation)
|
||||
|
||||
# Should have errors due to missing required 'schemaContent' argument
|
||||
assert result.errors is not None
|
||||
|
||||
|
||||
class TestMutationIntegration:
|
||||
"""Test full integration of mutations with database."""
|
||||
|
||||
def test_crud_workflow(self, temp_db_path):
|
||||
"""Test complete CRUD workflow for markdown files."""
|
||||
with patch('markitect.graphql.resolvers.get_default_database_path', return_value=temp_db_path):
|
||||
# 1. Create a file
|
||||
create_mutation = '''
|
||||
mutation {
|
||||
addMarkdownFile(
|
||||
filename: "workflow-test.md"
|
||||
content: "# Original Content\\n\\nOriginal text."
|
||||
) {
|
||||
success
|
||||
markdownFile {
|
||||
id
|
||||
filename
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
result = schema.execute(create_mutation)
|
||||
assert result.data['addMarkdownFile']['success'] is True
|
||||
file_id = result.data['addMarkdownFile']['markdownFile']['id']
|
||||
|
||||
# 2. Update the file
|
||||
update_mutation = '''
|
||||
mutation {
|
||||
updateMarkdownFile(
|
||||
id: %d
|
||||
content: "# Updated Content\\n\\nUpdated text."
|
||||
) {
|
||||
success
|
||||
markdownFile {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
''' % file_id
|
||||
|
||||
result = schema.execute(update_mutation)
|
||||
assert result.data['updateMarkdownFile']['success'] is True
|
||||
assert 'Updated Content' in result.data['updateMarkdownFile']['markdownFile']['content']
|
||||
|
||||
def test_schema_crud_workflow(self, temp_db_path):
|
||||
"""Test complete CRUD workflow for schemas."""
|
||||
with patch('markitect.graphql.resolvers.get_default_database_path', return_value=temp_db_path):
|
||||
# 1. Create a schema
|
||||
create_mutation = '''
|
||||
mutation {
|
||||
addSchema(
|
||||
filename: "workflow-schema.json"
|
||||
schemaContent: "{\\"type\\": \\"object\\", \\"title\\": \\"Original\\"}"
|
||||
) {
|
||||
success
|
||||
schema {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
result = schema.execute(create_mutation)
|
||||
assert result.data['addSchema']['success'] is True
|
||||
schema_id = result.data['addSchema']['schema']['id']
|
||||
|
||||
# 2. Update the schema
|
||||
update_mutation = '''
|
||||
mutation {
|
||||
updateSchema(
|
||||
id: %d
|
||||
schemaContent: "{\\"type\\": \\"object\\", \\"title\\": \\"Updated\\"}"
|
||||
) {
|
||||
success
|
||||
schema {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
''' % schema_id
|
||||
|
||||
result = schema.execute(update_mutation)
|
||||
assert result.data['updateSchema']['success'] is True
|
||||
assert result.data['updateSchema']['schema']['title'] == 'Updated'
|
||||
|
||||
# 3. Delete the schema
|
||||
delete_mutation = '''
|
||||
mutation {
|
||||
deleteSchema(filename: "workflow-schema.json") {
|
||||
success
|
||||
deletedFilename
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
result = schema.execute(delete_mutation)
|
||||
assert result.data['deleteSchema']['success'] is True
|
||||
assert result.data['deleteSchema']['deletedFilename'] == 'workflow-schema.json'
|
||||
|
||||
|
||||
class TestMutationCLI:
|
||||
"""Test CLI integration for mutations."""
|
||||
|
||||
def test_graphql_mutate_command_available(self):
|
||||
"""Test that graphql-mutate command is available."""
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "markitect.cli", "graphql-mutate", "--help"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
assert result.returncode == 0
|
||||
assert "Execute GraphQL mutations" in result.stdout
|
||||
assert "--local" in result.stdout
|
||||
assert "--variables" in result.stdout
|
||||
|
||||
def test_mutation_examples_in_help(self):
|
||||
"""Test that mutation examples are included in help."""
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "markitect.cli", "graphql-examples"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
assert result.returncode == 0
|
||||
assert "Mutation Examples" in result.stdout
|
||||
assert "addMarkdownFile" in result.stdout
|
||||
assert "updateMarkdownFile" in result.stdout
|
||||
assert "addSchema" in result.stdout
|
||||
assert "deleteSchema" in result.stdout
|
||||
|
||||
|
||||
class TestMutationPayloads:
|
||||
"""Test mutation payload structures."""
|
||||
|
||||
def test_add_markdown_file_payload_structure(self, temp_db_path):
|
||||
"""Test AddMarkdownFilePayload has correct structure."""
|
||||
with patch('markitect.graphql.resolvers.get_default_database_path', return_value=temp_db_path):
|
||||
mutation = '''
|
||||
mutation {
|
||||
addMarkdownFile(
|
||||
filename: "payload-test.md"
|
||||
content: "# Payload Test"
|
||||
) {
|
||||
success
|
||||
markdownFile {
|
||||
id
|
||||
filename
|
||||
content
|
||||
wordCount
|
||||
lineCount
|
||||
hasFrontMatter
|
||||
createdAt
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
result = schema.execute(mutation)
|
||||
|
||||
assert result.errors is None
|
||||
payload = result.data['addMarkdownFile']
|
||||
|
||||
# Check payload structure
|
||||
assert isinstance(payload['success'], bool)
|
||||
assert isinstance(payload['errors'], list)
|
||||
|
||||
if payload['success']:
|
||||
md_file = payload['markdownFile']
|
||||
assert md_file is not None
|
||||
assert isinstance(md_file['id'], int)
|
||||
assert isinstance(md_file['filename'], str)
|
||||
assert isinstance(md_file['wordCount'], int)
|
||||
assert isinstance(md_file['lineCount'], int)
|
||||
assert isinstance(md_file['hasFrontMatter'], bool)
|
||||
|
||||
def test_error_payload_structure(self, temp_db_path):
|
||||
"""Test error payloads have correct structure."""
|
||||
with patch('markitect.graphql.resolvers.get_default_database_path', return_value='/nonexistent/path.db'):
|
||||
mutation = '''
|
||||
mutation {
|
||||
addMarkdownFile(
|
||||
filename: "error-test.md"
|
||||
content: "# Error Test"
|
||||
) {
|
||||
success
|
||||
markdownFile {
|
||||
id
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
result = schema.execute(mutation)
|
||||
|
||||
assert result.errors is None
|
||||
payload = result.data['addMarkdownFile']
|
||||
|
||||
assert payload['success'] is False
|
||||
assert payload['markdownFile'] is None
|
||||
assert isinstance(payload['errors'], list)
|
||||
assert len(payload['errors']) > 0
|
||||
assert all(isinstance(error, str) for error in payload['errors'])
|
||||
Reference in New Issue
Block a user