File size: 17,705 Bytes
330b6e4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""

Integration tests for ChatAgent service.



Tests the complete message processing flow including session management,

language context switching, chat history, and LLM integration.

"""

import pytest
import os
from unittest.mock import Mock, patch, MagicMock
from datetime import datetime, timedelta
import redis

from chat_agent.services.chat_agent import ChatAgent, ChatAgentError, create_chat_agent
from chat_agent.services.groq_client import GroqClient, ChatMessage, LanguageContext
from chat_agent.services.language_context import LanguageContextManager
from chat_agent.services.session_manager import SessionManager, SessionNotFoundError
from chat_agent.services.chat_history import ChatHistoryManager
from chat_agent.models.chat_session import ChatSession
from chat_agent.models.message import Message
from chat_agent.models.base import db


@pytest.fixture
def mock_redis():
    """Mock Redis client for testing."""
    return Mock(spec=redis.Redis)


@pytest.fixture
def mock_groq_client():
    """Mock Groq client for testing."""
    client = Mock(spec=GroqClient)
    client.generate_response.return_value = "Test response from LLM"
    client.stream_response.return_value = iter(["Test ", "streaming ", "response"])
    client.get_model_info.return_value = {
        "model": "mixtral-8x7b-32768",
        "temperature": 0.7,
        "max_tokens": 2048
    }
    return client


@pytest.fixture
def language_context_manager():
    """Create language context manager for testing."""
    return LanguageContextManager()


@pytest.fixture
def mock_session_manager():
    """Mock session manager for testing."""
    manager = Mock(spec=SessionManager)
    
    # Create a mock session
    mock_session = Mock(spec=ChatSession)
    mock_session.id = "test-session-id"
    mock_session.user_id = "test-user-id"
    mock_session.language = "python"
    mock_session.created_at = datetime.utcnow()
    mock_session.last_active = datetime.utcnow()
    mock_session.message_count = 0
    mock_session.is_active = True
    mock_session.session_metadata = {}
    
    manager.get_session.return_value = mock_session
    manager.update_session_activity.return_value = None
    manager.increment_message_count.return_value = None
    manager.set_session_language.return_value = None
    
    return manager


@pytest.fixture
def mock_chat_history_manager():
    """Mock chat history manager for testing."""
    manager = Mock(spec=ChatHistoryManager)
    
    # Create mock messages
    mock_user_message = Mock(spec=Message)
    mock_user_message.id = "user-msg-id"
    mock_user_message.session_id = "test-session-id"
    mock_user_message.role = "user"
    mock_user_message.content = "Test user message"
    mock_user_message.language = "python"
    mock_user_message.timestamp = datetime.utcnow()
    mock_user_message.message_metadata = {}
    
    mock_assistant_message = Mock(spec=Message)
    mock_assistant_message.id = "assistant-msg-id"
    mock_assistant_message.session_id = "test-session-id"
    mock_assistant_message.role = "assistant"
    mock_assistant_message.content = "Test assistant response"
    mock_assistant_message.language = "python"
    mock_assistant_message.timestamp = datetime.utcnow()
    mock_assistant_message.message_metadata = {}
    
    manager.store_message.side_effect = [mock_user_message, mock_assistant_message]
    manager.get_recent_history.return_value = [mock_user_message]
    manager.get_message_count.return_value = 2
    manager.get_cache_stats.return_value = {
        'session_id': 'test-session-id',
        'cached_messages': 2,
        'cache_ttl': 3600,
        'max_cache_size': 20
    }
    
    return manager
@pyte
st.fixture
def chat_agent(mock_groq_client, language_context_manager, mock_session_manager, mock_chat_history_manager):
    """Create ChatAgent instance for testing."""
    return ChatAgent(
        groq_client=mock_groq_client,
        language_context_manager=language_context_manager,
        session_manager=mock_session_manager,
        chat_history_manager=mock_chat_history_manager
    )


class TestChatAgentMessageProcessing:
    """Test complete message processing workflow."""
    
    def test_process_message_success(self, chat_agent, mock_groq_client, mock_session_manager, mock_chat_history_manager):
        """Test successful message processing flow."""
        # Arrange
        session_id = "test-session-id"
        message = "How do I create a list in Python?"
        
        # Act
        result = chat_agent.process_message(session_id, message)
        
        # Assert
        assert result['response'] == "Test response from LLM"
        assert result['language'] == "python"
        assert result['session_id'] == session_id
        assert 'message_id' in result
        assert 'timestamp' in result
        assert 'metadata' in result
        
        # Verify service calls
        mock_session_manager.get_session.assert_called_once_with(session_id)
        mock_session_manager.update_session_activity.assert_called_once_with(session_id)
        mock_session_manager.increment_message_count.assert_called_once_with(session_id)
        
        # Verify message storage (user message, then assistant message)
        assert mock_chat_history_manager.store_message.call_count == 2
        
        # Verify LLM call
        mock_groq_client.generate_response.assert_called_once()
    
    def test_process_message_with_language_override(self, chat_agent, mock_groq_client, mock_session_manager):
        """Test message processing with language override."""
        # Arrange
        session_id = "test-session-id"
        message = "How do I create an array in JavaScript?"
        language = "javascript"
        
        # Act
        result = chat_agent.process_message(session_id, message, language)
        
        # Assert
        assert result['language'] == "javascript"
        mock_session_manager.set_session_language.assert_called_once_with(session_id, language)
    
    def test_process_message_invalid_session(self, chat_agent, mock_session_manager):
        """Test message processing with invalid session."""
        # Arrange
        mock_session_manager.get_session.side_effect = SessionNotFoundError("Session not found")
        
        # Act & Assert
        with pytest.raises(ChatAgentError, match="Session error"):
            chat_agent.process_message("invalid-session", "test message")
    
    def test_process_message_invalid_language(self, chat_agent, mock_session_manager):
        """Test message processing with invalid language falls back to session default."""
        # Arrange
        session_id = "test-session-id"
        message = "Test message"
        invalid_language = "invalid-lang"
        
        # Act
        result = chat_agent.process_message(session_id, message, invalid_language)
        
        # Assert - should fall back to session default (python)
        assert result['language'] == "python"


class TestChatAgentLanguageSwitching:
    """Test language switching functionality."""
    
    def test_switch_language_success(self, chat_agent, mock_session_manager, mock_chat_history_manager):
        """Test successful language switching."""
        # Arrange
        session_id = "test-session-id"
        new_language = "javascript"
        
        # Act
        result = chat_agent.switch_language(session_id, new_language)
        
        # Assert
        assert result['success'] is True
        assert result['new_language'] == new_language
        assert result['previous_language'] == "python"
        assert result['session_id'] == session_id
        assert 'message' in result
        assert 'timestamp' in result
        
        # Verify service calls
        mock_session_manager.get_session.assert_called_once_with(session_id)
        mock_session_manager.set_session_language.assert_called_once_with(session_id, new_language)
        
        # Verify switch message is stored
        mock_chat_history_manager.store_message.assert_called_once()
        store_call = mock_chat_history_manager.store_message.call_args
        assert store_call[1]['role'] == 'assistant'
        assert store_call[1]['language'] == new_language
        assert 'language_switch' in store_call[1]['message_metadata']['type']
    
    def test_switch_language_invalid_language(self, chat_agent):
        """Test language switching with invalid language."""
        # Arrange
        session_id = "test-session-id"
        invalid_language = "invalid-lang"
        
        # Act & Assert
        with pytest.raises(ChatAgentError, match="Unsupported language"):
            chat_agent.switch_language(session_id, invalid_language)
    
    def test_switch_language_invalid_session(self, chat_agent, mock_session_manager):
        """Test language switching with invalid session."""
        # Arrange
        mock_session_manager.get_session.side_effect = SessionNotFoundError("Session not found")
        
        # Act & Assert
        with pytest.raises(ChatAgentError, match="Session error"):
            chat_agent.switch_language("invalid-session", "javascript")
cla
ss TestChatAgentStreaming:
    """Test streaming response functionality."""
    
    def test_stream_response_success(self, chat_agent, mock_groq_client, mock_session_manager, mock_chat_history_manager):
        """Test successful streaming response."""
        # Arrange
        session_id = "test-session-id"
        message = "Explain Python functions"
        
        # Act
        stream_results = list(chat_agent.stream_response(session_id, message))
        
        # Assert
        assert len(stream_results) >= 5  # start + 3 chunks + complete
        
        # Check start event
        start_event = stream_results[0]
        assert start_event['type'] == 'start'
        assert start_event['session_id'] == session_id
        assert start_event['language'] == 'python'
        
        # Check chunk events
        chunk_events = [event for event in stream_results if event['type'] == 'chunk']
        assert len(chunk_events) == 3
        assert chunk_events[0]['content'] == "Test "
        assert chunk_events[1]['content'] == "streaming "
        assert chunk_events[2]['content'] == "response"
        
        # Check complete event
        complete_event = stream_results[-1]
        assert complete_event['type'] == 'complete'
        assert complete_event['session_id'] == session_id
        assert complete_event['total_chunks'] == 3
        assert 'processing_time' in complete_event
        
        # Verify service calls
        mock_session_manager.get_session.assert_called_once_with(session_id)
        mock_session_manager.increment_message_count.assert_called_once_with(session_id)
        
        # Verify message storage
        assert mock_chat_history_manager.store_message.call_count == 2
    
    def test_stream_response_error(self, chat_agent, mock_session_manager):
        """Test streaming response with session error."""
        # Arrange
        mock_session_manager.get_session.side_effect = SessionNotFoundError("Session not found")
        
        # Act
        stream_results = list(chat_agent.stream_response("invalid-session", "test message"))
        
        # Assert
        assert len(stream_results) == 1
        error_event = stream_results[0]
        assert error_event['type'] == 'error'
        assert 'Session error' in error_event['error']


class TestChatAgentHistory:
    """Test chat history retrieval functionality."""
    
    def test_get_chat_history_success(self, chat_agent, mock_session_manager, mock_chat_history_manager):
        """Test successful chat history retrieval."""
        # Arrange
        session_id = "test-session-id"
        limit = 5
        
        # Act
        history = chat_agent.get_chat_history(session_id, limit)
        
        # Assert
        assert isinstance(history, list)
        assert len(history) == 1  # Based on mock setup
        
        message = history[0]
        assert 'id' in message
        assert 'role' in message
        assert 'content' in message
        assert 'language' in message
        assert 'timestamp' in message
        assert 'metadata' in message
        
        # Verify service calls
        mock_session_manager.get_session.assert_called_once_with(session_id)
        mock_chat_history_manager.get_recent_history.assert_called_once_with(session_id, limit)
    
    def test_get_chat_history_invalid_session(self, chat_agent, mock_session_manager):
        """Test chat history retrieval with invalid session."""
        # Arrange
        mock_session_manager.get_session.side_effect = SessionNotFoundError("Session not found")
        
        # Act & Assert
        with pytest.raises(ChatAgentError, match="Session error"):
            chat_agent.get_chat_history("invalid-session")


class TestChatAgentSessionInfo:
    """Test session information retrieval."""
    
    def test_get_session_info_success(self, chat_agent, mock_session_manager, mock_chat_history_manager, language_context_manager):
        """Test successful session info retrieval."""
        # Arrange
        session_id = "test-session-id"
        
        # Act
        info = chat_agent.get_session_info(session_id)
        
        # Assert
        assert 'session' in info
        assert 'language_context' in info
        assert 'statistics' in info
        assert 'supported_languages' in info
        
        # Check session info
        session_info = info['session']
        assert session_info['id'] == session_id
        assert session_info['language'] == 'python'
        assert session_info['is_active'] is True
        
        # Check statistics
        stats = info['statistics']
        assert 'total_messages' in stats
        assert 'session_message_count' in stats
        assert 'cache_stats' in stats
        
        # Check supported languages
        assert isinstance(info['supported_languages'], list)
        assert 'python' in info['supported_languages']
        assert 'javascript' in info['supported_languages']
        
        # Verify service calls
        mock_session_manager.get_session.assert_called_once_with(session_id)
        mock_chat_history_manager.get_message_count.assert_called_once_with(session_id)
        mock_chat_history_manager.get_cache_stats.assert_called_once_with(session_id)


class TestChatAgentFactory:
    """Test ChatAgent factory function."""
    
    def test_create_chat_agent(self, mock_groq_client, language_context_manager, mock_session_manager, mock_chat_history_manager):
        """Test ChatAgent factory function."""
        # Act
        agent = create_chat_agent(
            mock_groq_client,
            language_context_manager,
            mock_session_manager,
            mock_chat_history_manager
        )
        
        # Assert
        assert isinstance(agent, ChatAgent)
        assert agent.groq_client == mock_groq_client
        assert agent.language_context_manager == language_context_manager
        assert agent.session_manager == mock_session_manager
        assert agent.chat_history_manager == mock_chat_history_manager


class TestChatAgentIntegrationFlow:
    """Test complete integration flows."""
    
    def test_complete_conversation_flow(self, chat_agent, mock_groq_client, mock_session_manager, mock_chat_history_manager):
        """Test a complete conversation flow with multiple messages."""
        session_id = "test-session-id"
        
        # First message
        result1 = chat_agent.process_message(session_id, "What is Python?")
        assert result1['language'] == 'python'
        
        # Switch language
        switch_result = chat_agent.switch_language(session_id, "javascript")
        assert switch_result['success'] is True
        assert switch_result['new_language'] == 'javascript'
        
        # Second message in new language
        result2 = chat_agent.process_message(session_id, "What is JavaScript?")
        assert result2['language'] == 'javascript'
        
        # Get session info
        info = chat_agent.get_session_info(session_id)
        assert info['session']['language'] == 'javascript'
        
        # Verify all service interactions occurred
        assert mock_session_manager.get_session.call_count >= 3
        assert mock_session_manager.set_session_language.call_count >= 1
        assert mock_chat_history_manager.store_message.call_count >= 5  # 2 conversations + 1 switch message
    
    def test_streaming_with_language_switch(self, chat_agent, mock_session_manager, mock_chat_history_manager):
        """Test streaming response after language switch."""
        session_id = "test-session-id"
        
        # Switch language first
        chat_agent.switch_language(session_id, "java")
        
        # Stream response in new language
        stream_results = list(chat_agent.stream_response(session_id, "Explain Java classes", "java"))
        
        # Verify language context is maintained
        start_event = stream_results[0]
        assert start_event['language'] == 'java'
        
        # Verify session language was set
        mock_session_manager.set_session_language.assert_called_with(session_id, "java")