Spaces:
Running
Running
| """ | |
| test_chatbot.py | |
| --------------- | |
| Unit tests for app/chatbot.py | |
| All heavy components (VectorStore, Retriever, LLMHandler) are mocked | |
| so tests run instantly without GPU or model downloads. | |
| """ | |
| import sys | |
| from pathlib import Path | |
| from unittest.mock import MagicMock, patch | |
| import pytest | |
| sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) | |
| from langchain.schema import Document | |
| from app.chatbot import Chatbot, ChatResponse | |
| from components.vector_store import VectorStore | |
| from components.retriever import Retriever | |
| from components.llm_handler import LLMHandler | |
| # ββ Helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def make_mock_retriever(docs=None): | |
| retriever = MagicMock(spec=Retriever) | |
| docs = docs or [] | |
| retriever.retrieve.return_value = [ | |
| (Document(page_content=d, metadata={"source": "doc.txt"}), 0.8) | |
| for d in docs | |
| ] | |
| return retriever | |
| def make_mock_llm(answer="This is the answer."): | |
| llm = MagicMock(spec=LLMHandler) | |
| llm.generate.return_value = answer | |
| return llm | |
| def make_mock_store(is_ready=True): | |
| store = MagicMock(spec=VectorStore) | |
| store.is_ready = is_ready | |
| return store | |
| # ββ Tests βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class TestChatbot: | |
| def test_returns_chat_response_object(self): | |
| store = make_mock_store() | |
| retriever = make_mock_retriever(["Some context about the topic."]) | |
| llm = make_mock_llm("The answer is 42.") | |
| bot = Chatbot(store, retriever=retriever, llm=llm) | |
| resp = bot.chat("What is the answer?") | |
| assert isinstance(resp, ChatResponse) | |
| def test_answer_comes_from_llm(self): | |
| store = make_mock_store() | |
| retriever = make_mock_retriever(["context"]) | |
| llm = make_mock_llm("Specific LLM answer here.") | |
| bot = Chatbot(store, retriever=retriever, llm=llm) | |
| resp = bot.chat("question") | |
| assert resp.answer == "Specific LLM answer here." | |
| def test_sources_are_populated(self): | |
| store = make_mock_store() | |
| retriever = make_mock_retriever(["context chunk"]) | |
| llm = make_mock_llm("answer") | |
| bot = Chatbot(store, retriever=retriever, llm=llm) | |
| resp = bot.chat("question") | |
| assert len(resp.sources) >= 1 | |
| assert "doc.txt" in resp.sources[0] | |
| def test_empty_query_returns_prompt_message(self): | |
| store = make_mock_store() | |
| bot = Chatbot(store, retriever=make_mock_retriever(), llm=make_mock_llm()) | |
| resp = bot.chat("") | |
| assert "please enter" in resp.answer.lower() | |
| def test_store_not_ready_returns_warning(self): | |
| store = make_mock_store(is_ready=False) | |
| retriever = make_mock_retriever() | |
| llm = make_mock_llm() | |
| bot = Chatbot(store, retriever=retriever, llm=llm) | |
| resp = bot.chat("Any question") | |
| assert "no documents" in resp.answer.lower() or "ingest" in resp.answer.lower() | |
| def test_no_retrieved_docs_returns_fallback(self): | |
| store = make_mock_store() | |
| retriever = make_mock_retriever([]) # empty retrieval | |
| llm = make_mock_llm() | |
| bot = Chatbot(store, retriever=retriever, llm=llm) | |
| resp = bot.chat("unanswerable question") | |
| assert "couldn't find" in resp.answer.lower() or "no relevant" in resp.answer.lower() | |
| assert resp.sources == [] | |
| def test_query_whitespace_is_stripped(self): | |
| store = make_mock_store() | |
| retriever = make_mock_retriever(["context"]) | |
| llm = make_mock_llm("answer") | |
| bot = Chatbot(store, retriever=retriever, llm=llm) | |
| resp = bot.chat(" padded query ") | |
| assert retriever.retrieve.called | |
| called_query = retriever.retrieve.call_args[0][0] | |
| assert called_query == "padded query" | |