Files
skill-seekers-reference/tests/test_api_reference_builder.py
Pablo Estevez 5ed767ff9a run ruff
2026-01-17 17:29:21 +00:00

347 lines
13 KiB
Python

#!/usr/bin/env python3
"""
Tests for api_reference_builder.py - Markdown API documentation generation.
Test Coverage:
- Class formatting
- Function formatting
- Parameter table generation
- Markdown output structure
- Integration with code analysis results
"""
import os
import shutil
import sys
import tempfile
import unittest
from pathlib import Path
# Add src to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from skill_seekers.cli.api_reference_builder import APIReferenceBuilder
class TestAPIReferenceBuilder(unittest.TestCase):
"""Tests for API reference builder"""
def setUp(self):
"""Set up test environment"""
self.temp_dir = tempfile.mkdtemp()
self.output_dir = Path(self.temp_dir) / "api_reference"
def tearDown(self):
"""Clean up test environment"""
shutil.rmtree(self.temp_dir, ignore_errors=True)
def test_class_formatting(self):
"""Test markdown formatting for class signatures."""
code_analysis = {
"files": [
{
"file": "test.py",
"language": "Python",
"classes": [
{
"name": "Calculator",
"docstring": "A simple calculator class.",
"base_classes": ["object"],
"methods": [
{
"name": "add",
"parameters": [
{"name": "a", "type_hint": "int", "default": None},
{"name": "b", "type_hint": "int", "default": None},
],
"return_type": "int",
"docstring": "Add two numbers.",
"is_async": False,
"is_method": True,
"decorators": [],
}
],
}
],
"functions": [],
}
]
}
builder = APIReferenceBuilder(code_analysis)
generated = builder.build_reference(self.output_dir)
# Verify file was generated
self.assertEqual(len(generated), 1)
output_file = list(generated.values())[0]
self.assertTrue(output_file.exists())
# Verify content
content = output_file.read_text()
self.assertIn("### Calculator", content)
self.assertIn("A simple calculator class", content)
self.assertIn("**Inherits from**: object", content)
self.assertIn("##### add", content)
self.assertIn("Add two numbers", content)
def test_function_formatting(self):
"""Test markdown formatting for function signatures."""
code_analysis = {
"files": [
{
"file": "utils.py",
"language": "Python",
"classes": [],
"functions": [
{
"name": "calculate_sum",
"parameters": [{"name": "numbers", "type_hint": "list", "default": None}],
"return_type": "int",
"docstring": "Calculate sum of numbers.",
"is_async": False,
"is_method": False,
"decorators": [],
}
],
}
]
}
builder = APIReferenceBuilder(code_analysis)
generated = builder.build_reference(self.output_dir)
# Verify content
output_file = list(generated.values())[0]
content = output_file.read_text()
self.assertIn("## Functions", content)
self.assertIn("### calculate_sum", content)
self.assertIn("Calculate sum of numbers", content)
self.assertIn("**Returns**: `int`", content)
def test_parameter_table_generation(self):
"""Test parameter table formatting."""
code_analysis = {
"files": [
{
"file": "test.py",
"language": "Python",
"classes": [],
"functions": [
{
"name": "create_user",
"parameters": [
{"name": "name", "type_hint": "str", "default": None},
{"name": "age", "type_hint": "int", "default": "18"},
{"name": "active", "type_hint": "bool", "default": "True"},
],
"return_type": "dict",
"docstring": "Create a user object.",
"is_async": False,
"is_method": False,
"decorators": [],
}
],
}
]
}
builder = APIReferenceBuilder(code_analysis)
generated = builder.build_reference(self.output_dir)
# Verify parameter table
output_file = list(generated.values())[0]
content = output_file.read_text()
self.assertIn("**Parameters**:", content)
self.assertIn("| Name | Type | Default | Description |", content)
self.assertIn("| name | str | - |", content) # Parameters with no default show "-"
self.assertIn("| age | int | 18 |", content)
self.assertIn("| active | bool | True |", content)
def test_markdown_output_structure(self):
"""Test overall markdown document structure."""
code_analysis = {
"files": [
{
"file": "module.py",
"language": "Python",
"classes": [{"name": "TestClass", "docstring": "Test class.", "base_classes": [], "methods": []}],
"functions": [
{
"name": "test_func",
"parameters": [],
"return_type": None,
"docstring": "Test function.",
"is_async": False,
"is_method": False,
"decorators": [],
}
],
}
]
}
builder = APIReferenceBuilder(code_analysis)
generated = builder.build_reference(self.output_dir)
# Verify structure
output_file = list(generated.values())[0]
content = output_file.read_text()
# Check header
self.assertIn("# API Reference: module.py", content)
self.assertIn("**Language**: Python", content)
self.assertIn("**Source**: `module.py`", content)
# Check sections in order
classes_pos = content.find("## Classes")
functions_pos = content.find("## Functions")
self.assertNotEqual(classes_pos, -1)
self.assertNotEqual(functions_pos, -1)
self.assertLess(classes_pos, functions_pos)
def test_integration_with_code_analyzer(self):
"""Test integration with actual code analyzer output format."""
# Simulate real code analyzer output
code_analysis = {
"files": [
{
"file": "calculator.py",
"language": "Python",
"classes": [
{
"name": "Calculator",
"base_classes": [],
"methods": [
{
"name": "add",
"parameters": [
{"name": "a", "type_hint": "float", "default": None},
{"name": "b", "type_hint": "float", "default": None},
],
"return_type": "float",
"docstring": "Add two numbers.",
"decorators": [],
"is_async": False,
"is_method": True,
}
],
"docstring": "Calculator class.",
"line_number": 1,
}
],
"functions": [],
},
{
"file": "utils.js",
"language": "JavaScript",
"classes": [],
"functions": [
{
"name": "formatDate",
"parameters": [{"name": "date", "type_hint": None, "default": None}],
"return_type": None,
"docstring": None,
"is_async": False,
"is_method": False,
"decorators": [],
}
],
},
]
}
builder = APIReferenceBuilder(code_analysis)
generated = builder.build_reference(self.output_dir)
# Verify multiple files generated
self.assertEqual(len(generated), 2)
# Verify filenames
filenames = [f.name for f in generated.values()]
self.assertIn("calculator.md", filenames)
self.assertIn("utils.md", filenames)
# Verify Python file content
py_file = next(f for f in generated.values() if f.name == "calculator.md")
py_content = py_file.read_text()
self.assertIn("Calculator class", py_content)
self.assertIn("add(a: float, b: float) → float", py_content)
# Verify JavaScript file content
js_file = next(f for f in generated.values() if f.name == "utils.md")
js_content = js_file.read_text()
self.assertIn("formatDate", js_content)
self.assertIn("**Language**: JavaScript", js_content)
def test_async_function_indicator(self):
"""Test that async functions are marked in output."""
code_analysis = {
"files": [
{
"file": "async_utils.py",
"language": "Python",
"classes": [],
"functions": [
{
"name": "fetch_data",
"parameters": [{"name": "url", "type_hint": "str", "default": None}],
"return_type": "dict",
"docstring": "Fetch data from URL.",
"is_async": True,
"is_method": False,
"decorators": [],
}
],
}
]
}
builder = APIReferenceBuilder(code_analysis)
generated = builder.build_reference(self.output_dir)
# Verify async indicator
output_file = list(generated.values())[0]
content = output_file.read_text()
self.assertIn("**Async function**", content)
self.assertIn("fetch_data", content)
def test_empty_analysis_skipped(self):
"""Test that files with no analysis are skipped."""
code_analysis = {
"files": [
{"file": "empty.py", "language": "Python", "classes": [], "functions": []},
{
"file": "valid.py",
"language": "Python",
"classes": [],
"functions": [
{
"name": "test",
"parameters": [],
"return_type": None,
"docstring": None,
"is_async": False,
"is_method": False,
"decorators": [],
}
],
},
]
}
builder = APIReferenceBuilder(code_analysis)
generated = builder.build_reference(self.output_dir)
# Only valid.py should be generated
self.assertEqual(len(generated), 1)
self.assertIn("valid.py", list(generated.keys())[0])
if __name__ == "__main__":
# Run tests with verbose output
unittest.main(verbosity=2)