""" Tests for Issue #54: Add content field instruction capabilities This test module implements comprehensive tests for content field instructions that provide guidance for content authors during document generation. Following TDD8 methodology - these tests are written before implementation. """ import json import pytest from pathlib import Path from tempfile import NamedTemporaryFile from click.testing import CliRunner from markitect.cli import cli from markitect.schema_generator import SchemaGenerator from markitect.stub_generator import StubGenerator from markitect.exceptions import InvalidInstructionTypeError class TestIssue54ContentInstructions: """Test suite for content field instruction functionality.""" def setup_method(self): """Set up test fixtures.""" self.schema_generator = SchemaGenerator() self.stub_generator = StubGenerator() self.runner = CliRunner() def test_cli_accepts_include_content_instructions_option(self): """Test that CLI accepts --include-content-instructions option.""" # Arrange markdown_content = """# Architecture Document ## Introduction This section provides an overview of the system. ## Design Principles Core principles guiding the design. """ with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: f.write(markdown_content) temp_file = Path(f.name) try: # Act result = self.runner.invoke(cli, [ 'schema-generate', '--include-content-instructions', str(temp_file) ]) # Assert assert result.exit_code == 0, f"CLI should accept --include-content-instructions option, got: {result.output}" finally: temp_file.unlink() def test_schema_generation_with_content_instructions_includes_instruction_fields(self): """Test that schema generation with content instructions includes instruction fields.""" # Arrange markdown_content = """# Software Architecture Document ## Introduction This section provides an overview of the system architecture. ### Purpose Explain the purpose and goals of this document. ## System Design Describe the overall system design and architecture. """ with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: f.write(markdown_content) temp_file = Path(f.name) try: # Act schema = self.schema_generator.generate_schema_from_file( temp_file, include_content_instructions=True ) # Assert - Schema should contain content instruction fields assert "properties" in schema assert "headings" in schema["properties"] headings = schema["properties"]["headings"]["properties"] # Level 1 heading should have content instructions level_1 = headings["level_1"] items_props = level_1["items"]["properties"] assert "x-markitect-content-instructions" in items_props assert items_props["x-markitect-content-instructions"]["type"] == "string" # Level 2 headings should have content instructions level_2 = headings["level_2"] items_props = level_2["items"]["properties"] assert "x-markitect-content-instructions" in items_props finally: temp_file.unlink() def test_schema_includes_content_instruction_metaschema_extension(self): """Test that schemas with content instructions include metaschema extension.""" # Arrange markdown_content = """# Test Document ## Section A Content for section A. """ with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: f.write(markdown_content) temp_file = Path(f.name) try: # Act schema = self.schema_generator.generate_schema_from_file( temp_file, include_content_instructions=True ) # Assert - Should have metaschema extension assert "x-markitect-content-instructions-enabled" in schema assert schema["x-markitect-content-instructions-enabled"] is True finally: temp_file.unlink() def test_content_instructions_support_different_instruction_types(self): """Test that content instructions can specify different instruction types.""" # Arrange markdown_content = """# Requirements Document ## Functional Requirements List all functional requirements. ## Non-Functional Requirements Describe performance, security, and usability requirements. """ with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: f.write(markdown_content) temp_file = Path(f.name) try: # Act schema = self.schema_generator.generate_schema_from_file( temp_file, include_content_instructions=True, instruction_type="description" ) # Assert - Should have instruction type specified headings = schema["properties"]["headings"]["properties"] level_2 = headings["level_2"] items_props = level_2["items"]["properties"] assert "x-markitect-instruction-type" in items_props assert items_props["x-markitect-instruction-type"]["enum"] == ["description"] finally: temp_file.unlink() def test_content_instructions_integration_with_outline_mode(self): """Test that content instructions work with outline mode.""" # Arrange markdown_content = """# Project Plan ## Phase 1: Planning Planning activities and deliverables. ### Requirements Gathering Gather and document all requirements. ### Architecture Design Design the system architecture. ## Phase 2: Implementation Implementation activities. """ with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: f.write(markdown_content) temp_file = Path(f.name) try: # Act result = self.runner.invoke(cli, [ 'schema-generate', '--mode', 'outline', '--include-content-instructions', '--depth', '2', str(temp_file) ]) # Assert assert result.exit_code == 0 schema = json.loads(result.output) # Should have both outline mode and content instructions extensions assert schema.get("x-markitect-outline-mode") is True assert schema.get("x-markitect-content-instructions-enabled") is True # Should only include headings up to depth 2 headings = schema["properties"]["headings"]["properties"] assert "level_1" in headings assert "level_2" in headings assert "level_3" not in headings # Should have content instructions in the headings level_2 = headings["level_2"] items_props = level_2["items"]["properties"] assert "x-markitect-content-instructions" in items_props finally: temp_file.unlink() def test_content_instructions_for_paragraphs_and_lists(self): """Test that content instructions can be added for paragraphs and lists.""" # Arrange markdown_content = """# User Guide ## Overview This guide explains how to use the system. Some introductory text here. - Feature A - Feature B - Feature C ## Getting Started Follow these steps to get started. """ with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: f.write(markdown_content) temp_file = Path(f.name) try: # Act schema = self.schema_generator.generate_schema_from_file( temp_file, include_content_instructions=True ) # Assert - Paragraphs should have content instructions if "paragraphs" in schema["properties"]: paragraphs_schema = schema["properties"]["paragraphs"] items_props = paragraphs_schema["items"]["properties"] assert "x-markitect-content-instructions" in items_props # Lists should have content instructions if "lists" in schema["properties"]: lists_schema = schema["properties"]["lists"] items_props = lists_schema["items"]["properties"] assert "x-markitect-content-instructions" in items_props finally: temp_file.unlink() def test_stub_generation_includes_content_instruction_placeholders(self): """Test that stub generation includes content instruction placeholders.""" # Arrange schema = { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "title": "Test Schema", "x-markitect-content-instructions-enabled": True, "properties": { "headings": { "type": "object", "properties": { "level_1": { "type": "array", "items": { "type": "object", "properties": { "content": {"type": "string"}, "x-markitect-content-instructions": { "type": "string", "const": "Provide the main title of the document" } } } }, "level_2": { "type": "array", "items": { "type": "object", "properties": { "content": {"type": "string"}, "x-markitect-content-instructions": { "type": "string", "const": "Describe each major section of the document" } } } } } } } } # Act stub_content = self.stub_generator.generate_stub_from_schema(schema) # Assert - Stub should include instruction placeholders assert "Provide the main title of the document" in stub_content assert "Describe each major section of the document" in stub_content def test_cli_supports_instruction_type_parameter(self): """Test that CLI supports --instruction-type parameter.""" # Arrange markdown_content = """# API Documentation ## Authentication How to authenticate with the API. ## Endpoints Available API endpoints. """ with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: f.write(markdown_content) temp_file = Path(f.name) try: # Act result = self.runner.invoke(cli, [ 'schema-generate', '--include-content-instructions', '--instruction-type', 'example', str(temp_file) ]) # Assert assert result.exit_code == 0 schema = json.loads(result.output) # Check that instruction type is set correctly headings = schema["properties"]["headings"]["properties"] level_1 = headings["level_1"] items_props = level_1["items"]["properties"] assert items_props["x-markitect-instruction-type"]["enum"] == ["example"] finally: temp_file.unlink() def test_backward_compatibility_without_content_instructions(self): """Test that existing behavior is maintained when content instructions are not enabled.""" # Arrange markdown_content = """# Test Document ## Section One Content here. """ with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: f.write(markdown_content) temp_file = Path(f.name) try: # Act - Generate schema without content instructions (default behavior) schema = self.schema_generator.generate_schema_from_file(temp_file) # Assert - Should NOT have content instruction fields headings = schema["properties"]["headings"]["properties"] level_1 = headings["level_1"] items_props = level_1["items"]["properties"] # Should not have content instruction fields assert "x-markitect-content-instructions" not in items_props assert "x-markitect-instruction-type" not in items_props # Should NOT have content instructions extension assert "x-markitect-content-instructions-enabled" not in schema finally: temp_file.unlink() def test_content_instructions_with_heading_text_capture_integration(self): """Test that content instructions work with heading text capture.""" # Arrange markdown_content = """# Architecture Overview ## System Components Core system components and their responsibilities. ## Data Flow How data flows through the system. """ with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: f.write(markdown_content) temp_file = Path(f.name) try: # Act schema = self.schema_generator.generate_schema_from_file( temp_file, capture_heading_text=True, include_content_instructions=True ) # Assert - Should have both heading text capture and content instructions assert schema.get("x-markitect-heading-text-capture") is True assert schema.get("x-markitect-content-instructions-enabled") is True # Headings should have both enum constraints and content instructions headings = schema["properties"]["headings"]["properties"] level_1 = headings["level_1"] items_props = level_1["items"]["properties"] # Should have enum constraint from heading text capture assert "enum" in items_props["content"] assert items_props["content"]["enum"] == ["Architecture Overview"] # Should also have content instructions assert "x-markitect-content-instructions" in items_props finally: temp_file.unlink() def test_cli_help_includes_content_instructions_options(self): """Test that CLI help includes documentation for content instruction options.""" # Act result = self.runner.invoke(cli, ['schema-generate', '--help']) # Assert assert result.exit_code == 0 help_text = result.output assert "--include-content-instructions" in help_text assert "--instruction-type" in help_text assert "content instructions" in help_text or "content guidance" in help_text def test_instruction_type_validation(self): """Test that --instruction-type parameter validates input correctly.""" # Arrange markdown_content = """# Test Document ## Section Content here. """ with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: f.write(markdown_content) temp_file = Path(f.name) try: # Act - Test invalid instruction type result = self.runner.invoke(cli, [ 'schema-generate', '--include-content-instructions', '--instruction-type', 'invalid-type', str(temp_file) ]) # Assert assert result.exit_code != 0 assert "Invalid instruction type" in result.output or "invalid-type" in result.output finally: temp_file.unlink() def test_content_instructions_generate_appropriate_default_text(self): """Test that content instructions generate appropriate default guidance text.""" # Arrange markdown_content = """# Development Guide ## Prerequisites System requirements and prerequisites. ### Software Requirements Required software and versions. ## Installation Step-by-step installation instructions. ## Configuration How to configure the system. """ with NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: f.write(markdown_content) temp_file = Path(f.name) try: # Act schema = self.schema_generator.generate_schema_from_file( temp_file, include_content_instructions=True, instruction_type="description" ) # Assert - Content instructions should contain appropriate guidance headings = schema["properties"]["headings"]["properties"] # Level 1 should have appropriate instructions level_1 = headings["level_1"] items_props = level_1["items"]["properties"] instructions = items_props["x-markitect-content-instructions"]["const"] assert len(instructions) > 0 assert isinstance(instructions, str) # Instructions should be contextually appropriate # (implementation will determine specific text) finally: temp_file.unlink()