# tests/test_chat_api.py """Tests for POST /api/chat — RAG chat (BSL, BYOK gate).""" from __future__ import annotations import sqlite3 from unittest.mock import MagicMock, patch from app.services.retriever import RetrievedChunk def test_chat_returns_402_without_ollama(client, monkeypatch): monkeypatch.delenv("PAGEPIPER_OLLAMA_URL", raising=False) resp = client.post("/api/chat", json={"message": "How does Fireball work?", "history": []}) assert resp.status_code == 402 body = resp.json() assert "detail" in body assert "Ollama" in body["detail"]["message"] def test_chat_returns_answer_with_mocked_ollama(client, test_db, monkeypatch): monkeypatch.setenv("PAGEPIPER_OLLAMA_URL", "http://localhost:11434") conn = sqlite3.connect(test_db) conn.execute( "INSERT OR IGNORE INTO documents(id, title, file_path, status) VALUES ('b1','PHB','phb.pdf','ready')" ) conn.execute( "INSERT INTO page_chunks(doc_id, page_number, text, source, word_count) " "VALUES ('b1',15,'Fireball deals 8d6 fire damage.','text_layer',6)" ) conn.commit() conn.close() mock_llm = MagicMock() mock_llm.complete.return_value = "Fireball deals 8d6 fire damage [p.15]." mock_chunks = [ RetrievedChunk( chunk_id="c1", doc_id="b1", page_number=15, text="Fireball deals 8d6 fire damage.", bm25_score=1.0, vector_score=None, ) ] with patch("app.api.chat.Retriever.hybrid_search", return_value=mock_chunks): with patch("app.api.chat._get_llm_router", return_value=mock_llm): resp = client.post( "/api/chat", json={"message": "How does Fireball work?", "history": [], "doc_ids": ["b1"]}, ) assert resp.status_code == 200 body = resp.json() assert "answer" in body assert "citations" in body assert "Fireball" in body["answer"]