Spaces:
Runtime error
Runtime error
| """Tests for network and API failure scenarios.""" | |
| from unittest.mock import MagicMock, Mock, patch | |
| import pytest | |
| import requests | |
| from models import create_llm, create_embeddings | |
| from qa_chain import QAChainWrapper | |
| from langchain_core.prompts import ChatPromptTemplate | |
| from langchain_core.documents import Document | |
| def test_llm_server_unavailable(mock_chat_openai): | |
| """Test graceful handling when LLM server is unavailable.""" | |
| # Simulate connection error | |
| mock_chat_openai.side_effect = requests.exceptions.ConnectionError("Connection refused") | |
| with pytest.raises((requests.exceptions.ConnectionError, Exception)): | |
| create_llm() | |
| def test_llm_server_timeout(mock_chat_openai): | |
| """Test handling of LLM server timeout.""" | |
| mock_llm = MagicMock() | |
| mock_llm.stream.side_effect = requests.exceptions.Timeout("Request timed out") | |
| mock_chat_openai.return_value = mock_llm | |
| llm = create_llm(streaming=True) | |
| # Should raise timeout error when streaming | |
| with pytest.raises(requests.exceptions.Timeout): | |
| list(llm.stream("test")) | |
| def test_llm_server_error_response(mock_chat_openai): | |
| """Test handling of LLM server error response.""" | |
| mock_llm = MagicMock() | |
| mock_llm.invoke.side_effect = Exception("500 Internal Server Error") | |
| mock_chat_openai.return_value = mock_llm | |
| llm = create_llm(streaming=False) | |
| with pytest.raises(Exception) as exc_info: | |
| llm.invoke("test") | |
| assert "500" in str(exc_info.value) or "Error" in str(exc_info.value) | |
| def test_embedding_model_download_failure(mock_embeddings_class): | |
| """Test handling of embedding model download failure.""" | |
| mock_embeddings_class.side_effect = Exception("Failed to download model") | |
| with pytest.raises(Exception) as exc_info: | |
| create_embeddings() | |
| assert "Failed" in str(exc_info.value) or "download" in str(exc_info.value).lower() | |
| def test_rag_query_with_llm_failure(mock_format_history, mock_create_llm, mock_vectorstore): | |
| """Test RAG query when LLM fails during streaming.""" | |
| from qa_chain import create_qa_chain | |
| mock_format_history.return_value = "" | |
| mock_llm = MagicMock() | |
| mock_llm.stream.side_effect = requests.exceptions.ConnectionError("LLM server unavailable") | |
| mock_create_llm.return_value = mock_llm | |
| mock_retriever = MagicMock() | |
| mock_doc = Document(page_content="Test", metadata={"source": "test.pdf", "page": 1}) | |
| mock_retriever.invoke.return_value = [mock_doc] | |
| mock_vectorstore.as_retriever.return_value = mock_retriever | |
| qa_chain = create_qa_chain(mock_vectorstore) | |
| # Mock the chain operator | |
| mock_chain = MagicMock() | |
| mock_chain.stream.side_effect = requests.exceptions.ConnectionError("LLM server unavailable") | |
| qa_chain._prompt.__or__ = MagicMock(return_value=mock_chain) | |
| inputs = { | |
| "question": "test question", | |
| "chat_history": [], | |
| } | |
| # Should handle error gracefully | |
| results = list(qa_chain.stream(inputs)) | |
| # Should return error message in chunk | |
| assert len(results) > 0 | |
| chunk_text = str(results[0].get("chunk", "")) | |
| # The error handling in qa_chain.py catches exceptions and yields error message | |
| assert len(chunk_text) > 0 # Should have some content (either response or error) | |
| def test_rag_query_with_retriever_failure(mock_format_history, mock_create_llm, mock_vectorstore): | |
| """Test RAG query when retriever fails.""" | |
| from qa_chain import QAChainWrapper | |
| from langchain_core.prompts import ChatPromptTemplate | |
| prompt = ChatPromptTemplate.from_template("Test: {question}") | |
| qa_chain_wrapper = QAChainWrapper(mock_vectorstore, prompt) | |
| mock_format_history.return_value = "" | |
| mock_llm = MagicMock() | |
| mock_chunk = MagicMock() | |
| mock_chunk.content = "Response" | |
| mock_llm.stream.return_value = [mock_chunk] | |
| mock_create_llm.return_value = mock_llm | |
| # Simulate retriever failure | |
| mock_retriever = MagicMock() | |
| mock_retriever.invoke.side_effect = Exception("ChromaDB connection failed") | |
| qa_chain_wrapper._retriever = mock_retriever | |
| mock_chain = MagicMock() | |
| mock_chain.stream.return_value = [mock_chunk] | |
| qa_chain_wrapper._prompt.__or__ = MagicMock(return_value=mock_chain) | |
| inputs = { | |
| "question": "test question", | |
| "chat_history": [], | |
| } | |
| # Should handle retriever error - may raise or handle gracefully | |
| try: | |
| results = list(qa_chain_wrapper.stream(inputs)) | |
| # If it doesn't raise, should have error in response or empty results | |
| if results: | |
| chunk = results[0].get("chunk", "") | |
| assert "Error" in str(chunk).lower() or len(results) == 0 or len(chunk) > 0 | |
| except Exception: | |
| # Exception is acceptable for retriever failure | |
| pass | |
| def test_vanilla_llm_network_failure(mock_create_llm): | |
| """Test vanilla LLM mode with network failure.""" | |
| from ui.handlers import create_stream_chat_response | |
| mock_llm = MagicMock() | |
| mock_llm.stream.side_effect = requests.exceptions.ConnectionError("Server unavailable") | |
| mock_create_llm.return_value = mock_llm | |
| stream_fn = create_stream_chat_response(MagicMock()) | |
| results = list( | |
| stream_fn( | |
| "test question", | |
| [], | |
| "Vanilla LLM", | |
| ) | |
| ) | |
| # Should return error message | |
| assert len(results) > 0 | |
| assert "Error" in results[-1][0] or "unavailable" in results[-1][0].lower() | |
| def test_stream_interruption_handling(mock_format_history, mock_create_llm, mock_vectorstore): | |
| """Test handling of stream interruption.""" | |
| from qa_chain import QAChainWrapper | |
| from langchain_core.prompts import ChatPromptTemplate | |
| prompt = ChatPromptTemplate.from_template("Test: {question}") | |
| qa_chain_wrapper = QAChainWrapper(mock_vectorstore, prompt) | |
| mock_format_history.return_value = "" | |
| # Simulate stream that gets interrupted | |
| mock_llm = MagicMock() | |
| mock_chunk1 = MagicMock() | |
| mock_chunk1.content = "Partial " | |
| mock_chunk2 = MagicMock() | |
| mock_chunk2.content = "response" | |
| def interrupted_stream(*args, **kwargs): | |
| yield mock_chunk1 | |
| raise KeyboardInterrupt("Stream interrupted") | |
| mock_llm.stream.side_effect = interrupted_stream | |
| mock_create_llm.return_value = mock_llm | |
| mock_retriever = MagicMock() | |
| mock_retriever.invoke.return_value = [Document(page_content="Test", metadata={})] | |
| qa_chain_wrapper._retriever = mock_retriever | |
| mock_chain = MagicMock() | |
| mock_chain.stream.side_effect = interrupted_stream | |
| qa_chain_wrapper._prompt.__or__ = MagicMock(return_value=mock_chain) | |
| inputs = { | |
| "question": "test question", | |
| "chat_history": [], | |
| } | |
| # Should handle interruption gracefully | |
| try: | |
| results = list(qa_chain_wrapper.stream(inputs)) | |
| # If it completes, should have partial response | |
| if results: | |
| assert len(results) > 0 | |
| except KeyboardInterrupt: | |
| # Interruption is acceptable | |
| pass | |
| def test_hybrid_search_with_semantic_failure(mock_bm25, mock_vectorstore): | |
| """Test hybrid search when semantic search fails.""" | |
| from retrievers import HybridRetriever | |
| from langchain_core.documents import Document | |
| mock_vectorstore.get.return_value = { | |
| "documents": ["Doc 1"], | |
| "metadatas": [{"source": "test.pdf", "page": 1}], | |
| } | |
| # Simulate semantic search failure | |
| mock_vectorstore.similarity_search_with_score.side_effect = Exception("Vector search failed") | |
| mock_bm25_instance = MagicMock() | |
| mock_bm25_instance.get_scores.return_value = [0.8] | |
| mock_bm25.return_value = mock_bm25_instance | |
| retriever = HybridRetriever(mock_vectorstore) | |
| # Should handle semantic failure gracefully | |
| results = retriever.hybrid_search("test query", k=1) | |
| # Should still return results based on BM25 only | |
| assert isinstance(results, list) | |