iflow-mcp_democratize-technology-chronos-mcp 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chronos_mcp/__init__.py +5 -0
- chronos_mcp/__main__.py +9 -0
- chronos_mcp/accounts.py +410 -0
- chronos_mcp/bulk.py +946 -0
- chronos_mcp/caldav_utils.py +149 -0
- chronos_mcp/calendars.py +204 -0
- chronos_mcp/config.py +187 -0
- chronos_mcp/credentials.py +190 -0
- chronos_mcp/events.py +515 -0
- chronos_mcp/exceptions.py +477 -0
- chronos_mcp/journals.py +477 -0
- chronos_mcp/logging_config.py +23 -0
- chronos_mcp/models.py +202 -0
- chronos_mcp/py.typed +0 -0
- chronos_mcp/rrule.py +259 -0
- chronos_mcp/search.py +315 -0
- chronos_mcp/server.py +121 -0
- chronos_mcp/tasks.py +518 -0
- chronos_mcp/tools/__init__.py +29 -0
- chronos_mcp/tools/accounts.py +151 -0
- chronos_mcp/tools/base.py +59 -0
- chronos_mcp/tools/bulk.py +557 -0
- chronos_mcp/tools/calendars.py +142 -0
- chronos_mcp/tools/events.py +698 -0
- chronos_mcp/tools/journals.py +310 -0
- chronos_mcp/tools/tasks.py +414 -0
- chronos_mcp/utils.py +163 -0
- chronos_mcp/validation.py +636 -0
- iflow_mcp_democratize_technology_chronos_mcp-2.0.0.dist-info/METADATA +299 -0
- iflow_mcp_democratize_technology_chronos_mcp-2.0.0.dist-info/RECORD +68 -0
- iflow_mcp_democratize_technology_chronos_mcp-2.0.0.dist-info/WHEEL +5 -0
- iflow_mcp_democratize_technology_chronos_mcp-2.0.0.dist-info/entry_points.txt +2 -0
- iflow_mcp_democratize_technology_chronos_mcp-2.0.0.dist-info/licenses/LICENSE +21 -0
- iflow_mcp_democratize_technology_chronos_mcp-2.0.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/conftest.py +91 -0
- tests/unit/__init__.py +0 -0
- tests/unit/test_accounts.py +380 -0
- tests/unit/test_accounts_ssrf.py +134 -0
- tests/unit/test_base.py +135 -0
- tests/unit/test_bulk.py +380 -0
- tests/unit/test_bulk_create.py +408 -0
- tests/unit/test_bulk_delete.py +341 -0
- tests/unit/test_bulk_resource_limits.py +74 -0
- tests/unit/test_caldav_utils.py +300 -0
- tests/unit/test_calendars.py +286 -0
- tests/unit/test_config.py +111 -0
- tests/unit/test_config_validation.py +128 -0
- tests/unit/test_credentials_security.py +189 -0
- tests/unit/test_cryptography_security.py +178 -0
- tests/unit/test_events.py +536 -0
- tests/unit/test_exceptions.py +58 -0
- tests/unit/test_journals.py +1097 -0
- tests/unit/test_models.py +95 -0
- tests/unit/test_race_conditions.py +202 -0
- tests/unit/test_recurring_events.py +156 -0
- tests/unit/test_rrule.py +217 -0
- tests/unit/test_search.py +372 -0
- tests/unit/test_search_advanced.py +333 -0
- tests/unit/test_server_input_validation.py +219 -0
- tests/unit/test_ssrf_protection.py +505 -0
- tests/unit/test_tasks.py +918 -0
- tests/unit/test_thread_safety.py +301 -0
- tests/unit/test_tools_journals.py +617 -0
- tests/unit/test_tools_tasks.py +968 -0
- tests/unit/test_url_validation_security.py +234 -0
- tests/unit/test_utils.py +180 -0
- tests/unit/test_validation.py +983 -0
|
@@ -0,0 +1,968 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comprehensive unit tests for chronos_mcp/tools/tasks.py module
|
|
3
|
+
Tests all MCP tool functions for 100% coverage with defensive programming patterns
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import uuid
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from unittest.mock import MagicMock, Mock, patch, AsyncMock
|
|
9
|
+
from typing import Dict, Any
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from chronos_mcp.tools.tasks import (
|
|
13
|
+
create_task,
|
|
14
|
+
list_tasks,
|
|
15
|
+
update_task,
|
|
16
|
+
delete_task,
|
|
17
|
+
register_task_tools,
|
|
18
|
+
_managers,
|
|
19
|
+
)
|
|
20
|
+
from chronos_mcp.models import TaskStatus
|
|
21
|
+
from chronos_mcp.exceptions import (
|
|
22
|
+
CalendarNotFoundError,
|
|
23
|
+
EventNotFoundError,
|
|
24
|
+
EventCreationError,
|
|
25
|
+
ValidationError,
|
|
26
|
+
ChronosError,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestTaskToolsComprehensive:
|
|
31
|
+
"""Test MCP task tool functions with comprehensive coverage"""
|
|
32
|
+
|
|
33
|
+
@pytest.fixture
|
|
34
|
+
def mock_managers(self):
|
|
35
|
+
"""Mock managers for dependency injection"""
|
|
36
|
+
task_manager = Mock()
|
|
37
|
+
return {"task_manager": task_manager}
|
|
38
|
+
|
|
39
|
+
@pytest.fixture
|
|
40
|
+
def sample_task(self):
|
|
41
|
+
"""Sample task object for testing"""
|
|
42
|
+
task = Mock()
|
|
43
|
+
task.uid = "task-123"
|
|
44
|
+
task.summary = "Test Task"
|
|
45
|
+
task.description = "Test description"
|
|
46
|
+
task.due = datetime(2025, 12, 31, 23, 59, tzinfo=timezone.utc)
|
|
47
|
+
task.priority = 5
|
|
48
|
+
task.status = TaskStatus.NEEDS_ACTION
|
|
49
|
+
task.percent_complete = 0
|
|
50
|
+
task.related_to = ["related-1", "related-2"]
|
|
51
|
+
return task
|
|
52
|
+
|
|
53
|
+
@pytest.fixture
|
|
54
|
+
def setup_managers(self, mock_managers):
|
|
55
|
+
"""Setup _managers module variable"""
|
|
56
|
+
original = _managers.copy()
|
|
57
|
+
_managers.clear()
|
|
58
|
+
_managers.update(mock_managers)
|
|
59
|
+
yield
|
|
60
|
+
_managers.clear()
|
|
61
|
+
_managers.update(original)
|
|
62
|
+
|
|
63
|
+
# CREATE_TASK TOOL TESTS
|
|
64
|
+
|
|
65
|
+
@pytest.mark.asyncio
|
|
66
|
+
async def test_create_task_minimal_success(self, setup_managers, sample_task):
|
|
67
|
+
"""Test create_task with minimal required parameters"""
|
|
68
|
+
_managers["task_manager"].create_task.return_value = sample_task
|
|
69
|
+
|
|
70
|
+
result = await create_task.fn(
|
|
71
|
+
calendar_uid="cal-123",
|
|
72
|
+
summary="Test Task",
|
|
73
|
+
description=None,
|
|
74
|
+
due=None,
|
|
75
|
+
priority=None,
|
|
76
|
+
status="NEEDS-ACTION",
|
|
77
|
+
related_to=None,
|
|
78
|
+
account=None,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
assert result["success"] is True
|
|
82
|
+
assert result["task"]["uid"] == "task-123"
|
|
83
|
+
assert result["task"]["summary"] == "Test Task"
|
|
84
|
+
assert "request_id" in result
|
|
85
|
+
_managers["task_manager"].create_task.assert_called_once()
|
|
86
|
+
|
|
87
|
+
@pytest.mark.asyncio
|
|
88
|
+
async def test_create_task_full_parameters(self, setup_managers, sample_task):
|
|
89
|
+
"""Test create_task with all parameters provided"""
|
|
90
|
+
_managers["task_manager"].create_task.return_value = sample_task
|
|
91
|
+
|
|
92
|
+
result = await create_task.fn(
|
|
93
|
+
calendar_uid="cal-123",
|
|
94
|
+
summary="Full Test Task",
|
|
95
|
+
description="Full description",
|
|
96
|
+
due="2025-12-31T23:59:00Z",
|
|
97
|
+
priority=3,
|
|
98
|
+
status="IN-PROCESS",
|
|
99
|
+
related_to=["related-1", "related-2"],
|
|
100
|
+
account="test_account",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
assert result["success"] is True
|
|
104
|
+
assert result["task"]["status"] == "NEEDS-ACTION" # from sample_task
|
|
105
|
+
_managers["task_manager"].create_task.assert_called_once()
|
|
106
|
+
|
|
107
|
+
@pytest.mark.asyncio
|
|
108
|
+
async def test_create_task_priority_string_conversion(
|
|
109
|
+
self, setup_managers, sample_task
|
|
110
|
+
):
|
|
111
|
+
"""Test create_task converts string priority to int"""
|
|
112
|
+
_managers["task_manager"].create_task.return_value = sample_task
|
|
113
|
+
|
|
114
|
+
result = await create_task.fn(
|
|
115
|
+
calendar_uid="cal-123",
|
|
116
|
+
summary="Test Task",
|
|
117
|
+
description=None,
|
|
118
|
+
due=None,
|
|
119
|
+
priority="5", # String that should convert to int
|
|
120
|
+
status="NEEDS-ACTION",
|
|
121
|
+
related_to=None,
|
|
122
|
+
account=None,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
assert result["success"] is True
|
|
126
|
+
_managers["task_manager"].create_task.assert_called_once()
|
|
127
|
+
|
|
128
|
+
@pytest.mark.asyncio
|
|
129
|
+
async def test_create_task_invalid_priority_string(self, setup_managers):
|
|
130
|
+
"""Test create_task handles invalid priority string"""
|
|
131
|
+
result = await create_task.fn(
|
|
132
|
+
calendar_uid="cal-123",
|
|
133
|
+
summary="Test Task",
|
|
134
|
+
description=None,
|
|
135
|
+
due=None,
|
|
136
|
+
priority="invalid", # Cannot convert to int
|
|
137
|
+
status="NEEDS-ACTION",
|
|
138
|
+
related_to=None,
|
|
139
|
+
account=None,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
assert result["success"] is False
|
|
143
|
+
assert "Invalid priority value" in result["error"]
|
|
144
|
+
assert result["error_code"] == "VALIDATION_ERROR"
|
|
145
|
+
assert "request_id" in result
|
|
146
|
+
|
|
147
|
+
@pytest.mark.asyncio
|
|
148
|
+
async def test_create_task_priority_type_error(self, setup_managers):
|
|
149
|
+
"""Test create_task handles TypeError in priority conversion"""
|
|
150
|
+
result = await create_task.fn(
|
|
151
|
+
calendar_uid="cal-123",
|
|
152
|
+
summary="Test Task",
|
|
153
|
+
description=None,
|
|
154
|
+
due=None,
|
|
155
|
+
priority={}, # TypeError when int({})
|
|
156
|
+
status="NEEDS-ACTION",
|
|
157
|
+
related_to=None,
|
|
158
|
+
account=None,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
assert result["success"] is False
|
|
162
|
+
assert "Invalid priority value" in result["error"]
|
|
163
|
+
|
|
164
|
+
@pytest.mark.asyncio
|
|
165
|
+
async def test_create_task_summary_validation_error(self, setup_managers):
|
|
166
|
+
"""Test create_task validation error for summary"""
|
|
167
|
+
with patch(
|
|
168
|
+
"chronos_mcp.tools.tasks.InputValidator.validate_text_field"
|
|
169
|
+
) as mock_validate:
|
|
170
|
+
mock_validate.side_effect = ValidationError("Summary too long")
|
|
171
|
+
|
|
172
|
+
result = await create_task.fn(
|
|
173
|
+
calendar_uid="cal-123",
|
|
174
|
+
summary="x" * 1000, # Very long summary
|
|
175
|
+
description=None,
|
|
176
|
+
due=None,
|
|
177
|
+
priority=None,
|
|
178
|
+
status="NEEDS-ACTION",
|
|
179
|
+
related_to=None,
|
|
180
|
+
account=None,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
assert result["success"] is False
|
|
184
|
+
assert "Summary too long" in result["error"]
|
|
185
|
+
assert result["error_code"] == "VALIDATION_ERROR"
|
|
186
|
+
|
|
187
|
+
@pytest.mark.asyncio
|
|
188
|
+
async def test_create_task_description_validation_error(self, setup_managers):
|
|
189
|
+
"""Test create_task validation error for description"""
|
|
190
|
+
with patch(
|
|
191
|
+
"chronos_mcp.tools.tasks.InputValidator.validate_text_field"
|
|
192
|
+
) as mock_validate:
|
|
193
|
+
# Summary passes, description fails
|
|
194
|
+
mock_validate.side_effect = [
|
|
195
|
+
"Valid Summary", # First call for summary
|
|
196
|
+
ValidationError("Description invalid"), # Second call for description
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
result = await create_task.fn(
|
|
200
|
+
calendar_uid="cal-123",
|
|
201
|
+
summary="Valid Summary",
|
|
202
|
+
description="Invalid description",
|
|
203
|
+
due=None,
|
|
204
|
+
priority=None,
|
|
205
|
+
status="NEEDS-ACTION",
|
|
206
|
+
related_to=None,
|
|
207
|
+
account=None,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
assert result["success"] is False
|
|
211
|
+
assert "Description invalid" in result["error"]
|
|
212
|
+
assert result["error_code"] == "VALIDATION_ERROR"
|
|
213
|
+
|
|
214
|
+
@pytest.mark.asyncio
|
|
215
|
+
async def test_create_task_invalid_priority_range(self, setup_managers):
|
|
216
|
+
"""Test create_task validates priority range"""
|
|
217
|
+
result = await create_task.fn(
|
|
218
|
+
calendar_uid="cal-123",
|
|
219
|
+
summary="Test Task",
|
|
220
|
+
description=None,
|
|
221
|
+
due=None,
|
|
222
|
+
priority=10, # Outside 1-9 range
|
|
223
|
+
status="NEEDS-ACTION",
|
|
224
|
+
related_to=None,
|
|
225
|
+
account=None,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
assert result["success"] is False
|
|
229
|
+
assert "Priority must be between 1 and 9" in result["error"]
|
|
230
|
+
assert result["error_code"] == "VALIDATION_ERROR"
|
|
231
|
+
|
|
232
|
+
@pytest.mark.asyncio
|
|
233
|
+
async def test_create_task_invalid_status(self, setup_managers):
|
|
234
|
+
"""Test create_task validates status enum"""
|
|
235
|
+
result = await create_task.fn(
|
|
236
|
+
calendar_uid="cal-123",
|
|
237
|
+
summary="Test Task",
|
|
238
|
+
description=None,
|
|
239
|
+
due=None,
|
|
240
|
+
priority=None,
|
|
241
|
+
status="INVALID-STATUS",
|
|
242
|
+
related_to=None,
|
|
243
|
+
account=None,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
assert result["success"] is False
|
|
247
|
+
assert "Invalid status" in result["error"]
|
|
248
|
+
assert result["error_code"] == "VALIDATION_ERROR"
|
|
249
|
+
|
|
250
|
+
@pytest.mark.asyncio
|
|
251
|
+
async def test_create_task_due_date_none(self, setup_managers, sample_task):
|
|
252
|
+
"""Test create_task with due date as None in response"""
|
|
253
|
+
sample_task.due = None
|
|
254
|
+
_managers["task_manager"].create_task.return_value = sample_task
|
|
255
|
+
|
|
256
|
+
result = await create_task.fn(
|
|
257
|
+
calendar_uid="cal-123",
|
|
258
|
+
summary="Test Task",
|
|
259
|
+
description=None,
|
|
260
|
+
due=None,
|
|
261
|
+
priority=None,
|
|
262
|
+
status="NEEDS-ACTION",
|
|
263
|
+
related_to=None,
|
|
264
|
+
account=None,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
assert result["success"] is True
|
|
268
|
+
assert result["task"]["due"] is None
|
|
269
|
+
|
|
270
|
+
@pytest.mark.asyncio
|
|
271
|
+
async def test_create_task_calendar_not_found_error(self, setup_managers):
|
|
272
|
+
"""Test create_task handles CalendarNotFoundError"""
|
|
273
|
+
error = CalendarNotFoundError("Calendar not found")
|
|
274
|
+
_managers["task_manager"].create_task.side_effect = error
|
|
275
|
+
|
|
276
|
+
result = await create_task.fn(
|
|
277
|
+
calendar_uid="cal-123",
|
|
278
|
+
summary="Test Task",
|
|
279
|
+
description=None,
|
|
280
|
+
due=None,
|
|
281
|
+
priority=None,
|
|
282
|
+
status="NEEDS-ACTION",
|
|
283
|
+
related_to=None,
|
|
284
|
+
account=None,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
assert result["success"] is False
|
|
288
|
+
assert "request_id" in result
|
|
289
|
+
|
|
290
|
+
@pytest.mark.asyncio
|
|
291
|
+
async def test_create_task_event_creation_error(self, setup_managers):
|
|
292
|
+
"""Test create_task handles EventCreationError"""
|
|
293
|
+
error = EventCreationError("Creation failed")
|
|
294
|
+
_managers["task_manager"].create_task.side_effect = error
|
|
295
|
+
|
|
296
|
+
result = await create_task.fn(
|
|
297
|
+
calendar_uid="cal-123",
|
|
298
|
+
summary="Test Task",
|
|
299
|
+
description=None,
|
|
300
|
+
due=None,
|
|
301
|
+
priority=None,
|
|
302
|
+
status="NEEDS-ACTION",
|
|
303
|
+
related_to=None,
|
|
304
|
+
account=None,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
assert result["success"] is False
|
|
308
|
+
assert result["error_code"] == "EventCreationError"
|
|
309
|
+
|
|
310
|
+
@pytest.mark.asyncio
|
|
311
|
+
async def test_create_task_chronos_error(self, setup_managers):
|
|
312
|
+
"""Test create_task handles general ChronosError"""
|
|
313
|
+
error = ChronosError("General error")
|
|
314
|
+
_managers["task_manager"].create_task.side_effect = error
|
|
315
|
+
|
|
316
|
+
result = await create_task.fn(
|
|
317
|
+
calendar_uid="cal-123",
|
|
318
|
+
summary="Test Task",
|
|
319
|
+
description=None,
|
|
320
|
+
due=None,
|
|
321
|
+
priority=None,
|
|
322
|
+
status="NEEDS-ACTION",
|
|
323
|
+
related_to=None,
|
|
324
|
+
account=None,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
assert result["success"] is False
|
|
328
|
+
assert result["error_code"] == "ChronosError"
|
|
329
|
+
|
|
330
|
+
@pytest.mark.asyncio
|
|
331
|
+
async def test_create_task_unexpected_exception(self, setup_managers):
|
|
332
|
+
"""Test create_task handles unexpected exceptions"""
|
|
333
|
+
_managers["task_manager"].create_task.side_effect = RuntimeError(
|
|
334
|
+
"Unexpected error"
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
result = await create_task.fn(
|
|
338
|
+
calendar_uid="cal-123",
|
|
339
|
+
summary="Test Task",
|
|
340
|
+
description=None,
|
|
341
|
+
due=None,
|
|
342
|
+
priority=None,
|
|
343
|
+
status="NEEDS-ACTION",
|
|
344
|
+
related_to=None,
|
|
345
|
+
account=None,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
assert result["success"] is False
|
|
349
|
+
assert "Failed to create task" in result["error"]
|
|
350
|
+
assert "request_id" in result
|
|
351
|
+
|
|
352
|
+
# LIST_TASKS TOOL TESTS
|
|
353
|
+
|
|
354
|
+
@pytest.mark.asyncio
|
|
355
|
+
async def test_list_tasks_success(self, setup_managers, sample_task):
|
|
356
|
+
"""Test list_tasks successful execution"""
|
|
357
|
+
_managers["task_manager"].list_tasks.return_value = [sample_task]
|
|
358
|
+
|
|
359
|
+
result = await list_tasks.fn(
|
|
360
|
+
calendar_uid="cal-123", status_filter=None, account=None
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
assert len(result["tasks"]) == 1
|
|
364
|
+
assert result["total"] == 1
|
|
365
|
+
assert result["calendar_uid"] == "cal-123"
|
|
366
|
+
assert "request_id" in result
|
|
367
|
+
|
|
368
|
+
@pytest.mark.asyncio
|
|
369
|
+
async def test_list_tasks_with_status_filter(self, setup_managers, sample_task):
|
|
370
|
+
"""Test list_tasks with status filter"""
|
|
371
|
+
_managers["task_manager"].list_tasks.return_value = [sample_task]
|
|
372
|
+
|
|
373
|
+
result = await list_tasks.fn(
|
|
374
|
+
calendar_uid="cal-123", status_filter="NEEDS-ACTION", account=None
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
assert len(result["tasks"]) == 1
|
|
378
|
+
_managers["task_manager"].list_tasks.assert_called_once_with(
|
|
379
|
+
calendar_uid="cal-123",
|
|
380
|
+
status_filter=TaskStatus.NEEDS_ACTION,
|
|
381
|
+
account_alias=None,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
@pytest.mark.asyncio
|
|
385
|
+
async def test_list_tasks_invalid_status_filter(self, setup_managers):
|
|
386
|
+
"""Test list_tasks with invalid status filter"""
|
|
387
|
+
result = await list_tasks.fn(
|
|
388
|
+
calendar_uid="cal-123", status_filter="INVALID-STATUS", account=None
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
assert result["success"] is False
|
|
392
|
+
assert "Invalid status filter" in result["error"]
|
|
393
|
+
assert result["error_code"] == "VALIDATION_ERROR"
|
|
394
|
+
|
|
395
|
+
@pytest.mark.asyncio
|
|
396
|
+
async def test_list_tasks_task_due_none(self, setup_managers):
|
|
397
|
+
"""Test list_tasks with task having None due date"""
|
|
398
|
+
task = Mock()
|
|
399
|
+
task.uid = "task-123"
|
|
400
|
+
task.summary = "Test Task"
|
|
401
|
+
task.description = "Test description"
|
|
402
|
+
task.due = None # No due date
|
|
403
|
+
task.priority = 5
|
|
404
|
+
task.status = TaskStatus.NEEDS_ACTION
|
|
405
|
+
task.percent_complete = 0
|
|
406
|
+
task.related_to = []
|
|
407
|
+
|
|
408
|
+
_managers["task_manager"].list_tasks.return_value = [task]
|
|
409
|
+
|
|
410
|
+
result = await list_tasks.fn(
|
|
411
|
+
calendar_uid="cal-123", status_filter=None, account=None
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
assert result["tasks"][0]["due"] is None
|
|
415
|
+
|
|
416
|
+
@pytest.mark.asyncio
|
|
417
|
+
async def test_list_tasks_calendar_not_found_error(self, setup_managers):
|
|
418
|
+
"""Test list_tasks handles CalendarNotFoundError"""
|
|
419
|
+
error = CalendarNotFoundError("Calendar not found")
|
|
420
|
+
_managers["task_manager"].list_tasks.side_effect = error
|
|
421
|
+
|
|
422
|
+
result = await list_tasks.fn(
|
|
423
|
+
calendar_uid="cal-123", status_filter=None, account=None
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
assert result["tasks"] == []
|
|
427
|
+
assert result["total"] == 0
|
|
428
|
+
assert "error" in result
|
|
429
|
+
assert "request_id" in result
|
|
430
|
+
|
|
431
|
+
@pytest.mark.asyncio
|
|
432
|
+
async def test_list_tasks_chronos_error(self, setup_managers):
|
|
433
|
+
"""Test list_tasks handles ChronosError"""
|
|
434
|
+
error = ChronosError("General error")
|
|
435
|
+
_managers["task_manager"].list_tasks.side_effect = error
|
|
436
|
+
|
|
437
|
+
result = await list_tasks.fn(
|
|
438
|
+
calendar_uid="cal-123", status_filter=None, account=None
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
assert result["tasks"] == []
|
|
442
|
+
assert result["total"] == 0
|
|
443
|
+
assert result["error_code"] == "ChronosError"
|
|
444
|
+
|
|
445
|
+
@pytest.mark.asyncio
|
|
446
|
+
async def test_list_tasks_unexpected_exception(self, setup_managers):
|
|
447
|
+
"""Test list_tasks handles unexpected exceptions"""
|
|
448
|
+
_managers["task_manager"].list_tasks.side_effect = RuntimeError(
|
|
449
|
+
"Unexpected error"
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
result = await list_tasks.fn(
|
|
453
|
+
calendar_uid="cal-123", status_filter=None, account=None
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
assert result["tasks"] == []
|
|
457
|
+
assert result["total"] == 0
|
|
458
|
+
assert "Failed to list tasks" in result["error"]
|
|
459
|
+
|
|
460
|
+
# UPDATE_TASK TOOL TESTS (uses @handle_tool_errors decorator)
|
|
461
|
+
|
|
462
|
+
@pytest.mark.asyncio
|
|
463
|
+
async def test_update_task_success(self, setup_managers, sample_task):
|
|
464
|
+
"""Test update_task successful execution"""
|
|
465
|
+
_managers["task_manager"].update_task.return_value = sample_task
|
|
466
|
+
|
|
467
|
+
result = await update_task.fn(
|
|
468
|
+
calendar_uid="cal-123",
|
|
469
|
+
task_uid="task-123",
|
|
470
|
+
summary="Updated Summary",
|
|
471
|
+
description=None,
|
|
472
|
+
due=None,
|
|
473
|
+
priority=None,
|
|
474
|
+
status=None,
|
|
475
|
+
percent_complete=None,
|
|
476
|
+
account=None,
|
|
477
|
+
request_id=None,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
assert result["success"] is True
|
|
481
|
+
assert result["task"]["uid"] == "task-123"
|
|
482
|
+
assert "request_id" in result
|
|
483
|
+
|
|
484
|
+
@pytest.mark.asyncio
|
|
485
|
+
async def test_update_task_priority_string_conversion(
|
|
486
|
+
self, setup_managers, sample_task
|
|
487
|
+
):
|
|
488
|
+
"""Test update_task converts string priority to int"""
|
|
489
|
+
_managers["task_manager"].update_task.return_value = sample_task
|
|
490
|
+
|
|
491
|
+
result = await update_task.fn(
|
|
492
|
+
calendar_uid="cal-123",
|
|
493
|
+
task_uid="task-123",
|
|
494
|
+
summary=None,
|
|
495
|
+
description=None,
|
|
496
|
+
due=None,
|
|
497
|
+
priority="7",
|
|
498
|
+
status=None,
|
|
499
|
+
percent_complete=None,
|
|
500
|
+
account=None,
|
|
501
|
+
request_id=None,
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
assert result["success"] is True
|
|
505
|
+
|
|
506
|
+
@pytest.mark.asyncio
|
|
507
|
+
async def test_update_task_invalid_priority_string(self, setup_managers):
|
|
508
|
+
"""Test update_task handles invalid priority string"""
|
|
509
|
+
result = await update_task.fn(
|
|
510
|
+
calendar_uid="cal-123",
|
|
511
|
+
task_uid="task-123",
|
|
512
|
+
summary=None,
|
|
513
|
+
description=None,
|
|
514
|
+
due=None,
|
|
515
|
+
priority="invalid",
|
|
516
|
+
status=None,
|
|
517
|
+
percent_complete=None,
|
|
518
|
+
account=None,
|
|
519
|
+
request_id=None,
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
assert result["success"] is False
|
|
523
|
+
assert "Invalid priority value" in result["error"]
|
|
524
|
+
|
|
525
|
+
@pytest.mark.asyncio
|
|
526
|
+
async def test_update_task_percent_complete_string_conversion(
|
|
527
|
+
self, setup_managers, sample_task
|
|
528
|
+
):
|
|
529
|
+
"""Test update_task converts string percent_complete to int"""
|
|
530
|
+
_managers["task_manager"].update_task.return_value = sample_task
|
|
531
|
+
|
|
532
|
+
result = await update_task.fn(
|
|
533
|
+
calendar_uid="cal-123",
|
|
534
|
+
task_uid="task-123",
|
|
535
|
+
summary=None,
|
|
536
|
+
description=None,
|
|
537
|
+
due=None,
|
|
538
|
+
priority=None,
|
|
539
|
+
status=None,
|
|
540
|
+
percent_complete="50",
|
|
541
|
+
account=None,
|
|
542
|
+
request_id=None,
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
assert result["success"] is True
|
|
546
|
+
|
|
547
|
+
@pytest.mark.asyncio
|
|
548
|
+
async def test_update_task_invalid_percent_complete_string(self, setup_managers):
|
|
549
|
+
"""Test update_task handles invalid percent_complete string"""
|
|
550
|
+
result = await update_task.fn(
|
|
551
|
+
calendar_uid="cal-123",
|
|
552
|
+
task_uid="task-123",
|
|
553
|
+
summary=None,
|
|
554
|
+
description=None,
|
|
555
|
+
due=None,
|
|
556
|
+
priority=None,
|
|
557
|
+
status=None,
|
|
558
|
+
percent_complete="invalid",
|
|
559
|
+
account=None,
|
|
560
|
+
request_id=None,
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
assert result["success"] is False
|
|
564
|
+
assert "Invalid percent_complete value" in result["error"]
|
|
565
|
+
|
|
566
|
+
@pytest.mark.asyncio
|
|
567
|
+
async def test_update_task_priority_range_validation(self, setup_managers):
|
|
568
|
+
"""Test update_task validates priority range"""
|
|
569
|
+
result = await update_task.fn(
|
|
570
|
+
calendar_uid="cal-123",
|
|
571
|
+
task_uid="task-123",
|
|
572
|
+
summary=None,
|
|
573
|
+
description=None,
|
|
574
|
+
due=None,
|
|
575
|
+
priority=15, # Outside 1-9 range
|
|
576
|
+
status=None,
|
|
577
|
+
percent_complete=None,
|
|
578
|
+
account=None,
|
|
579
|
+
request_id=None,
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
assert result["success"] is False
|
|
583
|
+
assert "Priority must be between 1 and 9" in result["error"]
|
|
584
|
+
|
|
585
|
+
@pytest.mark.asyncio
|
|
586
|
+
async def test_update_task_invalid_status(self, setup_managers):
|
|
587
|
+
"""Test update_task validates status enum"""
|
|
588
|
+
result = await update_task.fn(
|
|
589
|
+
calendar_uid="cal-123",
|
|
590
|
+
task_uid="task-123",
|
|
591
|
+
summary=None,
|
|
592
|
+
description=None,
|
|
593
|
+
due=None,
|
|
594
|
+
priority=None,
|
|
595
|
+
status="INVALID-STATUS",
|
|
596
|
+
percent_complete=None,
|
|
597
|
+
account=None,
|
|
598
|
+
request_id=None,
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
assert result["success"] is False
|
|
602
|
+
assert "Invalid status" in result["error"]
|
|
603
|
+
|
|
604
|
+
@pytest.mark.asyncio
|
|
605
|
+
async def test_update_task_percent_complete_range_validation(self, setup_managers):
|
|
606
|
+
"""Test update_task validates percent_complete range"""
|
|
607
|
+
result = await update_task.fn(
|
|
608
|
+
calendar_uid="cal-123",
|
|
609
|
+
task_uid="task-123",
|
|
610
|
+
summary=None,
|
|
611
|
+
description=None,
|
|
612
|
+
due=None,
|
|
613
|
+
priority=None,
|
|
614
|
+
status=None,
|
|
615
|
+
percent_complete=150, # Outside 0-100 range
|
|
616
|
+
account=None,
|
|
617
|
+
request_id=None,
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
assert result["success"] is False
|
|
621
|
+
assert "Percent complete must be between 0 and 100" in result["error"]
|
|
622
|
+
|
|
623
|
+
@pytest.mark.asyncio
|
|
624
|
+
async def test_update_task_due_none_in_response(self, setup_managers):
|
|
625
|
+
"""Test update_task with None due date in response"""
|
|
626
|
+
sample_task = Mock()
|
|
627
|
+
sample_task.uid = "task-123"
|
|
628
|
+
sample_task.summary = "Test Task"
|
|
629
|
+
sample_task.description = "Test description"
|
|
630
|
+
sample_task.due = None # No due date
|
|
631
|
+
sample_task.priority = 5
|
|
632
|
+
sample_task.status = TaskStatus.NEEDS_ACTION
|
|
633
|
+
sample_task.percent_complete = 0
|
|
634
|
+
sample_task.related_to = []
|
|
635
|
+
|
|
636
|
+
_managers["task_manager"].update_task.return_value = sample_task
|
|
637
|
+
|
|
638
|
+
result = await update_task.fn(
|
|
639
|
+
calendar_uid="cal-123",
|
|
640
|
+
task_uid="task-123",
|
|
641
|
+
summary="Updated",
|
|
642
|
+
description=None,
|
|
643
|
+
due=None,
|
|
644
|
+
priority=None,
|
|
645
|
+
status=None,
|
|
646
|
+
percent_complete=None,
|
|
647
|
+
account=None,
|
|
648
|
+
request_id=None,
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
assert result["success"] is True
|
|
652
|
+
assert result["task"]["due"] is None
|
|
653
|
+
|
|
654
|
+
# DELETE_TASK TOOL TESTS (uses @handle_tool_errors decorator)
|
|
655
|
+
|
|
656
|
+
@pytest.mark.asyncio
|
|
657
|
+
async def test_delete_task_success(self, setup_managers):
|
|
658
|
+
"""Test delete_task successful execution"""
|
|
659
|
+
_managers["task_manager"].delete_task.return_value = True
|
|
660
|
+
|
|
661
|
+
result = await delete_task.fn(
|
|
662
|
+
calendar_uid="cal-123", task_uid="task-123", account=None, request_id=None
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
assert result["success"] is True
|
|
666
|
+
assert "deleted successfully" in result["message"]
|
|
667
|
+
assert "request_id" in result
|
|
668
|
+
|
|
669
|
+
@pytest.mark.asyncio
|
|
670
|
+
async def test_delete_task_with_account(self, setup_managers):
|
|
671
|
+
"""Test delete_task with account parameter"""
|
|
672
|
+
_managers["task_manager"].delete_task.return_value = True
|
|
673
|
+
|
|
674
|
+
result = await delete_task.fn(
|
|
675
|
+
calendar_uid="cal-123",
|
|
676
|
+
task_uid="task-123",
|
|
677
|
+
account="test_account",
|
|
678
|
+
request_id=None,
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
_managers["task_manager"].delete_task.assert_called_once_with(
|
|
682
|
+
calendar_uid="cal-123",
|
|
683
|
+
task_uid="task-123",
|
|
684
|
+
account_alias="test_account",
|
|
685
|
+
request_id=result["request_id"],
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
# REGISTER_TASK_TOOLS TESTS
|
|
689
|
+
|
|
690
|
+
def test_register_task_tools(self, mock_managers, setup_managers):
|
|
691
|
+
"""Test register_task_tools function"""
|
|
692
|
+
mock_mcp = Mock()
|
|
693
|
+
|
|
694
|
+
register_task_tools(mock_mcp, mock_managers)
|
|
695
|
+
|
|
696
|
+
# Verify managers were updated - strict equality now works with clean state from fixture
|
|
697
|
+
assert _managers == mock_managers
|
|
698
|
+
|
|
699
|
+
# Verify all tools were registered
|
|
700
|
+
assert mock_mcp.tool.call_count == 4
|
|
701
|
+
|
|
702
|
+
# Verify specific tools were registered
|
|
703
|
+
calls = [call[0][0] for call in mock_mcp.tool.call_args_list]
|
|
704
|
+
assert create_task in calls
|
|
705
|
+
assert list_tasks in calls
|
|
706
|
+
assert update_task in calls
|
|
707
|
+
assert delete_task in calls
|
|
708
|
+
|
|
709
|
+
# FUNCTION ATTRIBUTE TESTS
|
|
710
|
+
|
|
711
|
+
def test_function_attributes_exist(self):
|
|
712
|
+
"""Test that .fn attributes exist for backwards compatibility"""
|
|
713
|
+
assert hasattr(create_task, "fn")
|
|
714
|
+
assert hasattr(list_tasks, "fn")
|
|
715
|
+
assert hasattr(update_task, "fn")
|
|
716
|
+
assert hasattr(delete_task, "fn")
|
|
717
|
+
|
|
718
|
+
assert create_task.fn == create_task
|
|
719
|
+
assert list_tasks.fn == list_tasks
|
|
720
|
+
assert update_task.fn == update_task
|
|
721
|
+
assert delete_task.fn == delete_task
|
|
722
|
+
|
|
723
|
+
# EDGE CASES AND DEFENSIVE PROGRAMMING
|
|
724
|
+
|
|
725
|
+
@pytest.mark.asyncio
|
|
726
|
+
async def test_create_task_zero_priority(self, setup_managers):
|
|
727
|
+
"""Test create_task with priority 0 (invalid)"""
|
|
728
|
+
result = await create_task.fn(
|
|
729
|
+
calendar_uid="cal-123",
|
|
730
|
+
summary="Test Task",
|
|
731
|
+
description=None,
|
|
732
|
+
due=None,
|
|
733
|
+
priority=0, # Below valid range
|
|
734
|
+
status="NEEDS-ACTION",
|
|
735
|
+
related_to=None,
|
|
736
|
+
account=None,
|
|
737
|
+
)
|
|
738
|
+
|
|
739
|
+
assert result["success"] is False
|
|
740
|
+
assert "Priority must be between 1 and 9" in result["error"]
|
|
741
|
+
|
|
742
|
+
@pytest.mark.asyncio
|
|
743
|
+
async def test_update_task_negative_percent_complete(self, setup_managers):
|
|
744
|
+
"""Test update_task with negative percent_complete"""
|
|
745
|
+
result = await update_task.fn(
|
|
746
|
+
calendar_uid="cal-123",
|
|
747
|
+
task_uid="task-123",
|
|
748
|
+
summary=None,
|
|
749
|
+
description=None,
|
|
750
|
+
due=None,
|
|
751
|
+
priority=None,
|
|
752
|
+
status=None,
|
|
753
|
+
percent_complete=-10, # Below valid range
|
|
754
|
+
account=None,
|
|
755
|
+
request_id=None,
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
assert result["success"] is False
|
|
759
|
+
assert "Percent complete must be between 0 and 100" in result["error"]
|
|
760
|
+
|
|
761
|
+
@pytest.mark.asyncio
|
|
762
|
+
async def test_create_task_malformed_due_date(self, setup_managers):
|
|
763
|
+
"""Test create_task with malformed due date triggering parse_datetime error"""
|
|
764
|
+
with patch("chronos_mcp.tools.tasks.parse_datetime") as mock_parse:
|
|
765
|
+
mock_parse.side_effect = ValueError("Invalid date format")
|
|
766
|
+
|
|
767
|
+
result = await create_task.fn(
|
|
768
|
+
calendar_uid="cal-123",
|
|
769
|
+
summary="Test Task",
|
|
770
|
+
description=None,
|
|
771
|
+
due="invalid-date",
|
|
772
|
+
priority=None,
|
|
773
|
+
status="NEEDS-ACTION",
|
|
774
|
+
related_to=None,
|
|
775
|
+
account=None,
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
assert result["success"] is False
|
|
779
|
+
assert "Failed to create task" in result["error"]
|
|
780
|
+
|
|
781
|
+
@pytest.mark.asyncio
|
|
782
|
+
async def test_update_task_malformed_due_date(self, setup_managers):
|
|
783
|
+
"""Test update_task with malformed due date triggering parse_datetime error"""
|
|
784
|
+
with patch("chronos_mcp.tools.tasks.parse_datetime") as mock_parse:
|
|
785
|
+
mock_parse.side_effect = ValueError("Invalid date format")
|
|
786
|
+
|
|
787
|
+
result = await update_task.fn(
|
|
788
|
+
calendar_uid="cal-123",
|
|
789
|
+
task_uid="task-123",
|
|
790
|
+
summary=None,
|
|
791
|
+
description=None,
|
|
792
|
+
due="invalid-date",
|
|
793
|
+
priority=None,
|
|
794
|
+
status=None,
|
|
795
|
+
percent_complete=None,
|
|
796
|
+
account=None,
|
|
797
|
+
request_id=None,
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
assert result["success"] is False
|
|
801
|
+
|
|
802
|
+
@pytest.mark.asyncio
|
|
803
|
+
async def test_create_task_empty_summary(self, setup_managers):
|
|
804
|
+
"""Test create_task with empty summary"""
|
|
805
|
+
with patch(
|
|
806
|
+
"chronos_mcp.tools.tasks.InputValidator.validate_text_field"
|
|
807
|
+
) as mock_validate:
|
|
808
|
+
mock_validate.side_effect = ValidationError("Summary is required")
|
|
809
|
+
|
|
810
|
+
result = await create_task.fn(
|
|
811
|
+
calendar_uid="cal-123",
|
|
812
|
+
summary="",
|
|
813
|
+
description=None,
|
|
814
|
+
due=None,
|
|
815
|
+
priority=None,
|
|
816
|
+
status="NEEDS-ACTION",
|
|
817
|
+
related_to=None,
|
|
818
|
+
account=None,
|
|
819
|
+
)
|
|
820
|
+
|
|
821
|
+
assert result["success"] is False
|
|
822
|
+
assert "Summary is required" in result["error"]
|
|
823
|
+
|
|
824
|
+
@pytest.mark.asyncio
|
|
825
|
+
async def test_list_tasks_with_account(self, setup_managers, sample_task):
|
|
826
|
+
"""Test list_tasks with account parameter"""
|
|
827
|
+
_managers["task_manager"].list_tasks.return_value = [sample_task]
|
|
828
|
+
|
|
829
|
+
result = await list_tasks.fn(
|
|
830
|
+
calendar_uid="cal-123", status_filter=None, account="test_account"
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
_managers["task_manager"].list_tasks.assert_called_once_with(
|
|
834
|
+
calendar_uid="cal-123", status_filter=None, account_alias="test_account"
|
|
835
|
+
)
|
|
836
|
+
assert result["total"] == 1
|
|
837
|
+
assert len(result["tasks"]) == 1
|
|
838
|
+
|
|
839
|
+
@pytest.mark.asyncio
|
|
840
|
+
async def test_update_task_all_parameters(self, setup_managers, sample_task):
|
|
841
|
+
"""Test update_task with all parameters"""
|
|
842
|
+
_managers["task_manager"].update_task.return_value = sample_task
|
|
843
|
+
|
|
844
|
+
result = await update_task.fn(
|
|
845
|
+
calendar_uid="cal-123",
|
|
846
|
+
task_uid="task-123",
|
|
847
|
+
summary="Updated Summary",
|
|
848
|
+
description="Updated description",
|
|
849
|
+
due="2025-12-31T23:59:00Z",
|
|
850
|
+
priority=3,
|
|
851
|
+
status="IN-PROCESS",
|
|
852
|
+
percent_complete=75,
|
|
853
|
+
account="test_account",
|
|
854
|
+
request_id=None,
|
|
855
|
+
)
|
|
856
|
+
|
|
857
|
+
assert result["success"] is True
|
|
858
|
+
assert result["task"]["uid"] == "task-123"
|
|
859
|
+
|
|
860
|
+
@pytest.mark.asyncio
|
|
861
|
+
async def test_update_task_summary_validation_error(self, setup_managers):
|
|
862
|
+
"""Test update_task validation error for summary"""
|
|
863
|
+
with patch(
|
|
864
|
+
"chronos_mcp.tools.tasks.InputValidator.validate_text_field"
|
|
865
|
+
) as mock_validate:
|
|
866
|
+
mock_validate.side_effect = ValidationError("Summary invalid")
|
|
867
|
+
|
|
868
|
+
result = await update_task.fn(
|
|
869
|
+
calendar_uid="cal-123",
|
|
870
|
+
task_uid="task-123",
|
|
871
|
+
summary="Invalid summary",
|
|
872
|
+
description=None,
|
|
873
|
+
due=None,
|
|
874
|
+
priority=None,
|
|
875
|
+
status=None,
|
|
876
|
+
percent_complete=None,
|
|
877
|
+
account=None,
|
|
878
|
+
request_id=None,
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
assert result["success"] is False
|
|
882
|
+
assert "Summary invalid" in result["error"]
|
|
883
|
+
|
|
884
|
+
@pytest.mark.asyncio
|
|
885
|
+
async def test_update_task_description_validation_error(self, setup_managers):
|
|
886
|
+
"""Test update_task validation error for description"""
|
|
887
|
+
with patch(
|
|
888
|
+
"chronos_mcp.tools.tasks.InputValidator.validate_text_field"
|
|
889
|
+
) as mock_validate:
|
|
890
|
+
mock_validate.side_effect = ValidationError("Description invalid")
|
|
891
|
+
|
|
892
|
+
result = await update_task.fn(
|
|
893
|
+
calendar_uid="cal-123",
|
|
894
|
+
task_uid="task-123",
|
|
895
|
+
summary=None,
|
|
896
|
+
description="Invalid description",
|
|
897
|
+
due=None,
|
|
898
|
+
priority=None,
|
|
899
|
+
status=None,
|
|
900
|
+
percent_complete=None,
|
|
901
|
+
account=None,
|
|
902
|
+
request_id=None,
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
assert result["success"] is False
|
|
906
|
+
assert "Description invalid" in result["error"]
|
|
907
|
+
|
|
908
|
+
@pytest.mark.asyncio
|
|
909
|
+
async def test_update_task_priority_type_error(self, setup_managers):
|
|
910
|
+
"""Test update_task handles TypeError in priority conversion"""
|
|
911
|
+
result = await update_task.fn(
|
|
912
|
+
calendar_uid="cal-123",
|
|
913
|
+
task_uid="task-123",
|
|
914
|
+
summary=None,
|
|
915
|
+
description=None,
|
|
916
|
+
due=None,
|
|
917
|
+
priority={}, # TypeError when int({})
|
|
918
|
+
status=None,
|
|
919
|
+
percent_complete=None,
|
|
920
|
+
account=None,
|
|
921
|
+
request_id=None,
|
|
922
|
+
)
|
|
923
|
+
|
|
924
|
+
assert result["success"] is False
|
|
925
|
+
assert "Invalid priority value" in result["error"]
|
|
926
|
+
|
|
927
|
+
@pytest.mark.asyncio
|
|
928
|
+
async def test_update_task_percent_complete_type_error(self, setup_managers):
|
|
929
|
+
"""Test update_task handles TypeError in percent_complete conversion"""
|
|
930
|
+
result = await update_task.fn(
|
|
931
|
+
calendar_uid="cal-123",
|
|
932
|
+
task_uid="task-123",
|
|
933
|
+
summary=None,
|
|
934
|
+
description=None,
|
|
935
|
+
due=None,
|
|
936
|
+
priority=None,
|
|
937
|
+
status=None,
|
|
938
|
+
percent_complete=[], # TypeError when int([])
|
|
939
|
+
account=None,
|
|
940
|
+
request_id=None,
|
|
941
|
+
)
|
|
942
|
+
|
|
943
|
+
assert result["success"] is False
|
|
944
|
+
assert "Invalid percent_complete value" in result["error"]
|
|
945
|
+
|
|
946
|
+
@pytest.mark.asyncio
|
|
947
|
+
async def test_managers_not_initialized(self):
|
|
948
|
+
"""Test behavior when _managers is not properly initialized"""
|
|
949
|
+
# Clear managers to simulate uninitialized state
|
|
950
|
+
original = _managers.copy()
|
|
951
|
+
_managers.clear()
|
|
952
|
+
|
|
953
|
+
try:
|
|
954
|
+
result = await create_task.fn(
|
|
955
|
+
calendar_uid="cal-123",
|
|
956
|
+
summary="Test Task",
|
|
957
|
+
description=None,
|
|
958
|
+
due=None,
|
|
959
|
+
priority=None,
|
|
960
|
+
status="NEEDS-ACTION",
|
|
961
|
+
related_to=None,
|
|
962
|
+
account=None,
|
|
963
|
+
)
|
|
964
|
+
# Should get an error response, not an exception
|
|
965
|
+
assert result["success"] is False
|
|
966
|
+
assert "Failed to create task" in result["error"]
|
|
967
|
+
finally:
|
|
968
|
+
_managers.update(original)
|