import pytest from unittest.mock import MagicMock, AsyncMock from fastapi import FastAPI from fastapi.testclient import TestClient from sqlalchemy.orm import Session from datetime import datetime # Import the dependencies and router factory from app.core.services import RAGService from app.api.dependencies import get_db from app.api.routes import create_api_router @pytest.fixture def client(): """ Pytest fixture to create a TestClient with a fully mocked environment. This creates an isolated FastAPI app for each test. """ test_app = FastAPI() mock_rag_service = MagicMock(spec=RAGService) mock_db_session = MagicMock(spec=Session) def override_get_db(): yield mock_db_session api_router = create_api_router(rag_service=mock_rag_service) test_app.dependency_overrides[get_db] = override_get_db test_app.include_router(api_router) yield TestClient(test_app), mock_rag_service # --- Root Endpoint --- def test_read_root(client): """Tests the root endpoint to ensure the API is running.""" test_client, _ = client response = test_client.get("/") assert response.status_code == 200 assert response.json() == {"status": "AI Model Hub is running!"} # --- Chat Endpoints --- def test_chat_handler_success(client): """Tests a successful chat request.""" test_client, mock_rag_service = client mock_rag_service.chat_with_rag.return_value = "This is a mocked RAG response." response = test_client.post("/chat", json={"prompt": "Hello!", "model": "gemini"}) assert response.status_code == 200 assert response.json()["answer"] == "This is a mocked RAG response." mock_rag_service.chat_with_rag.assert_called_once() def test_chat_handler_validation_error(client): """Tests the chat endpoint with invalid data (an empty prompt).""" test_client, _ = client response = test_client.post("/chat", json={"prompt": "", "model": "deepseek"}) assert response.status_code == 422 def test_chat_handler_internal_error(client): """Tests the chat endpoint when the RAG service raises an exception.""" test_client, mock_rag_service = client error_message = "LLM provider is down" mock_rag_service.chat_with_rag.side_effect = Exception(error_message) response = test_client.post("/chat", json={"prompt": "A valid question", "model": "deepseek"}) assert response.status_code == 500 assert error_message in response.json()["detail"] # --- Document Endpoints --- def test_add_document_success(client): """Tests successfully adding a document.""" test_client, mock_rag_service = client mock_rag_service.add_document.return_value = 123 doc_payload = {"title": "Test Doc", "text": "Content here"} # FIX: Use the correct plural URL '/documents' response = test_client.post("/documents", json=doc_payload) assert response.status_code == 200 assert response.json()["message"] == "Document 'Test Doc' added successfully with ID 123" mock_rag_service.add_document.assert_called_once() def test_get_documents_success(client): """Tests successfully retrieving a list of all documents.""" test_client, mock_rag_service = client # Arrange: Create mock document data that the service will return mock_docs = [ {"id": 1, "title": "Doc One", "status": "ready", "created_at": datetime.now()}, {"id": 2, "title": "Doc Two", "status": "processing", "created_at": datetime.now()} ] mock_rag_service.get_all_documents.return_value = mock_docs # Act response = test_client.get("/documents") # Assert assert response.status_code == 200 response_data = response.json() assert len(response_data["documents"]) == 2 assert response_data["documents"][0]["title"] == "Doc One" mock_rag_service.get_all_documents.assert_called_once() def test_delete_document_success(client): """Tests successfully deleting a document.""" test_client, mock_rag_service = client # Arrange: Mock the service to confirm deletion of document ID 42 mock_rag_service.delete_document.return_value = 42 # Act response = test_client.delete("/documents/42") # Assert assert response.status_code == 200 assert response.json()["message"] == "Document deleted successfully" assert response.json()["document_id"] == 42 mock_rag_service.delete_document.assert_called_once_with(db=mock_rag_service.delete_document.call_args.kwargs['db'], document_id=42) def test_delete_document_not_found(client): """Tests attempting to delete a document that does not exist.""" test_client, mock_rag_service = client # Arrange: Mock the service to indicate the document was not found mock_rag_service.delete_document.return_value = None # Act response = test_client.delete("/documents/999") # Assert assert response.status_code == 404 assert response.json()["detail"] == "Document with ID 999 not found."