diff --git a/tests/test_indexers.py b/tests/test_indexers.py new file mode 100644 index 0000000..146828f --- /dev/null +++ b/tests/test_indexers.py @@ -0,0 +1,342 @@ +"""Tests for the indexers.""" + +import tempfile +from pathlib import Path + +import pytest + +from src.indexer.openapi import OpenAPIIndexer +from src.indexer.readme import READMEIndexer +from src.indexer.code import CodeIndexer +from src.models.document import SourceType + + +class TestOpenAPIIndexer: + """Tests for the OpenAPI indexer.""" + + @pytest.fixture + def sample_openapi_yaml(self): + """Create a sample OpenAPI spec for testing.""" + content = """ +openapi: "3.0.0" +info: + title: Test API + version: "1.0.0" + description: A test API for unit testing + +paths: + /users: + get: + summary: List users + description: Get a list of all users + operationId: listUsers + tags: + - users + parameters: + - name: limit + in: query + schema: + type: integer + description: Maximum number of users + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: array + items: + type: object + post: + summary: Create user + operationId: createUser + tags: + - users + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + email: + type: string + responses: + '201': + description: User created + + /users/{userId}: + get: + summary: Get user + operationId: getUser + parameters: + - name: userId + in: path + required: true + schema: + type: integer + +components: + schemas: + User: + type: object + properties: + id: + type: integer + name: + type: string + required: + - id + - name +""" + return content + + def test_index_openapi_yaml(self, tmp_path, sample_openapi_yaml): + """Test indexing a YAML OpenAPI spec.""" + file_path = tmp_path / "openapi.yaml" + file_path.write_text(sample_openapi_yaml) + + indexer = OpenAPIIndexer() + documents = indexer.index(tmp_path, recursive=False) + + assert len(documents) > 0 + assert all(doc.source_type == SourceType.OPENAPI for doc in documents) + + titles = [doc.title for doc in documents] + assert any("Test API" in title for title in titles) + assert any("GET" in title or "/users" in title for title in titles) + + def test_index_openapi_json(self, tmp_path): + """Test indexing a JSON OpenAPI spec.""" + content = """{ + "openapi": "3.0.0", + "info": { + "title": "JSON API", + "version": "1.0.0" + }, + "paths": {} +}""" + file_path = tmp_path / "openapi.json" + file_path.write_text(content) + + indexer = OpenAPIIndexer() + documents = indexer.index(tmp_path, recursive=False) + + assert len(documents) > 0 + + def test_unsupported_file(self, tmp_path): + """Test that unsupported files are ignored.""" + file_path = tmp_path / "test.txt" + file_path.write_text("This is not an OpenAPI spec") + + indexer = OpenAPIIndexer() + documents = indexer.index(tmp_path, recursive=False) + + assert len(documents) == 0 + + +class TestREADMEIndexer: + """Tests for the README indexer.""" + + @pytest.fixture + def sample_readme(self): + """Create a sample README for testing.""" + content = """# Project Documentation + +This is a sample project for testing the README indexer. + +## Installation + +To install the project, run: + +```bash +pip install myproject +``` + +## Usage + +The main function can be called as follows: + +```python +from myproject import main +main() +``` + +## Configuration + +You can configure the project using environment variables: + +- `API_KEY`: Your API key +- `DEBUG`: Enable debug mode + +## API Reference + +### Endpoints + +- `GET /users` - List all users +- `POST /users` - Create a new user +""" + return content + + def test_index_readme(self, tmp_path, sample_readme): + """Test indexing a README file.""" + file_path = tmp_path / "README.md" + file_path.write_text(sample_readme) + + indexer = READMEIndexer() + documents = indexer.index(tmp_path, recursive=False) + + assert len(documents) > 0 + assert all(doc.source_type == SourceType.README for doc in documents) + + titles = [doc.title for doc in documents] + assert any("Project Documentation" in title for title in titles) + + def test_section_extraction(self, tmp_path, sample_readme): + """Test that sections are properly extracted.""" + file_path = tmp_path / "README.md" + file_path.write_text(sample_readme) + + indexer = READMEIndexer() + documents = indexer.index(tmp_path, recursive=False) + + assert len(documents) >= 1 + content = documents[0].content + assert "Installation" in content + assert "Usage" in content + + def test_unsupported_extension(self, tmp_path): + """Test that unsupported extensions are ignored.""" + file_path = tmp_path / "readme.xyz" + file_path.write_text("# Readme") + + indexer = READMEIndexer() + documents = indexer.index(tmp_path, recursive=False) + + assert len(documents) == 0 + + +class TestCodeIndexer: + """Tests for the code indexer.""" + + @pytest.fixture + def sample_python(self): + """Create a sample Python file for testing.""" + content = '''"""Sample module for testing.""" + + +def add_numbers(a, b): + """Add two numbers together. + + Args: + a: First number + b: Second number + + Returns: + The sum of a and b + """ + return a + b + + +class Calculator: + """A simple calculator class. + + This class provides basic arithmetic operations. + """ + + def __init__(self, initial_value=0): + """Initialize the calculator. + + Args: + initial_value: Starting value + """ + self.value = initial_value + + def add(self, number): + """Add a number to the current value. + + Args: + number: Number to add + """ + self.value += number +''' + return content + + def test_index_python(self, tmp_path, sample_python): + """Test indexing a Python file.""" + file_path = tmp_path / "sample.py" + file_path.write_text(sample_python) + + indexer = CodeIndexer() + documents = indexer.index(tmp_path, recursive=False) + + assert len(documents) > 0 + assert all(doc.source_type == SourceType.CODE for doc in documents) + + titles = [doc.title for doc in documents] + assert any("add_numbers" in title for title in titles) + assert any("Calculator" in title for title in titles) + + def test_function_docstring(self, tmp_path, sample_python): + """Test that function docstrings are extracted.""" + file_path = tmp_path / "sample.py" + file_path.write_text(sample_python) + + indexer = CodeIndexer() + documents = indexer.index(tmp_path, recursive=False) + + add_doc = [d for d in documents if "add_numbers" in d.title] + if add_doc: + assert "Add two numbers" in add_doc[0].content + + def test_class_docstring(self, tmp_path, sample_python): + """Test that class docstrings are extracted.""" + file_path = tmp_path / "sample.py" + file_path.write_text(sample_python) + + indexer = CodeIndexer() + documents = indexer.index(tmp_path, recursive=False) + + calc_doc = [d for d in documents if "Calculator" in d.title] + if calc_doc: + assert "simple calculator" in calc_doc[0].content.lower() + + def test_jsdoc_extraction(self, tmp_path): + """Test JSDoc comment extraction.""" + content = ''' +/** + * Multiplies two numbers. + * @param {number} a - First number + * @param {number} b - Second number + * @returns {number} The product of a and b + */ +function multiply(a, b) { + return a * b; +} + +/** + * A Greeter class. + * @class + */ +class Greeter { + /** + * Create a greeter. + * @param {string} name - Name to greet + */ + constructor(name) { + this.name = name; + } +} +''' + file_path = tmp_path / "sample.js" + file_path.write_text(content) + + indexer = CodeIndexer() + documents = indexer.index(tmp_path, recursive=False) + + func_doc = [d for d in documents if "multiply" in d.title] + if func_doc: + assert "Multiplies two numbers" in func_doc[0].content + assert "number" in func_doc[0].content