File size: 26,645 Bytes
0828c2c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
"""Tests for chat history feature in RAG pipeline."""

from unittest.mock import MagicMock, patch

import pytest

from src.rag_pipeline import RAGPipeline


class TestFormatChatHistory:
    """Test _format_chat_history method."""

    def test_format_chat_history_empty(self):
        """Test formatting empty chat history."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_handler.get_llm.return_value = MagicMock()
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            result = pipeline._format_chat_history(None)
            assert result == ""

            result = pipeline._format_chat_history([])
            assert result == ""

    def test_format_chat_history_single_user_message(self):
        """Test formatting single user message."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_handler.get_llm.return_value = MagicMock()
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            history = [{"role": "user", "content": "Hello"}]
            result = pipeline._format_chat_history(history)

            assert "PREVIOUS CONVERSATION" in result
            assert "User: Hello" in result

    def test_format_chat_history_single_assistant_message(self):
        """Test formatting single assistant message."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_handler.get_llm.return_value = MagicMock()
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            history = [{"role": "assistant", "content": "Hi there!"}]
            result = pipeline._format_chat_history(history)

            assert "PREVIOUS CONVERSATION" in result
            assert "Assistant: Hi there!" in result

    def test_format_chat_history_conversation(self):
        """Test formatting full conversation."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_handler.get_llm.return_value = MagicMock()
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            history = [
                {"role": "user", "content": "What is your name?"},
                {"role": "assistant", "content": "I'm an AI assistant."},
                {"role": "user", "content": "Tell me more."},
            ]
            result = pipeline._format_chat_history(history)

            assert "PREVIOUS CONVERSATION" in result
            assert "User: What is your name?" in result
            assert "Assistant: I'm an AI assistant." in result
            assert "User: Tell me more." in result

    def test_format_chat_history_missing_role(self):
        """Test formatting with missing role field."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_handler.get_llm.return_value = MagicMock()
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            history = [{"content": "Some message"}]
            result = pipeline._format_chat_history(history)

            # Should return empty string if role is missing
            assert result == ""

    def test_format_chat_history_missing_content(self):
        """Test formatting with missing content field."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_handler.get_llm.return_value = MagicMock()
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            history = [{"role": "user", "content": ""}]
            result = pipeline._format_chat_history(history)

            # Function formats empty content (doesn't filter it out)
            assert "PREVIOUS CONVERSATION" in result
            assert "User: " in result

    def test_format_chat_history_invalid_role(self):
        """Test formatting with invalid role."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_handler.get_llm.return_value = MagicMock()
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            history = [{"role": "system", "content": "System message"}]
            result = pipeline._format_chat_history(history)

            # Should return empty string for non-user/assistant roles
            assert result == ""


class TestQueryWithChatHistory:
    """Test query method with chat history."""

    def test_query_without_chat_history(self):
        """Test query without chat history."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_llm = MagicMock()
            mock_llm.invoke.return_value = "Test response"
            mock_handler.get_llm.return_value = mock_llm
            mock_handler.get_system_prompt.return_value = "System prompt"
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            response = pipeline.query("What is AI?")

            assert "result" in response
            assert isinstance(response["result"], str)
            assert len(response["result"]) > 0

    def test_query_with_chat_history(self):
        """Test query with chat history."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_llm = MagicMock()
            mock_llm.invoke.return_value = "Test response"
            mock_handler.get_llm.return_value = mock_llm
            mock_handler.get_system_prompt.return_value = "System prompt"
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            history = [
                {"role": "user", "content": "Hello"},
                {"role": "assistant", "content": "Hi!"},
            ]
            response = pipeline.query("What is AI?", chat_history=history)

            assert "result" in response
            assert isinstance(response["result"], str)
            assert len(response["result"]) > 0

    def test_query_with_empty_chat_history(self):
        """Test query with empty chat history list."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_llm = MagicMock()
            mock_llm.invoke.return_value = "Test response"
            mock_handler.get_llm.return_value = mock_llm
            mock_handler.get_system_prompt.return_value = "System prompt"
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            response = pipeline.query("What is AI?", chat_history=[])

            assert "result" in response
            assert isinstance(response["result"], str)


