#!/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)