356 lines
13 KiB
Python
356 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)
|