class TestGetAnswerWithChatHistory:
    """Test get_answer method with chat history."""

    def test_get_answer_without_chat_history(self):
        """Test get_answer without chat history."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_llm = MagicMock()
            mock_llm.invoke.return_value = "Test answer"
            mock_handler.get_llm.return_value = mock_llm
            mock_handler.get_system_prompt.return_value = "System prompt"
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            answer = pipeline.get_answer("What is AI?")

            assert isinstance(answer, str)
            assert len(answer) > 0

    def test_get_answer_with_chat_history(self):
        """Test get_answer with chat history."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_llm = MagicMock()
            mock_llm.invoke.return_value = "Test answer"
            mock_handler.get_llm.return_value = mock_llm
            mock_handler.get_system_prompt.return_value = "System prompt"
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            history = [{"role": "user", "content": "Previous question"}]
            answer = pipeline.get_answer("What is AI?", chat_history=history)

            assert isinstance(answer, str)
            assert len(answer) > 0


class TestGetAnswerWithSourcesChatHistory:
    """Test get_answer_with_sources method with chat history."""

    def test_get_answer_with_sources_without_chat_history(self):
        """Test get_answer_with_sources without chat history."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_llm = MagicMock()
            mock_llm.invoke.return_value = "Test answer"
            mock_handler.get_llm.return_value = mock_llm
            mock_handler.get_system_prompt.return_value = "System prompt"
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            answer, sources = pipeline.get_answer_with_sources("What is AI?")

            assert isinstance(answer, str)
            assert isinstance(sources, list)

    def test_get_answer_with_sources_with_chat_history(self):
        """Test get_answer_with_sources with chat history."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_llm = MagicMock()
            mock_llm.invoke.return_value = "Test answer"
            mock_handler.get_llm.return_value = mock_llm
            mock_handler.get_system_prompt.return_value = "System prompt"
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            history = [{"role": "user", "content": "Previous question"}]
            answer, sources = pipeline.get_answer_with_sources("What is AI?", chat_history=history)

            assert isinstance(answer, str)
            assert isinstance(sources, list)


class TestStreamQueryWithChatHistory:
    """Test stream_query method with chat history."""

    def test_stream_query_without_chat_history(self):
        """Test stream_query without chat history."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_llm = MagicMock()
            mock_llm.stream.return_value = iter(["Test", " response", " chunks"])
            mock_handler.get_llm.return_value = mock_llm
            mock_handler.get_system_prompt.return_value = "System prompt"
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            chunks = list(pipeline.stream_query("What is AI?"))

            assert len(chunks) > 0
            assert all(isinstance(chunk, str) for chunk in chunks)

    def test_stream_query_with_chat_history(self):
        """Test stream_query with chat history."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_llm = MagicMock()
            mock_llm.stream.return_value = iter(["Test", " response", " chunks"])
            mock_handler.get_llm.return_value = mock_llm
            mock_handler.get_system_prompt.return_value = "System prompt"
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            history = [{"role": "user", "content": "Previous question"}]
            chunks = list(pipeline.stream_query("What is AI?", chat_history=history))

            assert len(chunks) > 0
            assert all(isinstance(chunk, str) for chunk in chunks)

    def test_stream_query_error_handling(self):
        """Test stream_query error handling."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_llm = MagicMock()
            mock_llm.stream.side_effect = Exception("Stream error")
            mock_handler.get_llm.return_value = mock_llm
            mock_handler.get_system_prompt.return_value = "System prompt"
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            chunks = list(pipeline.stream_query("What is AI?"))

            # Should yield error message
            assert len(chunks) > 0
            assert "technical issue" in chunks[0].lower()


class TestStreamAnswerWithChatHistory:
    """Test stream_answer method with chat history."""

    def test_stream_answer_without_chat_history(self):
        """Test stream_answer without chat history."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_llm = MagicMock()
            mock_llm.stream.return_value = iter(["Answer", " chunks"])
            mock_handler.get_llm.return_value = mock_llm
            mock_handler.get_system_prompt.return_value = "System prompt"
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            chunks = list(pipeline.stream_answer("What is AI?"))

            assert len(chunks) > 0
            assert all(isinstance(chunk, str) for chunk in chunks)

    def test_stream_answer_with_chat_history(self):
        """Test stream_answer with chat history."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_llm = MagicMock()
            mock_llm.stream.return_value = iter(["Answer", " chunks"])
            mock_handler.get_llm.return_value = mock_llm
            mock_handler.get_system_prompt.return_value = "System prompt"
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            history = [{"role": "user", "content": "Previous question"}]
            chunks = list(pipeline.stream_answer("What is AI?", chat_history=history))

            assert len(chunks) > 0
            assert all(isinstance(chunk, str) for chunk in chunks)


class TestChatHistoryInPrompt:
    """Test that chat history is properly included in prompts."""

    def test_prompt_template_includes_chat_history_placeholder(self):
        """Test that prompt template includes chat_history placeholder."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_handler.get_llm.return_value = MagicMock()
            mock_handler.get_system_prompt.return_value = "System prompt"
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            template_str = pipeline.prompt_template.messages[0].prompt.template

            # Should contain chat_history placeholder
            assert "{chat_history}" in template_str

    def test_chat_history_position_in_template(self):
        """Test that chat history appears in correct position in template."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_handler.get_llm.return_value = MagicMock()
            mock_handler.get_system_prompt.return_value = "System prompt"
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            template_str = pipeline.prompt_template.messages[0].prompt.template

            # Chat history should come after system prompt but before main doc and context
            chat_history_pos = template_str.find("{chat_history}")
            main_doc_pos = template_str.find("{main_document_section}")
            context_pos = template_str.find("{context}")
            question_pos = template_str.find("{question}")

            # Verify order: system -> chat_history -> main_doc -> context -> question
            assert chat_history_pos < main_doc_pos
            assert chat_history_pos < context_pos
            assert chat_history_pos < question_pos


class TestChatHistoryIntegration:
    """Integration tests for chat history feature."""

    def test_multiple_queries_with_history(self):
        """Test multiple queries building up chat history."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_llm = MagicMock()
            mock_llm.invoke.return_value = "Test response"
            mock_handler.get_llm.return_value = mock_llm
            mock_handler.get_system_prompt.return_value = "System prompt"
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()

            # First query - no history
            response1 = pipeline.query("What is AI?")
            assert "result" in response1

            # Second query - with history from first query
            history = [
                {"role": "user", "content": "What is AI?"},
                {"role": "assistant", "content": response1["result"]},
            ]
            response2 = pipeline.query("Tell me more", chat_history=history)
            assert "result" in response2

            # Third query - with extended history
            extended_history = history + [
                {"role": "user", "content": "Tell me more"},
                {"role": "assistant", "content": response2["result"]},
            ]
            response3 = pipeline.query("What are the applications?", chat_history=extended_history)
            assert "result" in response3

    def test_chat_history_with_main_document(self):
        """Test chat history works together with main document."""
        with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
            mock_handler = MagicMock()
            mock_llm = MagicMock()
            mock_llm.invoke.return_value = "Test response"
            mock_handler.get_llm.return_value = mock_llm
            mock_handler.get_system_prompt.return_value = "System prompt"
            mock_llm_handler.return_value = mock_handler

            pipeline = RAGPipeline()
            history = [
                {"role": "user", "content": "What is your background?"},
                {"role": "assistant", "content": "I have experience in AI."},
            ]

            response = pipeline.query("Tell me more about your projects", chat_history=history)
            assert "result" in response
            assert isinstance(response["result"], str)


