File size: 15,649 Bytes
6a3de9e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Tests to verify the AI agent's responses and logic processing.

These tests check how the agent responds to different types of commands
and verify the response format and content.
"""
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from ai.agents.todo_agent import TodoAgent
from models.conversation import Conversation
from uuid import UUID, uuid4


@pytest.fixture
def todo_agent():
    """Create a TodoAgent instance for testing."""
    agent = TodoAgent()
    # Mock the internal components to avoid actual API calls
    agent.client = MagicMock()
    agent.config = MagicMock()
    return agent


@pytest.mark.asyncio
async def test_agent_add_task_command_response(todo_agent):
    """Test the agent's response to add task commands."""
    user_id = "test-user-123"
    message = "Add a task: Buy groceries"
    conversation = MagicMock()
    conversation.id = uuid4()

    # Mock the runner response for add_task
    mock_result = MagicMock()
    mock_result.final_output = "I've added the task 'Buy groceries' for you."

    with patch('ai.agents.todo_agent.Runner') as mock_runner:
        mock_runner.run = AsyncMock(return_value=mock_result)

        result = await todo_agent.process_message(user_id, message, conversation)

        # Verify the response contains expected elements
        assert "response" in result
        assert "Buy groceries" in result["response"] or "added" in result["response"].lower()
        assert "conversation_id" in result
        assert "tool_calls" in result
        assert "requires_action" in result


@pytest.mark.asyncio
async def test_agent_list_tasks_command_response(todo_agent):
    """Test the agent's response to list tasks commands."""
    user_id = "test-user-123"
    message = "Show me my tasks"
    conversation = MagicMock()
    conversation.id = uuid4()

    # Mock the runner response for list_tasks
    mock_result = MagicMock()
    mock_result.final_output = "Here are your tasks: 1. Buy groceries, 2. Clean house"

    with patch('ai.agents.todo_agent.Runner') as mock_runner:
        mock_runner.run = AsyncMock(return_value=mock_result)

        result = await todo_agent.process_message(user_id, message, conversation)

        # Verify the response contains expected elements
        assert "response" in result
        assert "tasks" in result["response"].lower()
        assert "conversation_id" in result
        assert "tool_calls" in result
        assert "requires_action" in result


@pytest.mark.asyncio
async def test_agent_complete_task_command_response(todo_agent):
    """Test the agent's response to complete task commands."""
    user_id = "test-user-123"
    message = "Complete task 1"
    conversation = MagicMock()
    conversation.id = uuid4()

    # Mock the runner response for complete_task
    mock_result = MagicMock()
    mock_result.final_output = "I've marked task 1 as completed."

    with patch('ai.agents.todo_agent.Runner') as mock_runner:
        mock_runner.run = AsyncMock(return_value=mock_result)

        result = await todo_agent.process_message(user_id, message, conversation)

        # Verify the response contains expected elements
        assert "response" in result
        assert "completed" in result["response"].lower()
        assert "conversation_id" in result
        assert "tool_calls" in result
        assert "requires_action" in result


@pytest.mark.asyncio
async def test_agent_delete_task_command_response(todo_agent):
    """Test the agent's response to delete task commands."""
    user_id = "test-user-123"
    message = "Delete task 2"
    conversation = MagicMock()
    conversation.id = uuid4()

    # Mock the runner response for delete_task
    mock_result = MagicMock()
    mock_result.final_output = "I've deleted task 2 for you."

    with patch('ai.agents.todo_agent.Runner') as mock_runner:
        mock_runner.run = AsyncMock(return_value=mock_result)

        result = await todo_agent.process_message(user_id, message, conversation)

        # Verify the response contains expected elements
        assert "response" in result
        assert "deleted" in result["response"].lower()
        assert "conversation_id" in result
        assert "tool_calls" in result
        assert "requires_action" in result


@pytest.mark.asyncio
async def test_agent_update_task_command_response(todo_agent):
    """Test the agent's response to update task commands."""
    user_id = "test-user-123"
    message = "Update task 3: Change title to 'Updated task'"
    conversation = MagicMock()
    conversation.id = uuid4()

    # Mock the runner response for update_task
    mock_result = MagicMock()
    mock_result.final_output = "I've updated task 3 with the new title."

    with patch('ai.agents.todo_agent.Runner') as mock_runner:
        mock_runner.run = AsyncMock(return_value=mock_result)

        result = await todo_agent.process_message(user_id, message, conversation)

        # Verify the response contains expected elements
        assert "response" in result
        assert "updated" in result["response"].lower()
        assert "conversation_id" in result
        assert "tool_calls" in result
        assert "requires_action" in result


