Complete TDD8 implementation of publication directory support for md-render command: CORE FEATURES: • Publication directory management with ~/Notes/ default • MARKITECT_PUBLICATION_DIR environment variable override • Single file processing with --use-publication-dir flag • Directory processing with --dont-use-publication-dir flag • Recursive directory traversal with structure preservation • Automatic directory creation and path normalization IMPLEMENTATION DETAILS: • Extended md-render command with new CLI flags • Added 9 new helper functions for directory/file processing • Support for both single files and directory inputs • Comprehensive error handling and validation • Maintains backward compatibility CLI FLAGS ADDED: • --use-publication-dir: Force single files to use publication directory • --dont-use-publication-dir: Force directory processing to place HTML next to MD BEHAVIOR: • Single files: HTML next to MD by default, publication dir with flag • Directories: HTML in publication dir by default, next to MD with flag • Environment variable MARKITECT_PUBLICATION_DIR overrides default TESTING: • 18 comprehensive tests covering all functionality • Publication directory management (4 tests) • Single file processing (3 tests) • Directory processing (4 tests) • CLI integration (4 tests) • Edge cases (3 tests) • 100% test pass rate TDD8 Workflow: ISSUE→TEST→RED→GREEN→REFACTOR→DOCUMENT→REFINE→PUBLISH 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
374 lines
14 KiB
Python
374 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Test suite for Issue #135: Instant Markdown base and publication directory
|
|
|
|
This test suite validates the publication directory functionality for the md-render command,
|
|
including directory processing, environment variable handling, and CLI flags.
|
|
|
|
TDD8 Workflow: ISSUE→TEST→RED→GREEN→REFACTOR→DOCUMENT→REFINE→PUBLISH
|
|
State: RED (Tests should fail initially)
|
|
"""
|
|
|
|
import pytest
|
|
import tempfile
|
|
import os
|
|
import shutil
|
|
from pathlib import Path
|
|
from unittest.mock import patch, MagicMock
|
|
import subprocess
|
|
|
|
|
|
class TestPublicationDirectoryManagement:
|
|
"""Test publication directory configuration and management."""
|
|
|
|
def test_default_publication_directory_is_notes_home(self):
|
|
"""Test that default publication directory is ~/Notes/."""
|
|
from markitect.plugins.builtin.markdown_commands import get_publication_directory
|
|
|
|
with patch.dict(os.environ, {}, clear=True):
|
|
# Remove any existing MARKITECT_PUBLICATION_DIR env var
|
|
if 'MARKITECT_PUBLICATION_DIR' in os.environ:
|
|
del os.environ['MARKITECT_PUBLICATION_DIR']
|
|
|
|
pub_dir = get_publication_directory()
|
|
expected = Path.home() / "Notes"
|
|
assert pub_dir == expected
|
|
|
|
def test_environment_variable_overrides_default_publication_directory(self):
|
|
"""Test that MARKITECT_PUBLICATION_DIR environment variable overrides default."""
|
|
from markitect.plugins.builtin.markdown_commands import get_publication_directory
|
|
|
|
custom_dir = "/tmp/custom_publication"
|
|
with patch.dict(os.environ, {'MARKITECT_PUBLICATION_DIR': custom_dir}):
|
|
pub_dir = get_publication_directory()
|
|
assert pub_dir == Path(custom_dir)
|
|
|
|
def test_publication_directory_creation_when_nonexistent(self):
|
|
"""Test that publication directory is created when it doesn't exist."""
|
|
from markitect.plugins.builtin.markdown_commands import ensure_publication_directory
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
pub_dir = Path(tmpdir) / "nonexistent" / "publication"
|
|
assert not pub_dir.exists()
|
|
|
|
ensure_publication_directory(pub_dir)
|
|
assert pub_dir.exists()
|
|
assert pub_dir.is_dir()
|
|
|
|
def test_publication_directory_path_normalization(self):
|
|
"""Test that publication directory paths are properly normalized."""
|
|
from markitect.plugins.builtin.markdown_commands import normalize_publication_path
|
|
|
|
# Test tilde expansion
|
|
path_with_tilde = "~/Documents/Notes"
|
|
normalized = normalize_publication_path(path_with_tilde)
|
|
assert str(normalized).startswith(str(Path.home()))
|
|
|
|
# Test relative path resolution
|
|
relative_path = "./publication"
|
|
normalized = normalize_publication_path(relative_path)
|
|
assert normalized.is_absolute()
|
|
|
|
|
|
class TestSingleFileProcessing:
|
|
"""Test single markdown file processing behavior."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test environment with temporary files."""
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.test_md_file = Path(self.temp_dir) / "test.md"
|
|
self.test_md_file.write_text("# Test Document\n\nThis is a test.")
|
|
|
|
# Set up publication directory
|
|
self.pub_dir = Path(self.temp_dir) / "publication"
|
|
self.pub_dir.mkdir(exist_ok=True)
|
|
|
|
def teardown_method(self):
|
|
"""Clean up test environment."""
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
def test_single_file_default_html_next_to_md(self):
|
|
"""Test that by default, single file HTML is generated next to MD file."""
|
|
from markitect.plugins.builtin.markdown_commands import process_single_file
|
|
|
|
result = process_single_file(
|
|
input_file=self.test_md_file,
|
|
use_publication_dir=False,
|
|
publication_dir=self.pub_dir
|
|
)
|
|
|
|
expected_output = self.test_md_file.with_suffix('.html')
|
|
assert result == expected_output
|
|
# This will fail until implementation is added
|
|
assert expected_output.exists()
|
|
|
|
def test_single_file_with_use_publication_dir_flag(self):
|
|
"""Test single file with --use-publication-dir places HTML in publication directory."""
|
|
from markitect.plugins.builtin.markdown_commands import process_single_file
|
|
|
|
result = process_single_file(
|
|
input_file=self.test_md_file,
|
|
use_publication_dir=True,
|
|
publication_dir=self.pub_dir
|
|
)
|
|
|
|
expected_output = self.pub_dir / "test.html"
|
|
assert result == expected_output
|
|
# This will fail until implementation is added
|
|
assert expected_output.exists()
|
|
|
|
def test_single_file_output_naming_conventions(self):
|
|
"""Test that output file naming follows conventions."""
|
|
from markitect.plugins.builtin.markdown_commands import get_output_filename
|
|
|
|
# Test basic naming
|
|
input_file = Path("document.md")
|
|
output_name = get_output_filename(input_file)
|
|
assert output_name == "document.html"
|
|
|
|
# Test with complex filename
|
|
input_file = Path("my-document_v2.md")
|
|
output_name = get_output_filename(input_file)
|
|
assert output_name == "my-document_v2.html"
|
|
|
|
|
|
class TestDirectoryProcessing:
|
|
"""Test directory tree processing functionality."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test directory structure."""
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.base_dir = Path(self.temp_dir) / "source"
|
|
self.pub_dir = Path(self.temp_dir) / "publication"
|
|
|
|
# Create test directory structure
|
|
self.base_dir.mkdir()
|
|
self.pub_dir.mkdir()
|
|
|
|
# Create test markdown files
|
|
(self.base_dir / "root.md").write_text("# Root Document")
|
|
(self.base_dir / "subdir").mkdir()
|
|
(self.base_dir / "subdir" / "sub.md").write_text("# Sub Document")
|
|
(self.base_dir / "subdir" / "deep").mkdir()
|
|
(self.base_dir / "subdir" / "deep" / "deep.md").write_text("# Deep Document")
|
|
|
|
# Create non-markdown files (should be ignored)
|
|
(self.base_dir / "readme.txt").write_text("Not markdown")
|
|
(self.base_dir / "image.png").write_bytes(b"fake image data")
|
|
|
|
def teardown_method(self):
|
|
"""Clean up test environment."""
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
def test_directory_default_html_in_publication_dir_with_structure(self):
|
|
"""Test that directory processing puts HTML in publication directory with preserved structure."""
|
|
from markitect.plugins.builtin.markdown_commands import process_directory
|
|
|
|
results = process_directory(
|
|
input_dir=self.base_dir,
|
|
use_publication_dir=True,
|
|
publication_dir=self.pub_dir
|
|
)
|
|
|
|
# Check that all expected HTML files are in publication directory
|
|
expected_files = [
|
|
self.pub_dir / "root.html",
|
|
self.pub_dir / "subdir" / "sub.html",
|
|
self.pub_dir / "subdir" / "deep" / "deep.html"
|
|
]
|
|
|
|
assert len(results) == 3
|
|
for expected_file in expected_files:
|
|
assert expected_file in results
|
|
# This will fail until implementation is added
|
|
assert expected_file.exists()
|
|
|
|
def test_directory_with_dont_use_publication_dir_html_next_to_md(self):
|
|
"""Test directory processing with --dont-use-publication-dir places HTML next to MD files."""
|
|
from markitect.plugins.builtin.markdown_commands import process_directory
|
|
|
|
results = process_directory(
|
|
input_dir=self.base_dir,
|
|
use_publication_dir=False,
|
|
publication_dir=self.pub_dir
|
|
)
|
|
|
|
# Check that HTML files are next to their corresponding MD files
|
|
expected_files = [
|
|
self.base_dir / "root.html",
|
|
self.base_dir / "subdir" / "sub.html",
|
|
self.base_dir / "subdir" / "deep" / "deep.html"
|
|
]
|
|
|
|
assert len(results) == 3
|
|
for expected_file in expected_files:
|
|
assert expected_file in results
|
|
# This will fail until implementation is added
|
|
assert expected_file.exists()
|
|
|
|
def test_directory_recursive_traversal(self):
|
|
"""Test that directory processing recursively finds all markdown files."""
|
|
from markitect.plugins.builtin.markdown_commands import find_markdown_files
|
|
|
|
md_files = find_markdown_files(self.base_dir)
|
|
|
|
expected_files = [
|
|
self.base_dir / "root.md",
|
|
self.base_dir / "subdir" / "sub.md",
|
|
self.base_dir / "subdir" / "deep" / "deep.md"
|
|
]
|
|
|
|
assert len(md_files) == 3
|
|
for expected_file in expected_files:
|
|
assert expected_file in md_files
|
|
|
|
def test_directory_structure_preservation(self):
|
|
"""Test that directory structure is preserved in publication directory."""
|
|
from markitect.plugins.builtin.markdown_commands import get_relative_output_path
|
|
|
|
# Test relative path calculation
|
|
source_file = self.base_dir / "subdir" / "deep" / "deep.md"
|
|
relative_path = get_relative_output_path(source_file, self.base_dir, self.pub_dir)
|
|
|
|
expected_path = self.pub_dir / "subdir" / "deep" / "deep.html"
|
|
assert relative_path == expected_path
|
|
|
|
|
|
class TestCLIIntegration:
|
|
"""Test CLI integration with new flags."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test environment."""
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.test_file = Path(self.temp_dir) / "test.md"
|
|
self.test_file.write_text("# Test Document")
|
|
|
|
def teardown_method(self):
|
|
"""Clean up test environment."""
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
def test_md_render_command_has_use_publication_dir_flag(self):
|
|
"""Test that md-render command has --use-publication-dir flag."""
|
|
result = subprocess.run(
|
|
["markitect", "md-render", "--help"],
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
assert "--use-publication-dir" in result.stdout
|
|
assert "publication directory" in result.stdout.lower()
|
|
|
|
def test_md_render_command_has_dont_use_publication_dir_flag(self):
|
|
"""Test that md-render command has --dont-use-publication-dir flag."""
|
|
result = subprocess.run(
|
|
["markitect", "md-render", "--help"],
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
assert "--dont-use-publication-dir" in result.stdout
|
|
|
|
def test_md_render_with_directory_input(self):
|
|
"""Test that md-render command accepts directory as input."""
|
|
# Create a test directory with markdown files
|
|
test_dir = Path(self.temp_dir) / "test_dir"
|
|
test_dir.mkdir()
|
|
(test_dir / "test.md").write_text("# Test")
|
|
|
|
# This should not fail with directory input
|
|
result = subprocess.run(
|
|
["markitect", "md-render", str(test_dir)],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=30
|
|
)
|
|
|
|
# Should not error on directory input
|
|
assert result.returncode == 0 or "directory" in result.stderr
|
|
|
|
def test_environment_variable_integration(self):
|
|
"""Test that MARKITECT_PUBLICATION_DIR environment variable is respected."""
|
|
pub_dir = Path(self.temp_dir) / "custom_pub"
|
|
pub_dir.mkdir()
|
|
|
|
env = os.environ.copy()
|
|
env['MARKITECT_PUBLICATION_DIR'] = str(pub_dir)
|
|
|
|
result = subprocess.run(
|
|
["markitect", "md-render", str(self.test_file), "--use-publication-dir"],
|
|
capture_output=True,
|
|
text=True,
|
|
env=env,
|
|
timeout=30
|
|
)
|
|
|
|
# Should succeed or at least recognize the flag
|
|
# This will fail until implementation is complete
|
|
assert result.returncode == 0
|
|
|
|
|
|
class TestEdgeCases:
|
|
"""Test edge cases and error conditions."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test environment."""
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
|
|
def teardown_method(self):
|
|
"""Clean up test environment."""
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
def test_empty_directory_processing(self):
|
|
"""Test processing of empty directory."""
|
|
from markitect.plugins.builtin.markdown_commands import process_directory
|
|
|
|
empty_dir = Path(self.temp_dir) / "empty"
|
|
empty_dir.mkdir()
|
|
pub_dir = Path(self.temp_dir) / "pub"
|
|
pub_dir.mkdir()
|
|
|
|
results = process_directory(
|
|
input_dir=empty_dir,
|
|
use_publication_dir=True,
|
|
publication_dir=pub_dir
|
|
)
|
|
|
|
assert results == []
|
|
|
|
def test_mixed_content_directory_processing(self):
|
|
"""Test directory with mixed content (markdown and other files)."""
|
|
from markitect.plugins.builtin.markdown_commands import find_markdown_files
|
|
|
|
mixed_dir = Path(self.temp_dir) / "mixed"
|
|
mixed_dir.mkdir()
|
|
|
|
# Create various file types
|
|
(mixed_dir / "document.md").write_text("# Document")
|
|
(mixed_dir / "readme.txt").write_text("Text file")
|
|
(mixed_dir / "image.jpg").write_bytes(b"fake image")
|
|
(mixed_dir / "script.py").write_text("print('hello')")
|
|
(mixed_dir / "another.md").write_text("# Another")
|
|
|
|
md_files = find_markdown_files(mixed_dir)
|
|
|
|
# Should only find markdown files
|
|
assert len(md_files) == 2
|
|
assert all(f.suffix == '.md' for f in md_files)
|
|
|
|
def test_nonexistent_input_error_handling(self):
|
|
"""Test error handling for nonexistent input files/directories."""
|
|
from markitect.plugins.builtin.markdown_commands import process_single_file
|
|
|
|
nonexistent_file = Path(self.temp_dir) / "nonexistent.md"
|
|
pub_dir = Path(self.temp_dir) / "pub"
|
|
|
|
with pytest.raises(FileNotFoundError):
|
|
process_single_file(
|
|
input_file=nonexistent_file,
|
|
use_publication_dir=False,
|
|
publication_dir=pub_dir
|
|
)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
pytest.main([__file__, "-v"]) |