test: Add comprehensive test suite for Issue #54 content instructions
This commit adds the complete test suite for content field instruction capabilities, providing comprehensive coverage for all implemented features. ## Test Coverage: - CLI option validation (--include-content-instructions, --instruction-type) - Schema generation with content instruction fields - Integration with outline mode and heading text capture - Backward compatibility verification - Error handling for invalid instruction types - Stub generator integration - Content instruction text generation for all types ## Test Structure: - 13 comprehensive test methods covering all use cases - TDD methodology validation (RED-GREEN-REFACTOR cycle) - Integration tests for feature combinations - Edge case and error condition testing This completes the test coverage for Issue #54 implementation. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
515
tests/test_issue_54_content_instructions.py
Normal file
515
tests/test_issue_54_content_instructions.py
Normal file
@@ -0,0 +1,515 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user