Fix failing tests that expected content to start with heading but now include schema reference comments. Also fix validate command syntax in test (positional to --schema flag). Fixes: - test_generate_stub_with_explicit_associated_path - test_generate_stub_interactive_mode_defaults_to_associated_path - test_generate_stub_validates_generated_draft_against_schema These tests were failing due to changes from Issue #55 schema reference metadata feature and validate command syntax updates. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
330 lines
12 KiB
Python
330 lines
12 KiB
Python
"""
|
|
CLI Integration Tests for Issue #40: Associated Files Management.
|
|
|
|
Tests the enhanced CLI commands that work with associated files.
|
|
"""
|
|
|
|
import json
|
|
import pytest
|
|
from pathlib import Path
|
|
from tempfile import TemporaryDirectory
|
|
from click.testing import CliRunner
|
|
|
|
from markitect.cli import cli
|
|
|
|
|
|
class TestIssue40CLIIntegration:
|
|
"""Test CLI integration for associated files management."""
|
|
|
|
@pytest.fixture
|
|
def runner(self):
|
|
"""Create CLI test runner."""
|
|
return CliRunner()
|
|
|
|
@pytest.fixture
|
|
def temp_dir(self):
|
|
"""Create a temporary directory for testing."""
|
|
with TemporaryDirectory() as temp_dir:
|
|
yield Path(temp_dir)
|
|
|
|
def test_schema_generate_with_explicit_associated_path(self, runner, temp_dir):
|
|
"""schema-generate can use explicit associated .json file path."""
|
|
md_file = temp_dir / "document.md"
|
|
md_file.write_text("# Document\n\n## Introduction\n\nContent here.")
|
|
|
|
# Explicitly specify associated file path
|
|
expected_schema = temp_dir / "document.json"
|
|
result = runner.invoke(cli, [
|
|
'schema-generate', str(md_file), '--output', str(expected_schema)
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
|
|
# Should create associated schema file
|
|
assert expected_schema.exists()
|
|
|
|
# Verify it's valid JSON
|
|
schema_content = json.loads(expected_schema.read_text())
|
|
assert schema_content['type'] == 'object'
|
|
|
|
def test_schema_generate_interactive_mode_defaults_to_associated_path(self, runner, temp_dir):
|
|
"""schema-generate in interactive mode should default to associated path."""
|
|
md_file = temp_dir / "document.md"
|
|
md_file.write_text("# Document\n\n## Introduction\n\nContent here.")
|
|
|
|
# Run schema-generate without --output in interactive mode
|
|
result = runner.invoke(cli, [
|
|
'schema-generate', str(md_file)
|
|
], env={'MARKITECT_MODE': 'interactive'})
|
|
|
|
assert result.exit_code == 0
|
|
|
|
# Should create associated schema file
|
|
expected_schema = temp_dir / "document.json"
|
|
assert expected_schema.exists()
|
|
|
|
# Verify it's valid JSON
|
|
schema_content = json.loads(expected_schema.read_text())
|
|
assert schema_content['type'] == 'object'
|
|
|
|
def test_generate_stub_with_explicit_associated_path(self, runner, temp_dir):
|
|
"""generate-stub can use explicit associated .md file path."""
|
|
schema_file = temp_dir / "template.json"
|
|
schema_content = {
|
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
"type": "object",
|
|
"title": "Template Schema",
|
|
"properties": {
|
|
"headings": {
|
|
"type": "object",
|
|
"properties": {
|
|
"level_1": {"type": "array", "minItems": 1, "maxItems": 1}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
schema_file.write_text(json.dumps(schema_content))
|
|
|
|
# Explicitly specify associated file path
|
|
expected_md = temp_dir / "template.md"
|
|
result = runner.invoke(cli, [
|
|
'generate-stub', str(schema_file), '--output', str(expected_md)
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
|
|
# Should create associated markdown file
|
|
assert expected_md.exists()
|
|
|
|
# Verify it's valid markdown with schema reference
|
|
md_content = expected_md.read_text()
|
|
# Content should include schema reference metadata and heading
|
|
assert '<!-- Generated from schema:' in md_content
|
|
assert '# Template Schema' in md_content
|
|
|
|
def test_generate_stub_interactive_mode_defaults_to_associated_path(self, runner, temp_dir):
|
|
"""generate-stub in interactive mode should default to associated path."""
|
|
schema_file = temp_dir / "template.json"
|
|
schema_content = {
|
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
"type": "object",
|
|
"title": "Template Schema",
|
|
"properties": {
|
|
"headings": {
|
|
"type": "object",
|
|
"properties": {
|
|
"level_1": {"type": "array", "minItems": 1, "maxItems": 1}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
schema_file.write_text(json.dumps(schema_content))
|
|
|
|
# Run generate-stub without --output in interactive mode
|
|
result = runner.invoke(cli, [
|
|
'generate-stub', str(schema_file)
|
|
], env={'MARKITECT_MODE': 'interactive'})
|
|
|
|
assert result.exit_code == 0
|
|
|
|
# Should create associated markdown file
|
|
expected_md = temp_dir / "template.md"
|
|
assert expected_md.exists()
|
|
|
|
# Verify it's valid markdown with schema reference
|
|
md_content = expected_md.read_text()
|
|
# Content should include schema reference metadata and heading
|
|
assert '<!-- Generated from schema:' in md_content
|
|
assert '# Template Schema' in md_content
|
|
|
|
def test_validate_auto_discovers_associated_schema(self, runner, temp_dir):
|
|
"""validate should auto-discover associated schema when not specified."""
|
|
md_file = temp_dir / "article.md"
|
|
schema_file = temp_dir / "article.json"
|
|
|
|
md_file.write_text("# Article\n\n## Introduction\n\nContent.")
|
|
schema_file.write_text('{"$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "title": "Article Schema"}')
|
|
|
|
# Run validate with only markdown file (should find associated schema)
|
|
result = runner.invoke(cli, [
|
|
'validate', str(md_file)
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
# Should indicate successful validation using auto-discovered schema
|
|
|
|
def test_new_associated_files_list_command(self, runner, temp_dir):
|
|
"""Should have new command to list associated file pairs."""
|
|
# Create some file pairs
|
|
(temp_dir / "doc1.md").write_text("# Doc 1")
|
|
(temp_dir / "doc1.json").write_text('{"type": "object"}')
|
|
|
|
(temp_dir / "doc2.md").write_text("# Doc 2")
|
|
(temp_dir / "doc2.json").write_text('{"type": "object"}')
|
|
|
|
# Create orphaned files
|
|
(temp_dir / "orphan.md").write_text("# Orphan")
|
|
|
|
result = runner.invoke(cli, [
|
|
'associated-files', 'list', str(temp_dir)
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
assert 'doc1' in result.output
|
|
assert 'doc2' in result.output
|
|
# Should show paired status
|
|
|
|
def test_new_associated_files_info_command(self, runner, temp_dir):
|
|
"""Should have command to show info about associated files."""
|
|
md_file = temp_dir / "example.md"
|
|
schema_file = temp_dir / "example.json"
|
|
|
|
md_file.write_text("# Example\n\nContent here.")
|
|
schema_file.write_text('{"type": "object", "title": "Example"}')
|
|
|
|
result = runner.invoke(cli, [
|
|
'associated-files', 'info', str(md_file)
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
assert 'example' in result.output
|
|
assert 'paired' in result.output.lower()
|
|
|
|
def test_new_associated_files_create_command(self, runner, temp_dir):
|
|
"""Should have command to create missing associated files."""
|
|
md_file = temp_dir / "lonely.md"
|
|
md_file.write_text("# Lonely Document\n\n## Section\n\nContent.")
|
|
|
|
# Create associated schema
|
|
result = runner.invoke(cli, [
|
|
'associated-files', 'create-schema', str(md_file)
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
|
|
expected_schema = temp_dir / "lonely.json"
|
|
assert expected_schema.exists()
|
|
|
|
# Verify it's a valid schema
|
|
schema = json.loads(expected_schema.read_text())
|
|
assert schema['type'] == 'object'
|
|
|
|
def test_new_associated_files_create_stub_command(self, runner, temp_dir):
|
|
"""Should have command to create stub from existing schema."""
|
|
schema_file = temp_dir / "template.json"
|
|
schema_content = {
|
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
"type": "object",
|
|
"title": "Template",
|
|
"properties": {
|
|
"headings": {
|
|
"type": "object",
|
|
"properties": {
|
|
"level_1": {"type": "array", "minItems": 1}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
schema_file.write_text(json.dumps(schema_content))
|
|
|
|
# Create associated markdown stub
|
|
result = runner.invoke(cli, [
|
|
'associated-files', 'create-stub', str(schema_file)
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
|
|
expected_md = temp_dir / "template.md"
|
|
assert expected_md.exists()
|
|
|
|
# Verify it's valid markdown
|
|
md_content = expected_md.read_text()
|
|
assert md_content.startswith('# ')
|
|
|
|
def test_associated_files_status_command(self, runner, temp_dir):
|
|
"""Should show status of associated files in directory."""
|
|
# Create mixed scenarios
|
|
(temp_dir / "paired.md").write_text("# Paired")
|
|
(temp_dir / "paired.json").write_text('{"type": "object"}')
|
|
|
|
(temp_dir / "lonely.md").write_text("# Lonely")
|
|
(temp_dir / "orphan.json").write_text('{"type": "object"}')
|
|
|
|
result = runner.invoke(cli, [
|
|
'associated-files', 'status', str(temp_dir)
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
assert 'paired' in result.output.lower() or 'orphaned' in result.output.lower()
|
|
|
|
def test_enhanced_commands_preserve_explicit_output(self, runner, temp_dir):
|
|
"""Enhanced commands should still respect explicit --output options."""
|
|
md_file = temp_dir / "source.md"
|
|
md_file.write_text("# Source\n\n## Content")
|
|
|
|
custom_output = temp_dir / "custom_name.json"
|
|
|
|
# Use explicit output path (should override associated file logic)
|
|
result = runner.invoke(cli, [
|
|
'schema-generate', str(md_file), '--output', str(custom_output)
|
|
])
|
|
|
|
assert result.exit_code == 0
|
|
assert custom_output.exists()
|
|
assert not (temp_dir / "source.json").exists()
|
|
|
|
def test_associated_files_help_commands(self, runner):
|
|
"""Associated files commands should provide helpful usage information."""
|
|
result = runner.invoke(cli, ['associated-files', '--help'])
|
|
assert result.exit_code == 0
|
|
assert 'associated' in result.output.lower()
|
|
assert 'files' in result.output.lower()
|
|
|
|
# Test subcommand help
|
|
result = runner.invoke(cli, ['associated-files', 'list', '--help'])
|
|
assert result.exit_code == 0
|
|
|
|
def test_error_handling_for_non_existent_files(self, runner, temp_dir):
|
|
"""Should handle non-existent files gracefully."""
|
|
non_existent = temp_dir / "does_not_exist.md"
|
|
|
|
result = runner.invoke(cli, [
|
|
'associated-files', 'info', str(non_existent)
|
|
])
|
|
|
|
assert result.exit_code != 0
|
|
assert 'not found' in result.output.lower() or 'error' in result.output.lower()
|
|
|
|
def test_workflow_integration_complete_cycle(self, runner, temp_dir):
|
|
"""Test complete workflow with associated files."""
|
|
original_md = temp_dir / "workflow.md"
|
|
original_md.write_text("# Workflow Example\n\n## Introduction\n\nTest content.")
|
|
|
|
# Step 1: Generate schema explicitly to workflow.json
|
|
schema_file = temp_dir / "workflow.json"
|
|
result1 = runner.invoke(cli, [
|
|
'schema-generate', str(original_md), '--output', str(schema_file)
|
|
])
|
|
assert result1.exit_code == 0
|
|
assert schema_file.exists()
|
|
|
|
# Step 2: Generate stub with different name to avoid conflict
|
|
stub_name = temp_dir / "workflow_template.md"
|
|
result2 = runner.invoke(cli, [
|
|
'generate-stub', str(schema_file), '--output', str(stub_name)
|
|
])
|
|
assert result2.exit_code == 0
|
|
assert stub_name.exists()
|
|
|
|
# Step 3: Validate original against its schema
|
|
result3 = runner.invoke(cli, [
|
|
'validate', str(original_md)
|
|
])
|
|
assert result3.exit_code == 0
|
|
|
|
# Step 4: List associated files
|
|
result4 = runner.invoke(cli, [
|
|
'associated-files', 'status', str(temp_dir)
|
|
])
|
|
assert result4.exit_code == 0
|
|
assert 'paired' in result4.output.lower() or 'orphaned' in result4.output.lower() |