diff --git a/src/server/main.py b/src/server/main.py index 50fabc92..20de4403 100644 --- a/src/server/main.py +++ b/src/server/main.py @@ -1,6 +1,7 @@ """ Main module for the FastAPI application. """ import os +from pathlib import Path from api_analytics.fastapi import Analytics from dotenv import load_dotenv @@ -24,8 +25,11 @@ # Register the custom exception handler for rate limits app.add_exception_handler(RateLimitExceeded, rate_limit_exception_handler) -# Mount static files to serve CSS, JS, and other static assets -app.mount("/static", StaticFiles(directory="static"), name="static") + +# Mount static files dynamically to serve CSS, JS, and other static assets +static_dir = Path(__file__).parent.parent / "static" +app.mount("/static", StaticFiles(directory=static_dir), name="static") + # Set up API analytics middleware if an API key is provided if app_analytics_key := os.getenv("API_ANALYTICS_KEY"): diff --git a/tests/test_flow_integration.py b/tests/test_flow_integration.py new file mode 100644 index 00000000..8fedeff5 --- /dev/null +++ b/tests/test_flow_integration.py @@ -0,0 +1,159 @@ +""" +Integration tests for GitIngest. +These tests cover core functionalities, edge cases, and concurrency handling. +""" + +import shutil +from concurrent.futures import ThreadPoolExecutor +from pathlib import Path +from unittest.mock import patch + +import pytest +from fastapi.testclient import TestClient + +from src.server.main import app + +BASE_DIR = Path(__file__).resolve().parent.parent +TEMPLATE_DIR = BASE_DIR / "src" / "templates" + + +@pytest.fixture(scope="module") +def test_client(): + """Create a test client fixture.""" + with TestClient(app) as client_instance: + client_instance.headers.update({"Host": "localhost"}) + yield client_instance + + +@pytest.fixture(scope="module", autouse=True) +def mock_static_files(): + """Mock the static file mount to avoid directory errors.""" + with patch("src.server.main.StaticFiles") as mock_static: + mock_static.return_value = None # Mocks the StaticFiles response + yield mock_static + + +@pytest.fixture(scope="module", autouse=True) +def mock_templates(): + """Mock Jinja2 template rendering to bypass actual file loading.""" + with patch("starlette.templating.Jinja2Templates.TemplateResponse") as mock_template: + mock_template.return_value = "Mocked Template Response" + yield mock_template + + +def cleanup_temp_directories(): + temp_dir = Path("/tmp/gitingest") + if temp_dir.exists(): + try: + shutil.rmtree(temp_dir) + except PermissionError as e: + print(f"Error cleaning up {temp_dir}: {e}") + + +@pytest.fixture(scope="module", autouse=True) +def cleanup(): + """Cleanup temporary directories after tests.""" + yield + cleanup_temp_directories() + + +@pytest.mark.asyncio +async def test_remote_repository_analysis(request): + """Test the complete flow of analyzing a remote repository.""" + client = request.getfixturevalue("test_client") + form_data = { + "input_text": "https://github.com/octocat/Hello-World", + "max_file_size": "243", + "pattern_type": "exclude", + "pattern": "", + } + + response = client.post("/", data=form_data) + assert response.status_code == 200, f"Form submission failed: {response.text}" + assert "Mocked Template Response" in response.text + + +@pytest.mark.asyncio +async def test_invalid_repository_url(request): + """Test handling of an invalid repository URL.""" + client = request.getfixturevalue("test_client") + form_data = { + "input_text": "https://github.com/nonexistent/repo", + "max_file_size": "243", + "pattern_type": "exclude", + "pattern": "", + } + + response = client.post("/", data=form_data) + assert response.status_code == 200, f"Request failed: {response.text}" + assert "Mocked Template Response" in response.text + + +@pytest.mark.asyncio +async def test_large_repository(request): + """Simulate analysis of a large repository with nested folders.""" + client = request.getfixturevalue("test_client") + form_data = { + "input_text": "https://github.com/large/repo-with-many-files", + "max_file_size": "243", + "pattern_type": "exclude", + "pattern": "", + } + + response = client.post("/", data=form_data) + assert response.status_code == 200, f"Request failed: {response.text}" + assert "Mocked Template Response" in response.text + + +@pytest.mark.asyncio +async def test_concurrent_requests(request): + """Test handling of multiple concurrent requests.""" + client = request.getfixturevalue("test_client") + + def make_request(): + form_data = { + "input_text": "https://github.com/octocat/Hello-World", + "max_file_size": "243", + "pattern_type": "exclude", + "pattern": "", + } + response = client.post("/", data=form_data) + assert response.status_code == 200, f"Request failed: {response.text}" + assert "Mocked Template Response" in response.text + + with ThreadPoolExecutor(max_workers=5) as executor: + futures = [executor.submit(make_request) for _ in range(5)] + for future in futures: + future.result() + + +@pytest.mark.asyncio +async def test_large_file_handling(request): + """Test handling of repositories with large files.""" + client = request.getfixturevalue("test_client") + form_data = { + "input_text": "https://github.com/octocat/Hello-World", + "max_file_size": "1", + "pattern_type": "exclude", + "pattern": "", + } + + response = client.post("/", data=form_data) + assert response.status_code == 200, f"Request failed: {response.text}" + assert "Mocked Template Response" in response.text + + +@pytest.mark.asyncio +async def test_repository_with_patterns(request): + """Test repository analysis with include/exclude patterns.""" + client = request.getfixturevalue("test_client") + form_data = { + "input_text": "https://github.com/octocat/Hello-World", + "max_file_size": "243", + "pattern_type": "include", + "pattern": "*.md", + } + + response = client.post("/", data=form_data) + assert response.status_code == 200, f"Request failed: {response.text}" + assert "Mocked Template Response" in response.text