class TestAppChatHistoryHelpers:
    """Test chat history helper functions from app.py."""

    def test_extract_chat_history_empty(self):
        """Test extract_chat_history with empty messages."""
        try:
            from app import extract_chat_history
        except ImportError:
            pytest.skip("Cannot import app module (streamlit may not be available)")

        result = extract_chat_history([])
        assert result == []

        result = extract_chat_history(None)
        assert result == []

    def test_extract_chat_history_single_message(self):
        """Test extract_chat_history with single message."""
        try:
            from app import extract_chat_history
        except ImportError:
            pytest.skip("Cannot import app module (streamlit may not be available)")

        messages = [{"role": "user", "content": "Hello"}]
        result = extract_chat_history(messages, exclude_last=True)
        assert result == []

        result = extract_chat_history(messages, exclude_last=False)
        assert result == [{"role": "user", "content": "Hello"}]

    def test_extract_chat_history_multiple_messages(self):
        """Test extract_chat_history with multiple messages."""
        try:
            from app import extract_chat_history
        except ImportError:
            pytest.skip("Cannot import app module (streamlit may not be available)")

        messages = [
            {"role": "user", "content": "Hello"},
            {"role": "assistant", "content": "Hi!"},
            {"role": "user", "content": "How are you?"},
        ]

        # Exclude last message
        result = extract_chat_history(messages, exclude_last=True)
        assert len(result) == 2
        assert result[0]["role"] == "user"
        assert result[1]["role"] == "assistant"

        # Include last message
        result = extract_chat_history(messages, exclude_last=False)
        assert len(result) == 3

    def test_extract_chat_history_filters_invalid_roles(self):
        """Test extract_chat_history filters out invalid roles."""
        try:
            from app import extract_chat_history
        except ImportError:
            pytest.skip("Cannot import app module (streamlit may not be available)")

        messages = [
            {"role": "user", "content": "Hello"},
            {"role": "system", "content": "System message"},
            {"role": "assistant", "content": "Hi!"},
        ]

        result = extract_chat_history(messages, exclude_last=False)
        assert len(result) == 2
        assert result[0]["role"] == "user"
        assert result[1]["role"] == "assistant"

    def test_extract_chat_history_filters_empty_content(self):
        """Test extract_chat_history filters out empty content."""
        try:
            from app import extract_chat_history
        except ImportError:
            pytest.skip("Cannot import app module (streamlit may not be available)")

        messages = [
            {"role": "user", "content": "Hello"},
            {"role": "assistant", "content": ""},
            {"role": "user", "content": "How are you?"},
        ]

        result = extract_chat_history(messages, exclude_last=False)
        assert len(result) == 2
        assert result[0]["role"] == "user"
        assert result[1]["role"] == "user"

    def test_truncate_chat_history_empty(self):
        """Test truncate_chat_history with empty history."""
        try:
            from app import truncate_chat_history
        except ImportError:
            pytest.skip("Cannot import app module (streamlit may not be available)")

        result = truncate_chat_history([])
        assert result == []

        result = truncate_chat_history([], max_turns=5)
        assert result == []

    def test_truncate_chat_history_no_limits(self):
        """Test truncate_chat_history with no limits."""
        try:
            from app import truncate_chat_history
        except ImportError:
            pytest.skip("Cannot import app module (streamlit may not be available)")

        history = [
            {"role": "user", "content": "Question 1"},
            {"role": "assistant", "content": "Answer 1"},
            {"role": "user", "content": "Question 2"},
            {"role": "assistant", "content": "Answer 2"},
        ]

        result = truncate_chat_history(history)
        assert len(result) == 4
        assert result == history

    def test_truncate_chat_history_by_turns(self):
        """Test truncate_chat_history by turn count."""
        try:
            from app import truncate_chat_history
        except ImportError:
            pytest.skip("Cannot import app module (streamlit may not be available)")

        history = [
            {"role": "user", "content": "Question 1"},
            {"role": "assistant", "content": "Answer 1"},
            {"role": "user", "content": "Question 2"},
            {"role": "assistant", "content": "Answer 2"},
            {"role": "user", "content": "Question 3"},
            {"role": "assistant", "content": "Answer 3"},
        ]

        # Keep only 2 most recent turns (4 messages)
        result = truncate_chat_history(history, max_turns=2)
        assert len(result) == 4
        assert result[0]["content"] == "Question 2"
        assert result[-1]["content"] == "Answer 3"

    def test_truncate_chat_history_by_tokens(self):
        """Test truncate_chat_history by token limit."""
        try:
            from app import truncate_chat_history
        except ImportError:
            pytest.skip("Cannot import app module (streamlit may not be available)")

        # Create history with varying message lengths
        history = [
            {"role": "user", "content": "Short question"},
            {"role": "assistant", "content": "Short answer"},
            {"role": "user", "content": "This is a much longer question that will use more tokens"},
            {
                "role": "assistant",
                "content": "This is a much longer answer that will also use more tokens",
            },
        ]

        # Truncate to a small token limit (should keep only recent messages that fit)
        result = truncate_chat_history(history, max_tokens=50)
        assert len(result) <= len(history)
        # Should keep most recent messages that fit within token limit

    def test_truncate_chat_history_by_turns_and_tokens(self):
        """Test truncate_chat_history with both turn and token limits."""
        try:
            from app import truncate_chat_history
        except ImportError:
            pytest.skip("Cannot import app module (streamlit may not be available)")

        history = [
            {"role": "user", "content": "Question 1"},
            {"role": "assistant", "content": "Answer 1"},
            {"role": "user", "content": "Question 2"},
            {"role": "assistant", "content": "Answer 2"},
        ]

        # First apply turn limit, then token limit
        result = truncate_chat_history(history, max_turns=1, max_tokens=1000)
        assert len(result) <= 2  # 1 turn = 2 messages

    def test_truncate_chat_history_zero_limits(self):
        """Test truncate_chat_history with zero limits."""
        try:
            from app import truncate_chat_history
        except ImportError:
            pytest.skip("Cannot import app module (streamlit may not be available)")

        history = [
            {"role": "user", "content": "Question 1"},
            {"role": "assistant", "content": "Answer 1"},
        ]

        # Zero limits don't truncate (function only truncates if > 0)
        # So it returns the original history unchanged
        result = truncate_chat_history(history, max_turns=0)
        assert result == history  # Returns original when limit is 0

        result = truncate_chat_history(history, max_tokens=0)
        assert result == history  # Returns original when limit is 0