| import pytest |
| from unittest.mock import MagicMock, patch |
| from langchain_core.documents import Document |
|
|
|
|
| def make_mock_retriever(docs=None): |
| retriever = MagicMock() |
| if docs is None: |
| docs = [Document(page_content="test content", metadata={"source": "test.pdf", "page": 1})] |
| retriever.invoke.return_value = docs |
| return retriever |
|
|
|
|
| def make_mock_memory(history=None): |
| memory = MagicMock() |
| memory.load_memory_variables.return_value = {"chat_history": history or []} |
| return memory |
|
|
|
|
| async def collect_events(gen): |
| events = [] |
| async for event in gen: |
| events.append(event) |
| return events |
|
|
|
|
| @pytest.mark.asyncio |
| async def test_stream_yields_tokens_then_done(): |
| from server.chain import stream_query_with_web |
|
|
| retriever = make_mock_retriever() |
| memory = make_mock_memory() |
|
|
| mock_chunk1 = MagicMock() |
| mock_chunk1.content = "Hello " |
| mock_chunk2 = MagicMock() |
| mock_chunk2.content = "world." |
|
|
| async def fake_astream(messages): |
| for chunk in [mock_chunk1, mock_chunk2]: |
| yield chunk |
|
|
| with patch("server.chain._create_llm") as mock_llm_factory: |
| mock_llm = MagicMock() |
| mock_llm.astream = fake_astream |
| mock_llm_factory.return_value = mock_llm |
|
|
| events = await collect_events( |
| stream_query_with_web(retriever, memory, "test question", []) |
| ) |
|
|
| token_events = [e for e in events if e["type"] == "token"] |
| done_events = [e for e in events if e["type"] == "done"] |
|
|
| assert len(token_events) == 2 |
| assert token_events[0]["content"] == "Hello " |
| assert token_events[1]["content"] == "world." |
| assert len(done_events) == 1 |
| assert done_events[0]["retrieval_method"] == "hybrid+rerank+web" |
| assert isinstance(done_events[0]["sources"], list) |
|
|
|
|
| @pytest.mark.asyncio |
| async def test_stream_yields_error_on_llm_failure(): |
| from server.chain import stream_query_with_web |
|
|
| retriever = make_mock_retriever() |
| memory = make_mock_memory() |
|
|
| async def failing_astream(messages): |
| raise RuntimeError("LLM exploded") |
| yield |
|
|
| with patch("server.chain._create_llm") as mock_llm_factory: |
| mock_llm = MagicMock() |
| mock_llm.astream = failing_astream |
| mock_llm_factory.return_value = mock_llm |
|
|
| events = await collect_events( |
| stream_query_with_web(retriever, memory, "test question", []) |
| ) |
|
|
| assert events[0]["type"] == "error" |
| assert "LLM exploded" in events[0]["message"] |
|
|
|
|
| @pytest.mark.asyncio |
| async def test_stream_saves_memory_after_tokens(): |
| from server.chain import stream_query_with_web |
|
|
| retriever = make_mock_retriever() |
| memory = make_mock_memory() |
|
|
| mock_chunk = MagicMock() |
| mock_chunk.content = "answer text" |
|
|
| async def fake_astream(messages): |
| yield mock_chunk |
|
|
| with patch("server.chain._create_llm") as mock_llm_factory: |
| mock_llm = MagicMock() |
| mock_llm.astream = fake_astream |
| mock_llm_factory.return_value = mock_llm |
|
|
| await collect_events( |
| stream_query_with_web(retriever, memory, "test question", []) |
| ) |
|
|
| memory.save_context.assert_called_once_with( |
| {"input": "test question"}, {"answer": "answer text"} |
| ) |
|
|