File size: 28,560 Bytes
4623a33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
import unittest
from unittest.mock import patch, MagicMock, mock_open, call
import os
import sys
# shutil is no longer needed as _move_indexed_file and index_directory are removed
from langchain_core.documents import Document
from langchain.schema import AIMessage

# Ensure the RAG_BOT module can be found
project_root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
if project_root_path not in sys.path:
    sys.path.insert(0, project_root_path)

from RAG_BOT.vector_store import VectorStore
# Import DocumentProcessor for mocking
from RAG_BOT.document_processor import DocumentProcessor


class TestVectorStore(unittest.TestCase):

    @patch('RAG_BOT.vector_store.DocumentProcessor') 
    @patch('RAG_BOT.vector_store.HuggingFaceEmbeddings')
    @patch('RAG_BOT.vector_store.Chroma')
    @patch('RAG_BOT.vector_store.Config')
    @patch('RAG_BOT.vector_store.logger')
    @patch('RAG_BOT.vector_store.os')
    def setUp(self, mock_os, mock_logger, mock_config_cls, mock_chroma_cls, mock_hf_embeddings_cls, mock_document_processor_cls):
        """Set up for each test."""
        # Configure mock Config instance
        self.mock_config_instance = MagicMock()
        self.mock_config_instance.VECTOR_STORE_PATH = "dummy/vector_store"
        self.mock_config_instance.EMBEDDING_MODEL_NAME = "dummy_model"
        self.mock_config_instance.DATA_PATH = "dummy/data" # Kept for now, though index_directory is removed
        self.mock_config_instance.INDEXED_DATA_PATH = "dummy/indexed_data" # Kept for now
        self.mock_config_instance.SEMANTIC_CHUNKING = True
        self.mock_config_instance.CHUNK_SIZE = 1000 # Added for index_document tests
        self.mock_config_instance.CHUNK_OVERLAP = 200 # Added for index_document tests
        self.mock_config_instance.get_system_prompt.return_value = "System prompt:"
        mock_config_cls.return_value = self.mock_config_instance

        # Configure mock os
        self.mock_os = mock_os
        self.mock_os.path.exists.return_value = False # Default: new store
        self.mock_os.listdir.return_value = []      # Default: empty directory
        self.mock_os.path.join.side_effect = os.path.join # Use actual os.path.join
        self.mock_os.path.abspath.side_effect = os.path.abspath
        self.mock_os.path.dirname.side_effect = os.path.dirname
        self.mock_os.path.basename.side_effect = os.path.basename
        self.mock_os.path.isdir.return_value = True # Assume paths are dirs by default

        # Configure mock Chroma
        self.mock_chroma_instance = MagicMock()
        mock_chroma_cls.return_value = self.mock_chroma_instance

        # Configure mock Embeddings
        self.mock_embeddings_instance = MagicMock()
        mock_hf_embeddings_cls.return_value = self.mock_embeddings_instance
        
        # Configure mock DocumentProcessor
        self.mock_document_processor_instance = MagicMock()
        mock_document_processor_cls.return_value = self.mock_document_processor_instance

        self.mock_logger = mock_logger

        # Initialize VectorStore - this will call the mocked dependencies
        self.vector_store = VectorStore()

    @patch('RAG_BOT.vector_store.DocumentProcessor') # Changed
    @patch('RAG_BOT.vector_store.HuggingFaceEmbeddings')
    @patch('RAG_BOT.vector_store.Chroma')
    @patch('RAG_BOT.vector_store.Config')
    @patch('RAG_BOT.vector_store.logger')
    @patch('RAG_BOT.vector_store.os')
    def test_init_new_vector_store(self, mock_os, mock_logger, mock_config_cls, mock_chroma_cls, mock_hf_embeddings_cls, mock_document_processor_cls): # Changed
        """Test initialization of a new vector store."""
        mock_config_instance = MagicMock()
        mock_config_instance.VECTOR_STORE_PATH = "new_store_path" # Use specific path for this test
        mock_config_instance.EMBEDDING_MODEL_NAME = "dummy_model_new"
        mock_config_cls.return_value = mock_config_instance

        mock_os.path.exists.return_value = True # Dir exists
        mock_os.listdir.return_value = []      # Dir is empty, so new store
        mock_os.path.join.side_effect = os.path.join
        mock_os.makedirs.return_value = None

        mock_embeddings_instance = MagicMock()
        mock_hf_embeddings_cls.return_value = mock_embeddings_instance
        mock_chroma_instance = MagicMock()
        mock_chroma_cls.return_value = mock_chroma_instance

        # Re-initialize to test this specific scenario
        vs = VectorStore(persist_directory="new_store_path")

        # Check HuggingFaceEmbeddings initialization
        mock_hf_embeddings_cls.assert_called_with(model_name="dummy_model_new")

        # Check Chroma initialization for new store
        mock_chroma_cls.assert_called_with(
            persist_directory="new_store_path",
            embedding_function=mock_embeddings_instance
        )
        mock_logger.info.assert_any_call("Embedding model initialized successfully.")
        mock_logger.info.assert_any_call(f"New vector store created successfully at: new_store_path")
        self.assertIsNotNone(vs.document_processor) # Changed

    @patch('RAG_BOT.vector_store.DocumentProcessor') # Added DocumentProcessor mock
    @patch('RAG_BOT.vector_store.HuggingFaceEmbeddings')
    @patch('RAG_BOT.vector_store.Chroma')
    @patch('RAG_BOT.vector_store.Config')
    @patch('RAG_BOT.vector_store.logger')
    @patch('RAG_BOT.vector_store.os')
    def test_init_load_existing_vector_store(self, mock_os, mock_logger, mock_config_cls, mock_chroma_cls, mock_hf_embeddings_cls, mock_document_processor_cls): # Added mock_document_processor_cls
        """Test initialization loading an existing vector store."""
        mock_config_instance = MagicMock()
        mock_config_instance.VECTOR_STORE_PATH = "existing_store_path"
        mock_config_instance.EMBEDDING_MODEL_NAME = "dummy_model_existing"
        mock_config_cls.return_value = mock_config_instance

        mock_os.path.exists.return_value = True
        mock_os.listdir.return_value = ["some_file.db"] # Non-empty dir
        mock_os.path.join.side_effect = os.path.join
        mock_os.makedirs.return_value = None

        mock_embeddings_instance = MagicMock()
        mock_hf_embeddings_cls.return_value = mock_embeddings_instance
        mock_chroma_instance = MagicMock()
        mock_chroma_cls.return_value = mock_chroma_instance

        vs = VectorStore(persist_directory="existing_store_path")

        mock_hf_embeddings_cls.assert_called_with(model_name="dummy_model_existing")
        mock_chroma_cls.assert_called_with(
            persist_directory="existing_store_path",
            embedding_function=mock_embeddings_instance
        )
        mock_logger.info.assert_any_call(f"Existing vector store loaded successfully from: existing_store_path")
        self.assertIsNotNone(vs.document_processor) # Added assertion for document_processor

    @patch('RAG_BOT.vector_store.DocumentProcessor') # Added DocumentProcessor mock
    @patch('RAG_BOT.vector_store.HuggingFaceEmbeddings')
    @patch('RAG_BOT.vector_store.Chroma')
    @patch('RAG_BOT.vector_store.Config')
    @patch('RAG_BOT.vector_store.logger')
    @patch('RAG_BOT.vector_store.os')
    def test_init_load_error_fallback_to_new(self, mock_os, mock_logger, mock_config_cls, mock_chroma_cls, mock_hf_embeddings_cls, mock_document_processor_cls): # Added mock_document_processor_cls
        """Test fallback to new store creation if loading existing fails."""
        mock_config_instance = MagicMock()
        mock_config_instance.VECTOR_STORE_PATH = "fallback_store_path"
        mock_config_instance.EMBEDDING_MODEL_NAME = "dummy_model_fallback"
        mock_config_cls.return_value = mock_config_instance

        mock_os.path.exists.return_value = True
        mock_os.listdir.return_value = ["some_file.db"] # Non-empty dir
        mock_os.path.join.side_effect = os.path.join
        mock_os.makedirs.return_value = None

        mock_embeddings_instance = MagicMock()
        mock_hf_embeddings_cls.return_value = mock_embeddings_instance
        mock_chroma_instance_success = MagicMock() # For the successful creation

        # Simulate Chroma load failure then success on new creation
        mock_chroma_cls.side_effect = [Exception("Load failed"), mock_chroma_instance_success]

        vs = VectorStore(persist_directory="fallback_store_path")

        mock_hf_embeddings_cls.assert_called_with(model_name="dummy_model_fallback")
        self.assertEqual(mock_chroma_cls.call_count, 2)
        # First call (failed load)
        mock_chroma_cls.assert_any_call(
            persist_directory="fallback_store_path",
            embedding_function=mock_embeddings_instance
        )
        # Second call (successful creation)
        mock_chroma_cls.assert_any_call(
            persist_directory="fallback_store_path",
            embedding_function=mock_embeddings_instance
        )

        mock_logger.error.assert_any_call(f"Error loading existing vector store from fallback_store_path: Load failed", exc_info=True)
        mock_logger.warning.assert_any_call(f"Attempting to create a new vector store at fallback_store_path due to loading error.")
        mock_logger.info.assert_any_call("New vector store created after load failure.")
        self.assertIsNotNone(vs.document_processor) # Added assertion for document_processor

    @patch('RAG_BOT.vector_store.DocumentProcessor') # Added DocumentProcessor mock
    @patch('RAG_BOT.vector_store.HuggingFaceEmbeddings')
    @patch('RAG_BOT.vector_store.Chroma')
    @patch('RAG_BOT.vector_store.Config')
    @patch('RAG_BOT.vector_store.logger')
    @patch('RAG_BOT.vector_store.os')
    def test_init_critical_creation_failure(self, mock_os, mock_logger, mock_config_cls, mock_chroma_cls, mock_hf_embeddings_cls, mock_document_processor_cls): # Added mock_document_processor_cls
        """Test critical failure during new vector store creation."""
        mock_config_instance = MagicMock()
        mock_config_instance.VECTOR_STORE_PATH = "critical_fail_path"
        mock_config_instance.EMBEDDING_MODEL_NAME = "dummy_model_critical"
        mock_config_cls.return_value = mock_config_instance

        mock_os.path.exists.return_value = True # Dir exists
        mock_os.listdir.return_value = []      # Dir is empty, new store attempt
        mock_os.path.join.side_effect = os.path.join
        mock_os.makedirs.return_value = None

        mock_hf_embeddings_cls.return_value = MagicMock()
        mock_chroma_cls.side_effect = Exception("Critical creation failed")

        with self.assertRaises(Exception) as context:
            VectorStore(persist_directory="critical_fail_path")
        self.assertTrue("Critical creation failed" in str(context.exception))
        mock_hf_embeddings_cls.assert_called_with(model_name="dummy_model_critical")
        mock_logger.critical.assert_any_call(f"Failed to create new vector store at critical_fail_path: Critical creation failed", exc_info=True)

    def test_get_vectordb(self):
        """Test the get_vectordb getter."""
        self.assertEqual(self.vector_store.get_vectordb(), self.mock_chroma_instance)

    @patch('RAG_BOT.vector_store.logger')
    def test_add_documents_success(self, mock_logger):
        """Test adding documents successfully."""        
        docs = [Document(page_content="doc1", metadata={"source": "source1.txt"})]
        self.vector_store.add_documents(docs)
        self.mock_chroma_instance.add_documents.assert_called_once_with(documents=docs)
        mock_logger.info.assert_called_with("Vector store updated with 1 document chunks from source: source1.txt") # os.path.basename

    @patch('RAG_BOT.vector_store.logger')
    def test_add_documents_empty_list(self, mock_logger):
        """Test adding an empty list of documents."""
        #self.mock_logger.reset_mock()
        self.vector_store.add_documents([])
        self.mock_chroma_instance.add_documents.assert_not_called()
        mock_logger.warning.assert_called_with("Attempted to add an empty list of documents. Skipping.")

    @patch('RAG_BOT.vector_store.logger')
    def test_add_documents_failure(self, mock_logger):
        """Test failure during adding documents to ChromaDB."""        
        docs = [Document(page_content="doc1", metadata={"source": "source_fail.txt"})]
        self.mock_chroma_instance.add_documents.side_effect = Exception("DB add error")
        self.vector_store.add_documents(docs)
        mock_logger.error.assert_called_with("Failed to add documents from source source_fail.txt to ChromaDB: DB add error", exc_info=True) # os.path.basename

    def test_document_exists_true(self):
        """Test document_exists when document is found.""" # Renamed
        self.mock_chroma_instance.get.return_value = {"ids": ["id1"], "metadatas": [], "documents": []}
        exists = self.vector_store.document_exists("2023-01-01", "en") # Renamed
        self.assertTrue(exists)
        self.mock_chroma_instance.get.assert_called_once_with(
            where={"$and": [{"date": "2023-01-01"}, {"language": "en"}]},
            limit=1,
            include=[]
        )

    def test_document_exists_false(self):
        """Test document_exists when document is not found.""" # Renamed
        self.mock_chroma_instance.get.return_value = {"ids": [], "metadatas": [], "documents": []}
        exists = self.vector_store.document_exists("2023-01-02", "en") # Renamed
        self.assertFalse(exists)

    @patch('RAG_BOT.vector_store.logger')
    def test_document_exists_no_date(self, mock_logger):
        """Test document_exists with no date string.""" # Renamed        
        exists = self.vector_store.document_exists(None, "en") # Renamed
        self.assertFalse(exists)
        mock_logger.warning.assert_called_with("Cannot check for existing document without a date string. Assuming it does not exist.")

    @patch('RAG_BOT.vector_store.logger')
    def test_document_exists_no_language(self, mock_logger):
        """Test document_exists with no language string.""" # Renamed
        self.mock_logger.reset_mock()
        exists = self.vector_store.document_exists("2023-01-01", None) # Renamed
        self.assertFalse(exists)
        mock_logger.warning.assert_called_with("Cannot check for existing document without language metadata. Assuming it does not exist.")

    @patch('RAG_BOT.vector_store.logger')
    def test_document_exists_db_error(self, mock_logger):
        """Test document_exists when ChromaDB get fails.""" # Renamed
        self.mock_logger.reset_mock()
        self.mock_chroma_instance.get.side_effect = Exception("DB get error")
        exists = self.vector_store.document_exists("2023-01-03", "en") # Renamed
        self.assertFalse(exists) # Should assume not exists on error
        mock_logger.error.assert_called_with(
            "Error checking ChromaDB for existing date 2023-01-03 and language en: DB get error. Assuming document does not exist.",
            exc_info=True
        )

    @patch.object(VectorStore, 'document_exists') 
    @patch('RAG_BOT.vector_store.logger')    
    def test_index_document_semantic_success(self, mock_logger, mock_doc_exists): 
        """Test index_document for a new document with semantic chunking."""        
        mock_doc_exists.return_value = False
        docs_in = [Document(page_content="doc content", metadata={"source": "test.doc", "date": "2023-01-01", "language": "en"})] # Generic doc
        chunks_out = [Document(page_content="chunk1", metadata={"source": "test.doc", "date": "2023-01-01", "language": "en"})]
        
        # Ensure the mock_document_processor_instance from setUp is used and configured
        self.mock_document_processor_instance.semantic_chunking.return_value = chunks_out # Use mock_document_processor_instance
        self.mock_document_processor_instance.semantic_chunking.side_effect = None 

        # self.mock_config_instance.SEMANTIC_CHUNKING = True # This should be set in setUp or per test if varied
        
        was_indexed = self.vector_store.index_document(docs_in, semantic_chunk=True) # Renamed

        self.assertTrue(was_indexed)
        mock_doc_exists.assert_called_once_with("2023-01-01", "en")
        self.mock_document_processor_instance.semantic_chunking.assert_called_once_with( 
            docs_in,
            chunk_size=self.mock_config_instance.CHUNK_SIZE,
            chunk_overlap=self.mock_config_instance.CHUNK_OVERLAP,
            model_name=self.mock_config_instance.EMBEDDING_MODEL_NAME
        )
        self.mock_chroma_instance.add_documents.assert_called_once_with(documents=chunks_out)
        
        # Assert no error or warning logs that would indicate a False return path
        mock_logger.error.assert_not_called()
        mock_logger.warning.assert_not_called()

        # Assert all expected info logs for the success path
        mock_logger.info.assert_any_call("Proceeding with chunking and indexing for test.doc.")
        # The add_documents method also logs an info message
        mock_logger.info.assert_any_call(f"Vector store updated with {len(chunks_out)} document chunks from source: test.doc")
        mock_logger.info.assert_any_call(f"Successfully indexed {len(chunks_out)} chunks from test.doc.")

    @patch.object(VectorStore, 'document_exists')     
    def test_index_document_non_semantic_success(self, mock_doc_exists): 
        """Test index_document for a new document with non-semantic chunking.""" # Renamed        
        self.mock_logger.reset_mock()
        mock_doc_exists.return_value = False
        docs_in = [Document(page_content="doc content", metadata={"source": "test.doc", "date": "2023-01-02", "language": "hi"})] # Generic doc
        chunks_out = [Document(page_content="chunk_doc", metadata={"source": "test.doc", "date": "2023-01-02", "language": "hi"})]
        self.mock_document_processor_instance.split_text.return_value = chunks_out 
        self.mock_document_processor_instance.split_text.side_effect = None
        self.mock_chroma_instance.add_documents.side_effect = None                

        was_indexed = self.vector_store.index_document(docs_in, semantic_chunk=False) # Renamed, removed chunk_size/overlap args

        self.assertTrue(was_indexed)
        mock_doc_exists.assert_called_once_with("2023-01-02", "hi")
        self.mock_document_processor_instance.split_text.assert_called_once_with( # Use mock_document_processor_instance
            docs_in, 
            chunk_size=self.mock_config_instance.CHUNK_SIZE, 
            chunk_overlap=self.mock_config_instance.CHUNK_OVERLAP
        )
        self.mock_chroma_instance.add_documents.assert_called_once_with(documents=chunks_out)
        # Assert no error or warning logs that would indicate a False return path
        self.mock_logger.error.assert_not_called()
        self.mock_logger.warning.assert_not_called()
        

    @patch.object(VectorStore, 'document_exists') # Renamed
    def test_index_document_already_exists(self, mock_doc_exists):
        """Test index_document skips if document already exists.""" # Renamed
        self.mock_logger.reset_mock()
        mock_doc_exists.return_value = True
        docs_in = [Document(page_content="content", metadata={"source": "exist.doc", "date": "2023-01-03", "language": "en"})]
        
        was_indexed = self.vector_store.index_document(docs_in) # Renamed

        self.assertFalse(was_indexed)
        mock_doc_exists.assert_called_once_with("2023-01-03", "en")
        self.mock_document_processor_instance.semantic_chunking.assert_not_called() # Check generic processor
        self.mock_document_processor_instance.split_text.assert_not_called() # Check generic processor
        self.mock_chroma_instance.add_documents.assert_not_called()
        self.mock_logger.info.assert_called_with("Document with date 2023-01-03 and language en (source: exist.doc) already indexed. Skipping.")

    def test_index_document_empty_input(self):
        """Test index_document with empty document list.""" # Renamed
        self.mock_logger.reset_mock()
        was_indexed = self.vector_store.index_document([]) # Renamed
        self.assertFalse(was_indexed)
        self.mock_logger.warning.assert_called_with("Attempted to index an empty list of documents. Skipping.")

    @patch.object(VectorStore, 'document_exists') # Renamed
    def test_index_document_no_chunks_generated(self, mock_doc_exists):
        """Test index_document when no chunks are generated.""" # Renamed
        self.mock_logger.reset_mock()
        mock_doc_exists.return_value = False
        docs_in = [Document(page_content="doc content", metadata={"source": "no_chunks.doc", "date": "2023-01-04", "language": "en"})]
        self.mock_document_processor_instance.semantic_chunking.return_value = [] # No chunks
        
        was_indexed = self.vector_store.index_document(docs_in, semantic_chunk=True) # Renamed

        self.assertFalse(was_indexed)
        self.mock_logger.warning.assert_called_with("No text chunks generated after processing no_chunks.doc. Nothing to index.")

    @patch.object(VectorStore, 'document_exists') # Renamed
    def test_index_document_chunking_error(self, mock_doc_exists):
        """Test index_document when chunking raises an error.""" # Renamed
        self.mock_logger.reset_mock()
        mock_doc_exists.return_value = False
        docs_in = [Document(page_content="doc content", metadata={"source": "chunk_error.doc", "date": "2023-01-05", "language": "en"})]
        self.mock_document_processor_instance.semantic_chunking.side_effect = Exception("Chunking error")

        was_indexed = self.vector_store.index_document(docs_in, semantic_chunk=True) # Renamed
        self.assertFalse(was_indexed)
        self.mock_logger.error.assert_called_with("Error during chunking or adding documents for chunk_error.doc: Chunking error", exc_info=True)
        self.mock_logger.info.assert_any_call("Proceeding with chunking and indexing for chunk_error.doc.")

    # Removed all _move_indexed_file tests as the method is removed
    # Removed all index_directory tests as the method is removed

    def test_log_all_indexed_metadata_success(self):
        """Test log_all_indexed_metadata with data."""
        self.mock_logger.reset_mock()
        self.mock_chroma_instance.get.return_value = {
            "ids": ["id1", "id2", "id3"],
            "metadatas": [
                {"date": "2023-01-01", "is_avyakt": True, "language": "en"},
                {"date": "2023-01-01", "is_avyakt": True, "language": "en"},
                {"date": "2023-01-02", "is_avyakt": False, "language": "hi"},
            ]
        }
        self.vector_store.log_all_indexed_metadata()
        self.mock_logger.info.assert_any_call("Retrieved metadata for 3 documents.")
        self.mock_logger.info.assert_any_call("Date: 2023-01-01 - Type: Avyakt, Language: en, Count: 2")
        self.mock_logger.info.assert_any_call("Date: 2023-01-02 - Type: Sakar/Other, Language: hi, Count: 1")

    def test_log_all_indexed_metadata_empty_db(self):
        """Test log_all_indexed_metadata with an empty database."""
        self.mock_logger.reset_mock()
        self.mock_chroma_instance.get.return_value = {"ids": [], "metadatas": []}
        self.vector_store.log_all_indexed_metadata()
        self.mock_logger.info.assert_any_call("ChromaDB index appears to be empty. No metadata to retrieve.")

    def test_log_all_indexed_metadata_db_error(self):
        """Test log_all_indexed_metadata when ChromaDB get fails."""
        self.mock_logger.reset_mock()
        self.mock_chroma_instance.get.side_effect = Exception("DB get all error")
        self.vector_store.log_all_indexed_metadata()
        self.mock_logger.error.assert_called_with("Error retrieving all metadata from ChromaDB: DB get all error", exc_info=True)

    @patch('RAG_BOT.vector_store.ChatGoogleGenerativeAI')
    @patch('RAG_BOT.vector_store.PromptTemplate')
    # Removed RunnablePassthrough mock as it's not directly used in the chain logic being tested here
    def test_query_index_success(self, mock_prompt_template_cls, mock_chat_google_cls):
        """Test query_index successfully returns a response."""
        mock_llm_instance = MagicMock()
        mock_llm_instance.invoke.return_value = AIMessage(content="Test LLM response")
        mock_chat_google_cls.return_value = mock_llm_instance

        mock_prompt_instance = MagicMock()
        mock_prompt_template_cls.return_value = mock_prompt_instance
        
        # Mock retriever and its invoke
        mock_retriever = MagicMock()
        retrieved_docs = [Document(page_content="context doc 1")]
        mock_retriever.invoke.return_value = retrieved_docs
        self.mock_chroma_instance.as_retriever.return_value = mock_retriever

        query = "What is love?"
        response = self.vector_store.query_index(query, k=5)

        self.assertEqual(response, "Test LLM response")
        self.mock_chroma_instance.as_retriever.assert_called_once_with(
            search_type="similarity",
            search_kwargs={"k": 5}
        )
        mock_retriever.invoke.assert_called_once_with(query)
        mock_prompt_template_cls.assert_called_once_with(
            input_variables=["context", "question"],
            template="System prompt:\n\nContext:\n{context}\n\nQuestion: {question}" # Assuming "System prompt:" is from mock_config
        )
        self.mock_config_instance.get_system_prompt.assert_called_with(language_code="en")
        mock_llm_instance.invoke.assert_called_once_with({"context": "context doc 1", "question": query})


    def test_query_index_vectordb_not_initialized(self):
        """Test query_index when vectordb is not initialized."""
        self.mock_logger.reset_mock()
        original_vectordb = self.vector_store.vectordb # Store original
        self.vector_store.vectordb = None # Simulate uninitialized DB
        response = self.vector_store.query_index("test query")
        self.assertEqual(response, "Error: Vector Store is not available.")
        self.mock_logger.error.assert_called_with("VectorDB not initialized. Cannot perform query.")

    @patch('RAG_BOT.vector_store.ChatGoogleGenerativeAI')
    def test_query_index_with_date_filter(self, mock_chat_google_cls):
        """Test query_index with a valid date filter."""
        mock_llm_instance = MagicMock()
        mock_llm_instance.invoke.return_value = AIMessage(content="Filtered response")
        mock_chat_google_cls.return_value = mock_llm_instance
        
        mock_retriever = MagicMock()
        mock_retriever.invoke.return_value = []
        self.mock_chroma_instance.as_retriever.return_value = mock_retriever

        self.vector_store.query_index("query", date_filter="2023-01-01")
        self.mock_chroma_instance.as_retriever.assert_called_with(
            search_type="similarity",
            search_kwargs={"k": 25, "filter": {"date": "2023-01-01"}} # Default k is 25
        )
        self.mock_config_instance.get_system_prompt.assert_called_with(language_code="en")

    def test_query_index_invalid_date_filter(self):
        """Test query_index with an invalid date filter format."""
        self.mock_logger.reset_mock()
        response = self.vector_store.query_index("query", date_filter="01-01-2023")
        self.assertEqual(response, "Error: Invalid date format for filter. Please use YYYY-MM-DD.")
        self.mock_logger.error.assert_called_with("Invalid date format provided: 01-01-2023. Should be YYYY-MM-DD.")

    @patch('RAG_BOT.vector_store.ChatGoogleGenerativeAI')
    def test_query_index_llm_error(self, mock_chat_google_cls):
        self.mock_logger.reset_mock()
        """Test query_index when the LLM call fails."""
        mock_llm_instance = MagicMock()
        mock_llm_instance.invoke.side_effect = Exception("LLM API error")
        mock_chat_google_cls.return_value = mock_llm_instance

        mock_retriever = MagicMock()
        mock_retriever.invoke.return_value = [Document(page_content="context")]
        self.mock_chroma_instance.as_retriever.return_value = mock_retriever
        
        response = self.vector_store.query_index("query")
        self.assertEqual(response, "Sorry, an error occurred while processing your query.")
        self.mock_logger.error.assert_called_with("Error during query execution: LLM API error", exc_info=True)
        self.mock_config_instance.get_system_prompt.assert_called_with(language_code="en")


if __name__ == '__main__':
    unittest.main()