@pytest.mark.asyncio
async def test_agent_unrecognized_command_response(todo_agent):
    """Test the agent's response to unrecognized commands."""
    user_id = "test-user-123"
    message = "Random message that doesn't match any command"
    conversation = MagicMock()
    conversation.id = uuid4()

    # Mock the runner response for unrecognized commands
    mock_result = MagicMock()
    mock_result.final_output = "I'm sorry, I didn't understand that command. Could you please rephrase?"

    with patch('ai.agents.todo_agent.Runner') as mock_runner:
        mock_runner.run = AsyncMock(return_value=mock_result)

        result = await todo_agent.process_message(user_id, message, conversation)

        # Verify the response contains expected elements
        assert "response" in result
        assert "sorry" in result["response"].lower() or "understand" in result["response"].lower()
        assert "conversation_id" in result
        assert "tool_calls" in result
        assert "requires_action" in result


@pytest.mark.asyncio
async def test_agent_command_recognition_various_formats(todo_agent):
    """Test the agent's ability to recognize commands in various formats."""
    test_cases = [
        # Add task variations
        ("Add task: Buy groceries", "add_task"),
        ("Create a task: Clean house", "add_task"),
        ("Add new task - Walk the dog", "add_task"),
        ("Make task: Prepare dinner", "add_task"),

        # List tasks variations
        ("Show me my tasks", "list_tasks"),
        ("List my tasks", "list_tasks"),
        ("What tasks do I have?", "list_tasks"),
        ("Display my tasks", "list_tasks"),

        # Complete task variations
        ("Complete task 1", "complete_task"),
        ("Mark task 1 as done", "complete_task"),
        ("Finish task 2", "complete_task"),
        ("Set task 3 to completed", "complete_task"),

        # Delete task variations
        ("Delete task 1", "delete_task"),
        ("Remove task 2", "delete_task"),
        ("Cancel task 3", "delete_task"),
        ("Delete the first task", "delete_task"),

        # Update task variations
        ("Update task 1", "update_task"),
        ("Change task 2 details", "update_task"),
        ("Edit task 3", "update_task"),
        ("Modify task 4 title", "update_task"),
    ]

    for message, expected_command_type in test_cases:
        command = await todo_agent.recognize_command(message)
        assert command == expected_command_type, f"Failed for message: '{message}', expected: {expected_command_type}, got: {command}"


@pytest.mark.asyncio
async def test_agent_command_recognition_case_insensitive(todo_agent):
    """Test the agent's ability to recognize commands regardless of case."""
    test_cases = [
        ("ADD A TASK: BUY GROCERIES", "add_task"),
        ("show me my TASKS", "list_tasks"),
        ("Complete TASK 1", "complete_task"),
        ("DELETE task 2", "delete_task"),
        ("update TaSk 3", "update_task"),
    ]

    for message, expected_command_type in test_cases:
        command = await todo_agent.recognize_command(message)
        assert command == expected_command_type, f"Failed for case-insensitive test: '{message}', expected: {expected_command_type}, got: {command}"


def test_agent_task_extraction_various_formats(todo_agent):
    """Test the agent's ability to extract task details from various message formats."""
    test_cases = [
        ("Add task: Buy groceries", {"title": "Buy groceries"}),
        ("Create: Clean the house", {"title": "Clean the house"}),
        ("Task - Walk the dog", {"title": "Walk the dog"}),
        ("New task: Prepare dinner with ingredients", {"title": "Prepare dinner with ingredients"}),
        ("Add: Simple task", {"title": "Simple task"}),
    ]

    for message, expected in test_cases:
        details = todo_agent.extract_task_details(message)
        assert "title" in details
        assert expected["title"] in details["title"]


@pytest.mark.asyncio
async def test_agent_process_message_with_different_users(todo_agent):
    """Test that the agent handles different users correctly."""
    test_users = ["user-1", "user-2", "user-3", "user-4", "user-5"]
    message = "Add a task: Test task for user isolation"

    for user_id in test_users:
        conversation = MagicMock()
        conversation.id = uuid4()

        # Mock the runner response
        mock_result = MagicMock()
        mock_result.final_output = f"Task added successfully for {user_id}"

        with patch('ai.agents.todo_agent.Runner') as mock_runner:
            mock_runner.run = AsyncMock(return_value=mock_result)

            result = await todo_agent.process_message(user_id, message, conversation)

            # Verify each user gets a proper response
            assert "response" in result
            assert user_id in result["response"]


