Spaces:
Sleeping
Sleeping
| import sys | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).parent.parent / "src")) | |
| import pytest | |
| import hmac | |
| from unittest.mock import Mock, MagicMock, patch | |
| class TestCheckPassword: | |
| """Tests for check_password function.""" | |
| def test_returns_true_when_already_authenticated(self, mock_st): | |
| """Should return True if password_correct is True.""" | |
| from app import check_password | |
| mock_st.session_state = MagicMock() | |
| mock_st.session_state.__contains__ = Mock(return_value=True) | |
| mock_st.session_state.password_correct = True | |
| result = check_password() | |
| assert result is True | |
| def test_returns_true_when_no_password_configured(self, mock_st): | |
| """Should return True when APP_PASSWORD is empty.""" | |
| from app import check_password | |
| mock_st.session_state = MagicMock() | |
| mock_st.session_state.__contains__ = Mock(return_value=False) | |
| result = check_password() | |
| assert result is True | |
| def test_returns_false_when_wrong_password(self, mock_st): | |
| """Should return False for wrong password.""" | |
| from app import check_password | |
| mock_st.session_state = MagicMock() | |
| mock_st.session_state.__contains__ = Mock(return_value=False) | |
| mock_st.text_input.return_value = "wrong" | |
| result = check_password() | |
| assert result is False | |
| mock_st.error.assert_called_once() | |
| def test_returns_false_when_no_password_entered(self, mock_st): | |
| """Should return False when no password entered.""" | |
| from app import check_password | |
| mock_st.session_state = MagicMock() | |
| mock_st.session_state.__contains__ = Mock(return_value=False) | |
| mock_st.text_input.return_value = "" | |
| result = check_password() | |
| assert result is False | |
| class TestProcessUploadedFiles: | |
| """Tests for process_uploaded_files function.""" | |
| def test_processes_single_file(self, mock_format, mock_process, mock_extract, mock_get_store): | |
| """Should process a single PDF file.""" | |
| from app import process_uploaded_files | |
| mock_file = Mock() | |
| mock_file.name = "test.pdf" | |
| mock_extract.return_value = [{"text": "content", "page_number": 1, "source": "test.pdf"}] | |
| mock_process.return_value = [{"text": "chunk", "metadata": {}}] | |
| mock_format.return_value = (["chunk"], [{}]) | |
| mock_store = Mock() | |
| mock_store.get_loaded_sources.return_value = set() | |
| mock_get_store.return_value = mock_store | |
| result = process_uploaded_files([mock_file]) | |
| assert result == 1 | |
| mock_store.add_documents.assert_called_once() | |
| def test_processes_multiple_files(self, mock_format, mock_process, mock_extract, mock_get_store): | |
| """Should process multiple PDF files.""" | |
| from app import process_uploaded_files | |
| mock_file1 = Mock() | |
| mock_file1.name = "test1.pdf" | |
| mock_file2 = Mock() | |
| mock_file2.name = "test2.pdf" | |
| mock_extract.return_value = [{"text": "content", "page_number": 1, "source": "test.pdf"}] | |
| mock_process.return_value = [{"text": "chunk", "metadata": {}}] | |
| mock_format.return_value = (["chunk"], [{}]) | |
| mock_store = Mock() | |
| mock_store.get_loaded_sources.return_value = set() | |
| mock_get_store.return_value = mock_store | |
| result = process_uploaded_files([mock_file1, mock_file2]) | |
| assert result == 2 | |
| assert mock_store.add_documents.call_count == 2 | |
| def test_empty_files_returns_zero(self): | |
| """Should return 0 for empty file list.""" | |
| from app import process_uploaded_files | |
| result = process_uploaded_files([]) | |
| assert result == 0 | |
| def test_none_files_returns_zero(self): | |
| """Should return 0 for None.""" | |
| from app import process_uploaded_files | |
| result = process_uploaded_files(None) | |
| assert result == 0 | |
| def test_skips_empty_chunks(self, mock_process, mock_extract, mock_get_store): | |
| """Should skip files with no chunks.""" | |
| from app import process_uploaded_files | |
| mock_file = Mock() | |
| mock_file.name = "empty.pdf" | |
| mock_extract.return_value = [] | |
| mock_process.return_value = [] | |
| mock_store = Mock() | |
| mock_store.get_loaded_sources.return_value = set() | |
| mock_get_store.return_value = mock_store | |
| result = process_uploaded_files([mock_file]) | |
| assert result == 0 | |
| mock_store.add_documents.assert_not_called() | |
| class TestResetApplication: | |
| """Tests for reset_application function.""" | |
| def test_resets_all_components(self, mock_st, mock_reset_embedder, mock_reset_store, mock_reset_retrieval, mock_reset_llm): | |
| """Should reset vector store and embedder.""" | |
| from app import reset_application | |
| mock_st.session_state = MagicMock() | |
| reset_application() | |
| mock_reset_store.assert_called_once() | |
| mock_reset_embedder.assert_called_once() | |
| mock_reset_retrieval.assert_called_once() | |
| mock_reset_llm.assert_called_once() | |
| def test_clears_session_state(self, mock_st, mock_reset_embedder, mock_reset_store, mock_reset_retrieval, mock_reset_llm): | |
| """Should clear session state values.""" | |
| from app import reset_application | |
| mock_st.session_state = MagicMock() | |
| mock_st.session_state.documents_processed = True | |
| mock_st.session_state.chunk_count = 100 | |
| mock_st.session_state.chat_history = [{"role": "user", "content": "hi"}] | |
| mock_st.session_state.sources = [{}] | |
| reset_application() | |
| assert mock_st.session_state.documents_processed is False | |
| assert mock_st.session_state.chunk_count == 0 | |
| assert mock_st.session_state.chat_history == [] | |
| assert mock_st.session_state.sources == [] | |
| class TestInitSessionState: | |
| """Tests for init_session_state function.""" | |
| def test_initializes_missing_keys(self, mock_st): | |
| """Should initialize missing session state keys.""" | |
| from app import init_session_state | |
| mock_st.session_state = {} | |
| init_session_state() | |
| assert "documents_processed" in mock_st.session_state | |
| assert "chunk_count" in mock_st.session_state | |
| assert "chat_history" in mock_st.session_state | |
| assert "selected_model" in mock_st.session_state | |
| assert "use_reranking" in mock_st.session_state | |
| def test_preserves_existing_values(self, mock_st): | |
| """Should not overwrite existing session state values.""" | |
| from app import init_session_state | |
| mock_st.session_state = {"chunk_count": 50} | |
| init_session_state() | |
| assert mock_st.session_state["chunk_count"] == 50 | |
| def test_default_values(self, mock_st): | |
| """Should set correct default values.""" | |
| from app import init_session_state | |
| from config import DEFAULT_LLM_MODEL | |
| mock_st.session_state = {} | |
| init_session_state() | |
| assert mock_st.session_state["documents_processed"] is False | |
| assert mock_st.session_state["chunk_count"] == 0 | |
| assert mock_st.session_state["chat_history"] == [] | |
| assert mock_st.session_state["use_reranking"] is True | |
| assert mock_st.session_state["selected_model"] == DEFAULT_LLM_MODEL | |
| class TestGenerateResponse: | |
| """Tests for generate_response function.""" | |
| def reset_handler(self): | |
| from src.integration.query_handler import reset_query_handler | |
| reset_query_handler() | |
| yield | |
| reset_query_handler() | |
| def test_returns_response_sources_debug(self, mock_get_handler): | |
| from app import generate_response | |
| mock_handler = MagicMock() | |
| mock_result = MagicMock() | |
| mock_result.response = "response text" | |
| mock_result.sources = [{"source": "test.pdf"}] | |
| mock_result.mode.value = "documents" | |
| mock_result.execution_time = 0.5 | |
| mock_result.intent = None | |
| mock_result.sql_query = None | |
| mock_result.db_results = None | |
| mock_result.success = True | |
| mock_result.error = None | |
| mock_result.routing_metadata = None | |
| mock_handler.execute.return_value = mock_result | |
| mock_get_handler.return_value = mock_handler | |
| response, sources, debug = generate_response("question", True, [], "model-id") | |
| assert response == "response text" | |
| assert sources == [{"source": "test.pdf"}] | |
| assert debug["success"] is True | |
| def test_passes_reranking_flag(self, mock_get_handler): | |
| from app import generate_response | |
| mock_handler = MagicMock() | |
| mock_result = MagicMock() | |
| mock_result.response = "" | |
| mock_result.sources = [] | |
| mock_result.mode.value = "documents" | |
| mock_result.execution_time = 0.1 | |
| mock_result.intent = None | |
| mock_result.sql_query = None | |
| mock_result.db_results = None | |
| mock_result.success = True | |
| mock_result.error = None | |
| mock_result.routing_metadata = None | |
| mock_handler.execute.return_value = mock_result | |
| mock_get_handler.return_value = mock_handler | |
| generate_response("q", False, [], "model") | |
| mock_handler.execute.assert_called_once() | |
| def test_passes_chat_history_to_retrieve(self, mock_get_handler): | |
| from app import generate_response | |
| mock_handler = MagicMock() | |
| mock_result = MagicMock() | |
| mock_result.response = "" | |
| mock_result.sources = [] | |
| mock_result.mode.value = "documents" | |
| mock_result.execution_time = 0.1 | |
| mock_result.intent = None | |
| mock_result.sql_query = None | |
| mock_result.db_results = None | |
| mock_result.success = True | |
| mock_result.error = None | |
| mock_result.routing_metadata = None | |
| mock_handler.execute.return_value = mock_result | |
| mock_get_handler.return_value = mock_handler | |
| history = [{"role": "user", "content": "Atlas nedir?"}] | |
| generate_response("Bunun ozellikleri?", True, history, "model") | |
| call_kwargs = mock_handler.execute.call_args | |
| assert call_kwargs[1]["chat_history"] == history | |
| def test_limits_chat_history(self, mock_get_handler): | |
| from app import generate_response | |
| mock_handler = MagicMock() | |
| mock_result = MagicMock() | |
| mock_result.response = "" | |
| mock_result.sources = [] | |
| mock_result.mode.value = "documents" | |
| mock_result.execution_time = 0.1 | |
| mock_result.intent = None | |
| mock_result.sql_query = None | |
| mock_result.db_results = None | |
| mock_result.success = True | |
| mock_result.error = None | |
| mock_result.routing_metadata = None | |
| mock_handler.execute.return_value = mock_result | |
| mock_get_handler.return_value = mock_handler | |
| long_history = [{"role": "user", "content": f"msg{i}"} for i in range(10)] | |
| generate_response("q", True, long_history, "model") | |
| call_kwargs = mock_handler.execute.call_args | |
| assert call_kwargs[1]["chat_history"] == long_history | |
| def test_passes_model_id(self, mock_get_handler): | |
| from app import generate_response | |
| mock_handler = MagicMock() | |
| mock_result = MagicMock() | |
| mock_result.response = "" | |
| mock_result.sources = [] | |
| mock_result.mode.value = "documents" | |
| mock_result.execution_time = 0.1 | |
| mock_result.intent = None | |
| mock_result.sql_query = None | |
| mock_result.db_results = None | |
| mock_result.success = True | |
| mock_result.error = None | |
| mock_result.routing_metadata = None | |
| mock_handler.execute.return_value = mock_result | |
| mock_get_handler.return_value = mock_handler | |
| generate_response("q", True, [], "custom-model") | |
| call_kwargs = mock_handler.execute.call_args | |
| assert call_kwargs[1]["model_id"] == "custom-model" | |
| class TestLoadCss: | |
| """Tests for load_css function.""" | |
| def test_loads_css_from_file(self, mock_path): | |
| """Should load CSS content from file.""" | |
| from app import load_css | |
| mock_path.exists.return_value = True | |
| mock_path.read_text.return_value = ".test { color: red; }" | |
| result = load_css() | |
| assert result == ".test { color: red; }" | |
| def test_returns_empty_if_file_missing(self, mock_path): | |
| """Should return empty string if file doesn't exist.""" | |
| from app import load_css | |
| mock_path.exists.return_value = False | |
| result = load_css() | |
| assert result == "" | |
| class TestApplyCustomCss: | |
| """Tests for apply_custom_css function.""" | |
| def test_calls_markdown_with_css(self, mock_st, mock_load): | |
| """Should inject CSS via st.markdown.""" | |
| from app import apply_custom_css | |
| mock_load.return_value = ".test { color: red; }" | |
| apply_custom_css() | |
| mock_st.markdown.assert_called_once() | |
| call_args = mock_st.markdown.call_args | |
| assert "unsafe_allow_html" in call_args[1] | |
| assert call_args[1]["unsafe_allow_html"] is True | |
| def test_skips_if_no_css(self, mock_st, mock_load): | |
| """Should not call markdown if CSS is empty.""" | |
| from app import apply_custom_css | |
| mock_load.return_value = "" | |
| apply_custom_css() | |
| mock_st.markdown.assert_not_called() | |
| def test_wraps_css_in_style_tags(self, mock_st, mock_load): | |
| """Should wrap CSS content in style tags.""" | |
| from app import apply_custom_css | |
| mock_load.return_value = ".box { padding: 10px; }" | |
| apply_custom_css() | |
| call_args = mock_st.markdown.call_args[0][0] | |
| assert "<style>" in call_args | |
| assert "</style>" in call_args | |
| assert ".box { padding: 10px; }" in call_args | |
| class TestGetBaseDocuments: | |
| """Tests for get_base_documents function.""" | |
| def test_returns_empty_if_path_not_exists(self, mock_path): | |
| """Should return empty list if documents folder doesn't exist.""" | |
| from app import get_base_documents | |
| mock_path.exists.return_value = False | |
| result = get_base_documents() | |
| assert result == [] | |
| def test_returns_pdf_files(self, mock_path): | |
| """Should return list of PDF files.""" | |
| from app import get_base_documents | |
| mock_path.exists.return_value = True | |
| mock_path.glob.return_value = [Path("/docs/a.pdf"), Path("/docs/b.pdf")] | |
| result = get_base_documents() | |
| assert len(result) == 2 | |
| mock_path.glob.assert_called_with("*.pdf") | |
| class TestLoadDefaultDocuments: | |
| """Tests for load_default_documents function.""" | |
| def test_skips_if_already_loaded(self, mock_st, mock_get_docs): | |
| """Should skip if base_docs_loaded is True.""" | |
| from app import load_default_documents | |
| mock_st.session_state.get.return_value = True | |
| load_default_documents() | |
| mock_get_docs.assert_not_called() | |
| def test_skips_if_no_documents(self, mock_st, mock_get_docs, mock_get_store): | |
| """Should set flag and return if no documents.""" | |
| from app import load_default_documents | |
| mock_st.session_state = MagicMock() | |
| mock_st.session_state.get.return_value = False | |
| mock_get_docs.return_value = [] | |
| load_default_documents() | |
| assert mock_st.session_state.base_docs_loaded is True | |
| def test_processes_documents( | |
| self, mock_st, mock_get_docs, mock_get_store, | |
| mock_extract, mock_process, mock_format | |
| ): | |
| """Should process and store base documents.""" | |
| from app import load_default_documents | |
| mock_st.session_state = MagicMock() | |
| mock_st.session_state.get.return_value = False | |
| mock_get_docs.return_value = [Path("/docs/test.pdf")] | |
| mock_store = MagicMock() | |
| mock_store.get_collection_stats.return_value = {"points_count": 0} | |
| mock_get_store.return_value = mock_store | |
| mock_extract.return_value = [{"text": "content", "page_number": 1, "source": "test.pdf"}] | |
| mock_process.return_value = [{"text": "chunk", "metadata": {}}] | |
| mock_format.return_value = (["chunk"], [{}]) | |
| load_default_documents() | |
| mock_store.add_documents.assert_called_once() | |
| assert mock_st.session_state.base_docs_loaded is True | |
| assert mock_st.session_state.documents_processed is True | |
| class TestRenderDebugInfo: | |
| """Tests for render_debug_info function.""" | |
| def test_shows_query_rewriting_when_rewritten(self, mock_st): | |
| """Should display query rewriting section when query was rewritten.""" | |
| from app import render_debug_info | |
| mock_col = MagicMock() | |
| mock_col.__enter__ = MagicMock(return_value=mock_col) | |
| mock_col.__exit__ = MagicMock(return_value=False) | |
| mock_st.columns.side_effect = [ | |
| [mock_col, mock_col], | |
| [mock_col, mock_col, mock_col] | |
| ] | |
| debug_info = { | |
| "query": "Bunun ozellikleri?", | |
| "search_query": "Atlas ERP ozellikleri nelerdir?", | |
| "query_rewritten": True, | |
| "rewrite_info": { | |
| "is_follow_up": True, | |
| "method": "llm", | |
| "rewrite_time_ms": 150 | |
| } | |
| } | |
| render_debug_info(debug_info) | |
| mock_st.subheader.assert_called() | |
| assert mock_st.columns.call_count == 2 | |
| def test_skips_rewriting_section_when_not_rewritten(self, mock_st): | |
| """Should skip query rewriting section when query was not rewritten.""" | |
| from app import render_debug_info | |
| debug_info = { | |
| "query": "Atlas nedir?", | |
| "search_query": "Atlas nedir?", | |
| "query_rewritten": False, | |
| "rewrite_info": None | |
| } | |
| render_debug_info(debug_info) | |
| mock_st.info.assert_not_called() | |
| mock_st.success.assert_not_called() | |
| def test_always_shows_retrieval_details(self, mock_st): | |
| """Should always show retrieval details JSON.""" | |
| from app import render_debug_info | |
| debug_info = { | |
| "query": "Test", | |
| "query_rewritten": False, | |
| "stage1_count": 10 | |
| } | |
| render_debug_info(debug_info) | |
| mock_st.json.assert_called_once() | |