Some checks failed
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 / performance-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
Establishes robust testing framework with clean architecture patterns: ## Phase 1: Test Infrastructure Foundation - Global test configuration with pytest.ini and conftest.py - Isolated test workspaces and environment management - Comprehensive fixture library for all test types - Test requirements and dependency management ## Phase 2: Advanced Testing Patterns - Test builders using builder pattern for domain objects - Mock factories for repositories, services, and configs - API response builders for external system simulation - Enhanced unit tests with proper mocking and isolation ## Phase 3: Test Performance and Quality - Performance testing framework with benchmarks - Memory usage monitoring and leak detection - Custom assertions for domain-specific validation - Parametrized testing for comprehensive coverage ## Phase 4: CI/CD Integration - GitHub Actions workflow for automated testing - Multi-stage testing: unit → integration → e2e → performance - Code quality checks with flake8, mypy, black, isort - Security scanning with safety and bandit ## Testing Architecture Benefits ✅ 100+ new test infrastructure components ✅ Standardized test organization (unit/integration/e2e) ✅ Mock-based testing with no external dependencies ✅ Performance regression detection ✅ Comprehensive fixture library ✅ CI/CD pipeline with quality gates The testing framework supports the domain logic separation and provides a solid foundation for maintaining high code quality as the system evolves. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
302 lines
10 KiB
Python
302 lines
10 KiB
Python
"""
|
|
Markdown document builders and sample generators for testing.
|
|
"""
|
|
|
|
from typing import Dict, List, Optional
|
|
import random
|
|
import string
|
|
|
|
|
|
class MarkdownDocumentBuilder:
|
|
"""Builder pattern for creating test markdown documents."""
|
|
|
|
def __init__(self):
|
|
self.content_parts: List[str] = []
|
|
self.metadata: Dict[str, str] = {}
|
|
|
|
def with_heading(self, text: str, level: int = 1) -> "MarkdownDocumentBuilder":
|
|
"""Add a heading to the document."""
|
|
if level < 1 or level > 6:
|
|
raise ValueError("Heading level must be between 1 and 6")
|
|
|
|
heading_marker = "#" * level
|
|
self.content_parts.append(f"{heading_marker} {text}")
|
|
return self
|
|
|
|
def with_paragraph(self, text: str) -> "MarkdownDocumentBuilder":
|
|
"""Add a paragraph to the document."""
|
|
self.content_parts.append(text)
|
|
return self
|
|
|
|
def with_list(self, items: List[str], ordered: bool = False) -> "MarkdownDocumentBuilder":
|
|
"""Add a list to the document."""
|
|
if ordered:
|
|
list_items = [f"{i+1}. {item}" for i, item in enumerate(items)]
|
|
else:
|
|
list_items = [f"- {item}" for item in items]
|
|
|
|
self.content_parts.append("\n".join(list_items))
|
|
return self
|
|
|
|
def with_code_block(self, code: str, language: str = "python") -> "MarkdownDocumentBuilder":
|
|
"""Add a code block to the document."""
|
|
self.content_parts.append(f"```{language}\n{code}\n```")
|
|
return self
|
|
|
|
def with_link(self, text: str, url: str) -> "MarkdownDocumentBuilder":
|
|
"""Add a link to the document."""
|
|
self.content_parts.append(f"[{text}]({url})")
|
|
return self
|
|
|
|
def with_metadata(self, key: str, value: str) -> "MarkdownDocumentBuilder":
|
|
"""Add metadata (front matter) to the document."""
|
|
self.metadata[key] = value
|
|
return self
|
|
|
|
def with_table(self, headers: List[str], rows: List[List[str]]) -> "MarkdownDocumentBuilder":
|
|
"""Add a table to the document."""
|
|
table_lines = []
|
|
|
|
# Header row
|
|
table_lines.append("| " + " | ".join(headers) + " |")
|
|
|
|
# Separator row
|
|
table_lines.append("| " + " | ".join(["-" * len(header) for header in headers]) + " |")
|
|
|
|
# Data rows
|
|
for row in rows:
|
|
table_lines.append("| " + " | ".join(row) + " |")
|
|
|
|
self.content_parts.append("\n".join(table_lines))
|
|
return self
|
|
|
|
def with_blockquote(self, text: str) -> "MarkdownDocumentBuilder":
|
|
"""Add a blockquote to the document."""
|
|
quote_lines = [f"> {line}" for line in text.split("\n")]
|
|
self.content_parts.append("\n".join(quote_lines))
|
|
return self
|
|
|
|
def build(self) -> str:
|
|
"""Build the final markdown document."""
|
|
content = "\n\n".join(self.content_parts)
|
|
|
|
if self.metadata:
|
|
metadata_lines = [f"{k}: {v}" for k, v in self.metadata.items()]
|
|
content = "---\n" + "\n".join(metadata_lines) + "\n---\n\n" + content
|
|
|
|
return content
|
|
|
|
|
|
class LargeMarkdownGenerator:
|
|
"""Generator for creating large markdown documents for performance testing."""
|
|
|
|
def __init__(self, seed: Optional[int] = None):
|
|
self.random = random.Random(seed)
|
|
|
|
def generate_document(self, size: str = "1mb") -> str:
|
|
"""Generate a large markdown document of specified size."""
|
|
size_bytes = self._parse_size(size)
|
|
builder = MarkdownDocumentBuilder()
|
|
|
|
# Add metadata
|
|
builder.with_metadata("title", "Large Test Document")
|
|
builder.with_metadata("author", "Test Generator")
|
|
builder.with_metadata("size", size)
|
|
|
|
# Add content until we reach target size
|
|
current_size = 0
|
|
section_count = 0
|
|
|
|
while current_size < size_bytes:
|
|
section_count += 1
|
|
section_title = f"Section {section_count}"
|
|
builder.with_heading(section_title, level=2)
|
|
|
|
# Add paragraphs
|
|
for _ in range(self.random.randint(3, 8)):
|
|
paragraph = self._generate_paragraph()
|
|
builder.with_paragraph(paragraph)
|
|
current_size += len(paragraph) + 2 # +2 for newlines
|
|
|
|
if current_size >= size_bytes:
|
|
break
|
|
|
|
# Add a list occasionally
|
|
if self.random.random() < 0.3:
|
|
items = [self._generate_sentence() for _ in range(self.random.randint(3, 7))]
|
|
builder.with_list(items)
|
|
current_size += sum(len(item) for item in items) + len(items) * 3 # Approximate
|
|
|
|
# Add a code block occasionally
|
|
if self.random.random() < 0.2:
|
|
code = self._generate_code_block()
|
|
builder.with_code_block(code)
|
|
current_size += len(code) + 10 # +10 for code block markers
|
|
|
|
return builder.build()
|
|
|
|
def _parse_size(self, size: str) -> int:
|
|
"""Parse size string (e.g., '1mb', '500kb') to bytes."""
|
|
size = size.lower()
|
|
if size.endswith("kb"):
|
|
return int(size[:-2]) * 1024
|
|
elif size.endswith("mb"):
|
|
return int(size[:-2]) * 1024 * 1024
|
|
elif size.endswith("gb"):
|
|
return int(size[:-2]) * 1024 * 1024 * 1024
|
|
else:
|
|
return int(size)
|
|
|
|
def _generate_paragraph(self) -> str:
|
|
"""Generate a paragraph of random text."""
|
|
sentences = []
|
|
for _ in range(self.random.randint(3, 8)):
|
|
sentences.append(self._generate_sentence())
|
|
return " ".join(sentences)
|
|
|
|
def _generate_sentence(self) -> str:
|
|
"""Generate a random sentence."""
|
|
words = []
|
|
for _ in range(self.random.randint(5, 15)):
|
|
words.append(self._generate_word())
|
|
|
|
sentence = " ".join(words).capitalize()
|
|
return sentence + "."
|
|
|
|
def _generate_word(self) -> str:
|
|
"""Generate a random word."""
|
|
length = self.random.randint(3, 12)
|
|
return "".join(self.random.choices(string.ascii_lowercase, k=length))
|
|
|
|
def _generate_code_block(self) -> str:
|
|
"""Generate a random code block."""
|
|
lines = []
|
|
for _ in range(self.random.randint(5, 15)):
|
|
line = self._generate_code_line()
|
|
lines.append(line)
|
|
return "\n".join(lines)
|
|
|
|
def _generate_code_line(self) -> str:
|
|
"""Generate a line of code-like text."""
|
|
templates = [
|
|
"def {func_name}({params}):",
|
|
" return {expression}",
|
|
"if {condition}:",
|
|
" {statement}",
|
|
"# {comment}",
|
|
"class {class_name}:",
|
|
" self.{attr} = {value}",
|
|
"import {module}",
|
|
"from {module} import {name}",
|
|
]
|
|
|
|
template = self.random.choice(templates)
|
|
variables = {
|
|
"func_name": self._generate_word(),
|
|
"params": ", ".join([self._generate_word() for _ in range(self.random.randint(0, 3))]),
|
|
"expression": f"{self._generate_word()}({self._generate_word()})",
|
|
"condition": f"{self._generate_word()} == {self.random.randint(1, 100)}",
|
|
"statement": f"{self._generate_word()} = {self.random.randint(1, 100)}",
|
|
"comment": " ".join([self._generate_word() for _ in range(self.random.randint(2, 6))]),
|
|
"class_name": self._generate_word().capitalize(),
|
|
"attr": self._generate_word(),
|
|
"value": str(self.random.randint(1, 100)),
|
|
"module": self._generate_word(),
|
|
"name": self._generate_word(),
|
|
}
|
|
|
|
return template.format(**variables)
|
|
|
|
|
|
# Pre-built sample documents
|
|
SAMPLE_SIMPLE_DOCUMENT = """# Simple Document
|
|
|
|
This is a simple test document.
|
|
|
|
## Features
|
|
|
|
- Feature 1
|
|
- Feature 2
|
|
- Feature 3
|
|
"""
|
|
|
|
SAMPLE_COMPLEX_DOCUMENT = (
|
|
MarkdownDocumentBuilder()
|
|
.with_metadata("title", "Complex Test Document")
|
|
.with_metadata("author", "Test Suite")
|
|
.with_metadata("tags", "test, complex, sample")
|
|
.with_heading("Complex Test Document")
|
|
.with_paragraph("This is a complex test document with various markdown features.")
|
|
.with_heading("Table of Contents", level=2)
|
|
.with_list([
|
|
"Introduction",
|
|
"Features",
|
|
"Examples",
|
|
"Conclusion"
|
|
], ordered=True)
|
|
.with_heading("Introduction", level=2)
|
|
.with_paragraph("This document demonstrates various markdown features.")
|
|
.with_blockquote("This is an important note about the document.")
|
|
.with_heading("Features", level=2)
|
|
.with_list([
|
|
"**Bold text**",
|
|
"*Italic text*",
|
|
"`Code inline`",
|
|
"[Links](https://example.com)"
|
|
])
|
|
.with_heading("Code Example", level=3)
|
|
.with_code_block('''def hello_world():
|
|
"""Print hello world message."""
|
|
print("Hello, World!")
|
|
return "success"''')
|
|
.with_heading("Data Table", level=3)
|
|
.with_table(
|
|
["Name", "Type", "Description"],
|
|
[
|
|
["title", "string", "Document title"],
|
|
["author", "string", "Document author"],
|
|
["tags", "array", "Document tags"]
|
|
]
|
|
)
|
|
.with_heading("Conclusion", level=2)
|
|
.with_paragraph("This document shows the power of markdown for documentation.")
|
|
.build()
|
|
)
|
|
|
|
SAMPLE_TECHNICAL_DOCUMENT = (
|
|
MarkdownDocumentBuilder()
|
|
.with_metadata("title", "API Documentation")
|
|
.with_metadata("version", "1.0.0")
|
|
.with_metadata("category", "technical")
|
|
.with_heading("API Documentation")
|
|
.with_paragraph("This document describes the REST API endpoints.")
|
|
.with_heading("Authentication", level=2)
|
|
.with_paragraph("All API requests require authentication via API key.")
|
|
.with_code_block('''curl -H "Authorization: Bearer YOUR_API_KEY" \\
|
|
https://api.example.com/v1/endpoint''', "bash")
|
|
.with_heading("Endpoints", level=2)
|
|
.with_heading("GET /users", level=3)
|
|
.with_paragraph("Retrieve a list of users.")
|
|
.with_table(
|
|
["Parameter", "Type", "Required", "Description"],
|
|
[
|
|
["limit", "integer", "No", "Maximum number of results"],
|
|
["offset", "integer", "No", "Number of results to skip"],
|
|
["filter", "string", "No", "Filter criteria"]
|
|
]
|
|
)
|
|
.with_heading("Response", level=4)
|
|
.with_code_block('''{
|
|
"users": [
|
|
{
|
|
"id": 1,
|
|
"name": "John Doe",
|
|
"email": "john@example.com"
|
|
}
|
|
],
|
|
"total": 1,
|
|
"offset": 0,
|
|
"limit": 10
|
|
}''', "json")
|
|
.build()
|
|
) |