@pytest.mark.asyncio
async def test_agent_process_message_returns_correct_structure(todo_agent):
    """Test that the agent always returns the correct response structure."""
    user_id = "test-user-123"
    message = "Add a task: Test structure"
    conversation = MagicMock()
    conversation.id = uuid4()

    # Mock the runner response
    mock_result = MagicMock()
    mock_result.final_output = "Task processed successfully"

    with patch('ai.agents.todo_agent.Runner') as mock_runner:
        mock_runner.run = AsyncMock(return_value=mock_result)

        result = await todo_agent.process_message(user_id, message, conversation)

        # Verify the response structure
        assert isinstance(result, dict)
        assert "response" in result
        assert "conversation_id" in result
        assert "tool_calls" in result
        assert "requires_action" in result

        # Verify types
        assert isinstance(result["response"], str)
        assert isinstance(result["conversation_id"], str)
        assert isinstance(result["tool_calls"], list)
        assert isinstance(result["requires_action"], bool)


@pytest.mark.asyncio
async def test_agent_error_handling_in_process_message(todo_agent):
    """Test that the agent handles errors gracefully in process_message."""
    user_id = "test-user-123"
    message = "Add a task: Test error handling"
    conversation = MagicMock()
    conversation.id = uuid4()

    # Mock the runner to raise an exception
    with patch('ai.agents.todo_agent.Runner') as mock_runner:
        mock_runner.run = AsyncMock(side_effect=Exception("API Error"))

        result = await todo_agent.process_message(user_id, message, conversation)

        # Verify error handling response structure
        assert isinstance(result, dict)
        assert "response" in result
        assert "conversation_id" in result
        assert "tool_calls" in result
        assert "requires_action" in result

        # Verify error message is in the response
        assert "error" in result["response"].lower()


@pytest.mark.asyncio
async def test_agent_process_message_with_complex_commands(todo_agent):
    """Test the agent's response to complex commands with multiple parts."""
    user_id = "test-user-123"
    test_cases = [
        "Add a new task with high priority: Buy groceries by Friday",
        "Create a task to clean the house - it's urgent",
        "I need to add a task: Finish the project report by tomorrow",
        "Can you create a task for me: Schedule dentist appointment next week"
    ]

    for message in test_cases:
        conversation = MagicMock()
        conversation.id = uuid4()

        # Mock the runner response
        mock_result = MagicMock()
        mock_result.final_output = "Task processed successfully"

        with patch('ai.agents.todo_agent.Runner') as mock_runner:
            mock_runner.run = AsyncMock(return_value=mock_result)

            result = await todo_agent.process_message(user_id, message, conversation)

            # Verify the response structure
            assert "response" in result
            assert "conversation_id" in result
            assert "tool_calls" in result
            assert "requires_action" in result


@pytest.mark.asyncio
async def test_agent_command_recognition_with_context(todo_agent):
    """Test the agent's command recognition with context clues."""
    test_cases = [
        # Commands with context
        ("Could you please add a task: Buy milk", "add_task"),
        ("Can you show me what tasks I have?", "list_tasks"),
        ("I've finished task 1, please mark it as done", "complete_task"),
        ("I no longer need task 2, please remove it", "delete_task"),
        ("I need to change the due date for task 3", "update_task"),
    ]

    for message, expected_command_type in test_cases:
        command = await todo_agent.recognize_command(message)
        assert command == expected_command_type, f"Failed for contextual message: '{message}', expected: {expected_command_type}, got: {command}"


@pytest.mark.asyncio
async def test_agent_response_quality_for_different_scenarios(todo_agent):
    """Test the quality of agent responses for different scenarios."""
    scenarios = [
        {
            "message": "Add a task: Buy groceries",
            "expected_elements": ["task", "groceries", "add"]
        },
        {
            "message": "List all my tasks",
            "expected_elements": ["tasks", "list", "show"]
        },
        {
            "message": "Complete task 123",
            "expected_elements": ["complete", "task", "123"]
        },
        {
            "message": "Update task 456 to have high priority",
            "expected_elements": ["update", "task", "456", "priority"]
        }
    ]

    for scenario in scenarios:
        user_id = "test-user-123"
        conversation = MagicMock()
        conversation.id = uuid4()

        # Mock the runner response
        mock_result = MagicMock()
        mock_result.final_output = f"Processed: {scenario['message']}"

        with patch('ai.agents.todo_agent.Runner') as mock_runner:
            mock_runner.run = AsyncMock(return_value=mock_result)

            result = await todo_agent.process_message(user_id, scenario["message"], conversation)

            # Check that response contains expected elements
            response_lower = result["response"].lower()
            for element in scenario["expected_elements"]:
                assert element.lower() in response_lower


if __name__ == "__main__":
    pytest.main([__file__])