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.
Files changed (68) hide show
  1. chronos_mcp/__init__.py +5 -0
  2. chronos_mcp/__main__.py +9 -0
  3. chronos_mcp/accounts.py +410 -0
  4. chronos_mcp/bulk.py +946 -0
  5. chronos_mcp/caldav_utils.py +149 -0
  6. chronos_mcp/calendars.py +204 -0
  7. chronos_mcp/config.py +187 -0
  8. chronos_mcp/credentials.py +190 -0
  9. chronos_mcp/events.py +515 -0
  10. chronos_mcp/exceptions.py +477 -0
  11. chronos_mcp/journals.py +477 -0
  12. chronos_mcp/logging_config.py +23 -0
  13. chronos_mcp/models.py +202 -0
  14. chronos_mcp/py.typed +0 -0
  15. chronos_mcp/rrule.py +259 -0
  16. chronos_mcp/search.py +315 -0
  17. chronos_mcp/server.py +121 -0
  18. chronos_mcp/tasks.py +518 -0
  19. chronos_mcp/tools/__init__.py +29 -0
  20. chronos_mcp/tools/accounts.py +151 -0
  21. chronos_mcp/tools/base.py +59 -0
  22. chronos_mcp/tools/bulk.py +557 -0
  23. chronos_mcp/tools/calendars.py +142 -0
  24. chronos_mcp/tools/events.py +698 -0
  25. chronos_mcp/tools/journals.py +310 -0
  26. chronos_mcp/tools/tasks.py +414 -0
  27. chronos_mcp/utils.py +163 -0
  28. chronos_mcp/validation.py +636 -0
  29. iflow_mcp_democratize_technology_chronos_mcp-2.0.0.dist-info/METADATA +299 -0
  30. iflow_mcp_democratize_technology_chronos_mcp-2.0.0.dist-info/RECORD +68 -0
  31. iflow_mcp_democratize_technology_chronos_mcp-2.0.0.dist-info/WHEEL +5 -0
  32. iflow_mcp_democratize_technology_chronos_mcp-2.0.0.dist-info/entry_points.txt +2 -0
  33. iflow_mcp_democratize_technology_chronos_mcp-2.0.0.dist-info/licenses/LICENSE +21 -0
  34. iflow_mcp_democratize_technology_chronos_mcp-2.0.0.dist-info/top_level.txt +2 -0
  35. tests/__init__.py +0 -0
  36. tests/conftest.py +91 -0
  37. tests/unit/__init__.py +0 -0
  38. tests/unit/test_accounts.py +380 -0
  39. tests/unit/test_accounts_ssrf.py +134 -0
  40. tests/unit/test_base.py +135 -0
  41. tests/unit/test_bulk.py +380 -0
  42. tests/unit/test_bulk_create.py +408 -0
  43. tests/unit/test_bulk_delete.py +341 -0
  44. tests/unit/test_bulk_resource_limits.py +74 -0
  45. tests/unit/test_caldav_utils.py +300 -0
  46. tests/unit/test_calendars.py +286 -0
  47. tests/unit/test_config.py +111 -0
  48. tests/unit/test_config_validation.py +128 -0
  49. tests/unit/test_credentials_security.py +189 -0
  50. tests/unit/test_cryptography_security.py +178 -0
  51. tests/unit/test_events.py +536 -0
  52. tests/unit/test_exceptions.py +58 -0
  53. tests/unit/test_journals.py +1097 -0
  54. tests/unit/test_models.py +95 -0
  55. tests/unit/test_race_conditions.py +202 -0
  56. tests/unit/test_recurring_events.py +156 -0
  57. tests/unit/test_rrule.py +217 -0
  58. tests/unit/test_search.py +372 -0
  59. tests/unit/test_search_advanced.py +333 -0
  60. tests/unit/test_server_input_validation.py +219 -0
  61. tests/unit/test_ssrf_protection.py +505 -0
  62. tests/unit/test_tasks.py +918 -0
  63. tests/unit/test_thread_safety.py +301 -0
  64. tests/unit/test_tools_journals.py +617 -0
  65. tests/unit/test_tools_tasks.py +968 -0
  66. tests/unit/test_url_validation_security.py +234 -0
  67. tests/unit/test_utils.py +180 -0
  68. tests/unit/test_validation.py +983 -0
@@ -0,0 +1,617 @@
1
+ """
2
+ Comprehensive unit tests for chronos_mcp/tools/journals.py module
3
+ Tests all MCP journal 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.journals import (
13
+ create_journal,
14
+ list_journals,
15
+ update_journal,
16
+ delete_journal,
17
+ register_journal_tools,
18
+ _managers,
19
+ )
20
+ from chronos_mcp.exceptions import (
21
+ CalendarNotFoundError,
22
+ EventNotFoundError,
23
+ EventCreationError,
24
+ ValidationError,
25
+ ChronosError,
26
+ )
27
+
28
+
29
+ class TestJournalTools:
30
+ """Test MCP journal tool functions with comprehensive coverage"""
31
+
32
+ @pytest.fixture
33
+ def mock_managers(self):
34
+ """Mock managers for dependency injection"""
35
+ journal_manager = Mock()
36
+ return {"journal_manager": journal_manager}
37
+
38
+ @pytest.fixture
39
+ def sample_journal(self):
40
+ """Sample journal object for testing"""
41
+ journal = Mock()
42
+ journal.uid = "journal-123"
43
+ journal.summary = "Test Journal"
44
+ journal.description = "Test journal content"
45
+ journal.dtstart = datetime(2025, 12, 31, 23, 59, tzinfo=timezone.utc)
46
+ journal.related_to = ["related-1", "related-2"]
47
+ return journal
48
+
49
+ @pytest.fixture
50
+ def setup_managers(self, mock_managers):
51
+ """Setup _managers module variable"""
52
+ original = _managers.copy()
53
+ _managers.clear()
54
+ _managers.update(mock_managers)
55
+ yield
56
+ _managers.clear()
57
+ _managers.update(original)
58
+
59
+ # CREATE_JOURNAL TOOL TESTS
60
+
61
+ @pytest.mark.asyncio
62
+ async def test_create_journal_minimal_success(self, setup_managers, sample_journal):
63
+ """Test create_journal with minimal required parameters"""
64
+ _managers["journal_manager"].create_journal.return_value = sample_journal
65
+
66
+ result = await create_journal.fn(
67
+ calendar_uid="cal-123",
68
+ summary="Test Journal",
69
+ description=None,
70
+ entry_date=None,
71
+ related_to=None,
72
+ account=None,
73
+ )
74
+
75
+ assert result["success"] is True
76
+ assert result["journal"]["uid"] == "journal-123"
77
+ assert result["journal"]["summary"] == "Test Journal"
78
+ assert "request_id" in result
79
+ _managers["journal_manager"].create_journal.assert_called_once()
80
+
81
+ @pytest.mark.asyncio
82
+ async def test_create_journal_full_parameters(self, setup_managers, sample_journal):
83
+ """Test create_journal with all parameters provided"""
84
+ _managers["journal_manager"].create_journal.return_value = sample_journal
85
+
86
+ result = await create_journal.fn(
87
+ calendar_uid="cal-123",
88
+ summary="Full Test Journal",
89
+ description="Full journal content",
90
+ entry_date="2025-12-31T23:59:00Z",
91
+ related_to=["related-1", "related-2"],
92
+ account="test_account",
93
+ )
94
+
95
+ assert result["success"] is True
96
+ assert result["journal"]["summary"] == "Test Journal" # from sample_journal
97
+ _managers["journal_manager"].create_journal.assert_called_once()
98
+
99
+ @pytest.mark.asyncio
100
+ async def test_create_journal_summary_validation_error(self, setup_managers):
101
+ """Test create_journal validation error for summary"""
102
+ with patch(
103
+ "chronos_mcp.tools.journals.InputValidator.validate_text_field"
104
+ ) as mock_validate:
105
+ mock_validate.side_effect = ValidationError("Summary too long")
106
+
107
+ result = await create_journal.fn(
108
+ calendar_uid="cal-123",
109
+ summary="x" * 1000, # Very long summary
110
+ description=None,
111
+ entry_date=None,
112
+ related_to=None,
113
+ account=None,
114
+ )
115
+
116
+ assert result["success"] is False
117
+ assert "Summary too long" in result["error"]
118
+ assert result["error_code"] == "VALIDATION_ERROR"
119
+
120
+ @pytest.mark.asyncio
121
+ async def test_create_journal_description_validation_error(self, setup_managers):
122
+ """Test create_journal validation error for description"""
123
+ with patch(
124
+ "chronos_mcp.tools.journals.InputValidator.validate_text_field"
125
+ ) as mock_validate:
126
+ # Summary passes, description fails
127
+ mock_validate.side_effect = [
128
+ "Valid Summary", # First call for summary
129
+ ValidationError("Description invalid"), # Second call for description
130
+ ]
131
+
132
+ result = await create_journal.fn(
133
+ calendar_uid="cal-123",
134
+ summary="Valid Summary",
135
+ description="Invalid description",
136
+ entry_date=None,
137
+ related_to=None,
138
+ account=None,
139
+ )
140
+
141
+ assert result["success"] is False
142
+ assert "Description invalid" in result["error"]
143
+ assert result["error_code"] == "VALIDATION_ERROR"
144
+
145
+ @pytest.mark.asyncio
146
+ async def test_create_journal_entry_date_none(self, setup_managers, sample_journal):
147
+ """Test create_journal with entry date as None in response"""
148
+ sample_journal.dtstart = None
149
+ _managers["journal_manager"].create_journal.return_value = sample_journal
150
+
151
+ result = await create_journal.fn(
152
+ calendar_uid="cal-123",
153
+ summary="Test Journal",
154
+ description=None,
155
+ entry_date=None,
156
+ related_to=None,
157
+ account=None,
158
+ )
159
+
160
+ assert result["success"] is True
161
+ assert result["journal"]["entry_date"] is None
162
+
163
+ @pytest.mark.asyncio
164
+ async def test_create_journal_chronos_error(self, setup_managers):
165
+ """Test create_journal handles ChronosError"""
166
+ error = ChronosError("General error")
167
+ _managers["journal_manager"].create_journal.side_effect = error
168
+
169
+ result = await create_journal.fn(
170
+ calendar_uid="cal-123",
171
+ summary="Test Journal",
172
+ description=None,
173
+ entry_date=None,
174
+ related_to=None,
175
+ account=None,
176
+ )
177
+
178
+ assert result["success"] is False
179
+ assert result["error_code"] == "ChronosError"
180
+
181
+ @pytest.mark.asyncio
182
+ async def test_create_journal_unexpected_exception(self, setup_managers):
183
+ """Test create_journal handles unexpected exceptions"""
184
+ _managers["journal_manager"].create_journal.side_effect = RuntimeError(
185
+ "Unexpected error"
186
+ )
187
+
188
+ result = await create_journal.fn(
189
+ calendar_uid="cal-123",
190
+ summary="Test Journal",
191
+ description=None,
192
+ entry_date=None,
193
+ related_to=None,
194
+ account=None,
195
+ )
196
+
197
+ assert result["success"] is False
198
+ assert "Failed to create journal" in result["error"]
199
+ assert "request_id" in result
200
+
201
+ @pytest.mark.asyncio
202
+ async def test_create_journal_malformed_entry_date(self, setup_managers):
203
+ """Test create_journal with malformed entry date triggering parse_datetime error"""
204
+ with patch("chronos_mcp.tools.journals.parse_datetime") as mock_parse:
205
+ mock_parse.side_effect = ValueError("Invalid date format")
206
+
207
+ result = await create_journal.fn(
208
+ calendar_uid="cal-123",
209
+ summary="Test Journal",
210
+ description=None,
211
+ entry_date="invalid-date",
212
+ related_to=None,
213
+ account=None,
214
+ )
215
+
216
+ assert result["success"] is False
217
+ assert "Failed to create journal" in result["error"]
218
+
219
+ # LIST_JOURNALS TOOL TESTS
220
+
221
+ @pytest.mark.asyncio
222
+ async def test_list_journals_success(self, setup_managers, sample_journal):
223
+ """Test list_journals successful execution"""
224
+ _managers["journal_manager"].list_journals.return_value = [sample_journal]
225
+
226
+ result = await list_journals.fn(calendar_uid="cal-123", account=None, limit=50)
227
+
228
+ assert len(result["journals"]) == 1
229
+ assert result["total"] == 1
230
+ assert result["calendar_uid"] == "cal-123"
231
+ assert "request_id" in result
232
+
233
+ @pytest.mark.asyncio
234
+ async def test_list_journals_with_account_and_limit(
235
+ self, setup_managers, sample_journal
236
+ ):
237
+ """Test list_journals with account and limit parameters"""
238
+ _managers["journal_manager"].list_journals.return_value = [sample_journal]
239
+
240
+ result = await list_journals.fn(
241
+ calendar_uid="cal-123", account="test_account", limit=10
242
+ )
243
+
244
+ assert len(result["journals"]) == 1
245
+ _managers["journal_manager"].list_journals.assert_called_once_with(
246
+ calendar_uid="cal-123", limit=10, account_alias="test_account"
247
+ )
248
+
249
+ @pytest.mark.asyncio
250
+ async def test_list_journals_limit_string_conversion(
251
+ self, setup_managers, sample_journal
252
+ ):
253
+ """Test list_journals converts string limit to int"""
254
+ _managers["journal_manager"].list_journals.return_value = [sample_journal]
255
+
256
+ result = await list_journals.fn(
257
+ calendar_uid="cal-123",
258
+ account=None,
259
+ limit="25", # String that should convert to int
260
+ )
261
+
262
+ assert len(result["journals"]) == 1
263
+ _managers["journal_manager"].list_journals.assert_called_once_with(
264
+ calendar_uid="cal-123", limit=25, account_alias=None
265
+ )
266
+
267
+ @pytest.mark.asyncio
268
+ async def test_list_journals_invalid_limit_string(self, setup_managers):
269
+ """Test list_journals handles invalid limit string"""
270
+ result = await list_journals.fn(
271
+ calendar_uid="cal-123",
272
+ account=None,
273
+ limit="invalid", # Cannot convert to int
274
+ )
275
+
276
+ assert result["journals"] == []
277
+ assert result["total"] == 0
278
+ assert "Invalid limit value" in result["error"]
279
+ assert result["error_code"] == "VALIDATION_ERROR"
280
+ assert "request_id" in result
281
+
282
+ @pytest.mark.asyncio
283
+ async def test_list_journals_limit_type_error(self, setup_managers):
284
+ """Test list_journals handles TypeError in limit conversion"""
285
+ result = await list_journals.fn(
286
+ calendar_uid="cal-123", account=None, limit={} # TypeError when int({})
287
+ )
288
+
289
+ assert result["journals"] == []
290
+ assert result["total"] == 0
291
+ assert "Invalid limit value" in result["error"]
292
+
293
+ @pytest.mark.asyncio
294
+ async def test_list_journals_entry_date_none(self, setup_managers):
295
+ """Test list_journals with journal having None entry date"""
296
+ journal = Mock()
297
+ journal.uid = "journal-123"
298
+ journal.summary = "Test Journal"
299
+ journal.description = "Test content"
300
+ journal.dtstart = None # No entry date
301
+ journal.related_to = []
302
+
303
+ _managers["journal_manager"].list_journals.return_value = [journal]
304
+
305
+ result = await list_journals.fn(calendar_uid="cal-123", account=None, limit=50)
306
+
307
+ assert result["journals"][0]["entry_date"] is None
308
+
309
+ @pytest.mark.asyncio
310
+ async def test_list_journals_calendar_not_found_error(self, setup_managers):
311
+ """Test list_journals handles CalendarNotFoundError"""
312
+ error = CalendarNotFoundError("Calendar not found")
313
+ _managers["journal_manager"].list_journals.side_effect = error
314
+
315
+ result = await list_journals.fn(calendar_uid="cal-123", account=None, limit=50)
316
+
317
+ assert result["journals"] == []
318
+ assert result["total"] == 0
319
+ assert "error" in result
320
+ assert "request_id" in result
321
+
322
+ @pytest.mark.asyncio
323
+ async def test_list_journals_chronos_error(self, setup_managers):
324
+ """Test list_journals handles ChronosError"""
325
+ error = ChronosError("General error")
326
+ _managers["journal_manager"].list_journals.side_effect = error
327
+
328
+ result = await list_journals.fn(calendar_uid="cal-123", account=None, limit=50)
329
+
330
+ assert result["journals"] == []
331
+ assert result["total"] == 0
332
+ assert result["error_code"] == "ChronosError"
333
+
334
+ @pytest.mark.asyncio
335
+ async def test_list_journals_unexpected_exception(self, setup_managers):
336
+ """Test list_journals handles unexpected exceptions"""
337
+ _managers["journal_manager"].list_journals.side_effect = RuntimeError(
338
+ "Unexpected error"
339
+ )
340
+
341
+ result = await list_journals.fn(calendar_uid="cal-123", account=None, limit=50)
342
+
343
+ assert result["journals"] == []
344
+ assert result["total"] == 0
345
+ assert "Failed to list journals" in result["error"]
346
+
347
+ # UPDATE_JOURNAL TOOL TESTS (uses @handle_tool_errors decorator)
348
+
349
+ @pytest.mark.asyncio
350
+ async def test_update_journal_success(self, setup_managers, sample_journal):
351
+ """Test update_journal successful execution"""
352
+ _managers["journal_manager"].update_journal.return_value = sample_journal
353
+
354
+ result = await update_journal.fn(
355
+ calendar_uid="cal-123",
356
+ journal_uid="journal-123",
357
+ summary="Updated Summary",
358
+ description=None,
359
+ entry_date=None,
360
+ account=None,
361
+ request_id=None,
362
+ )
363
+
364
+ assert result["success"] is True
365
+ assert result["journal"]["uid"] == "journal-123"
366
+ assert "request_id" in result
367
+
368
+ @pytest.mark.asyncio
369
+ async def test_update_journal_all_parameters(self, setup_managers, sample_journal):
370
+ """Test update_journal with all parameters"""
371
+ _managers["journal_manager"].update_journal.return_value = sample_journal
372
+
373
+ result = await update_journal.fn(
374
+ calendar_uid="cal-123",
375
+ journal_uid="journal-123",
376
+ summary="Updated Summary",
377
+ description="Updated content",
378
+ entry_date="2025-12-31T23:59:00Z",
379
+ account="test_account",
380
+ request_id=None,
381
+ )
382
+
383
+ assert result["success"] is True
384
+
385
+ @pytest.mark.asyncio
386
+ async def test_update_journal_summary_validation_error(self, setup_managers):
387
+ """Test update_journal validation error for summary"""
388
+ with patch(
389
+ "chronos_mcp.tools.journals.InputValidator.validate_text_field"
390
+ ) as mock_validate:
391
+ mock_validate.side_effect = ValidationError("Summary invalid")
392
+
393
+ result = await update_journal.fn(
394
+ calendar_uid="cal-123",
395
+ journal_uid="journal-123",
396
+ summary="Invalid summary",
397
+ description=None,
398
+ entry_date=None,
399
+ account=None,
400
+ request_id=None,
401
+ )
402
+
403
+ assert result["success"] is False
404
+ assert "Summary invalid" in result["error"]
405
+
406
+ @pytest.mark.asyncio
407
+ async def test_update_journal_description_validation_error(self, setup_managers):
408
+ """Test update_journal validation error for description"""
409
+ with patch(
410
+ "chronos_mcp.tools.journals.InputValidator.validate_text_field"
411
+ ) as mock_validate:
412
+ mock_validate.side_effect = ValidationError("Description invalid")
413
+
414
+ result = await update_journal.fn(
415
+ calendar_uid="cal-123",
416
+ journal_uid="journal-123",
417
+ summary=None,
418
+ description="Invalid description",
419
+ entry_date=None,
420
+ account=None,
421
+ request_id=None,
422
+ )
423
+
424
+ assert result["success"] is False
425
+ assert "Description invalid" in result["error"]
426
+
427
+ @pytest.mark.asyncio
428
+ async def test_update_journal_entry_date_none_in_response(self, setup_managers):
429
+ """Test update_journal with None entry date in response"""
430
+ sample_journal = Mock()
431
+ sample_journal.uid = "journal-123"
432
+ sample_journal.summary = "Test Journal"
433
+ sample_journal.description = "Test content"
434
+ sample_journal.dtstart = None # No entry date
435
+ sample_journal.related_to = []
436
+
437
+ _managers["journal_manager"].update_journal.return_value = sample_journal
438
+
439
+ result = await update_journal.fn(
440
+ calendar_uid="cal-123",
441
+ journal_uid="journal-123",
442
+ summary="Updated",
443
+ description=None,
444
+ entry_date=None,
445
+ account=None,
446
+ request_id=None,
447
+ )
448
+
449
+ assert result["success"] is True
450
+ assert result["journal"]["entry_date"] is None
451
+
452
+ @pytest.mark.asyncio
453
+ async def test_update_journal_malformed_entry_date(self, setup_managers):
454
+ """Test update_journal with malformed entry date triggering parse_datetime error"""
455
+ with patch("chronos_mcp.tools.journals.parse_datetime") as mock_parse:
456
+ mock_parse.side_effect = ValueError("Invalid date format")
457
+
458
+ result = await update_journal.fn(
459
+ calendar_uid="cal-123",
460
+ journal_uid="journal-123",
461
+ summary=None,
462
+ description=None,
463
+ entry_date="invalid-date",
464
+ account=None,
465
+ request_id=None,
466
+ )
467
+
468
+ assert result["success"] is False
469
+
470
+ # DELETE_JOURNAL TOOL TESTS (uses @handle_tool_errors decorator)
471
+
472
+ @pytest.mark.asyncio
473
+ async def test_delete_journal_success(self, setup_managers):
474
+ """Test delete_journal successful execution"""
475
+ _managers["journal_manager"].delete_journal.return_value = True
476
+
477
+ result = await delete_journal.fn(
478
+ calendar_uid="cal-123",
479
+ journal_uid="journal-123",
480
+ account=None,
481
+ request_id=None,
482
+ )
483
+
484
+ assert result["success"] is True
485
+ assert "deleted successfully" in result["message"]
486
+ assert "request_id" in result
487
+
488
+ @pytest.mark.asyncio
489
+ async def test_delete_journal_with_account(self, setup_managers):
490
+ """Test delete_journal with account parameter"""
491
+ _managers["journal_manager"].delete_journal.return_value = True
492
+
493
+ result = await delete_journal.fn(
494
+ calendar_uid="cal-123",
495
+ journal_uid="journal-123",
496
+ account="test_account",
497
+ request_id=None,
498
+ )
499
+
500
+ _managers["journal_manager"].delete_journal.assert_called_once_with(
501
+ calendar_uid="cal-123",
502
+ journal_uid="journal-123",
503
+ account_alias="test_account",
504
+ request_id=result["request_id"],
505
+ )
506
+
507
+ # REGISTER_JOURNAL_TOOLS TESTS
508
+
509
+ def test_register_journal_tools(self, mock_managers, setup_managers):
510
+ """Test register_journal_tools function"""
511
+ mock_mcp = Mock()
512
+
513
+ register_journal_tools(mock_mcp, mock_managers)
514
+
515
+ # Verify managers were updated - strict equality now works with clean state from fixture
516
+ assert _managers == mock_managers
517
+
518
+ # Verify all tools were registered
519
+ assert mock_mcp.tool.call_count == 4
520
+
521
+ # Verify specific tools were registered
522
+ calls = [call[0][0] for call in mock_mcp.tool.call_args_list]
523
+ assert create_journal in calls
524
+ assert list_journals in calls
525
+ assert update_journal in calls
526
+ assert delete_journal in calls
527
+
528
+ # FUNCTION ATTRIBUTE TESTS
529
+
530
+ def test_function_attributes_exist(self):
531
+ """Test that .fn attributes exist for backwards compatibility"""
532
+ assert hasattr(create_journal, "fn")
533
+ assert hasattr(list_journals, "fn")
534
+ assert hasattr(update_journal, "fn")
535
+ assert hasattr(delete_journal, "fn")
536
+
537
+ assert create_journal.fn == create_journal
538
+ assert list_journals.fn == list_journals
539
+ assert update_journal.fn == update_journal
540
+ assert delete_journal.fn == delete_journal
541
+
542
+ # EDGE CASES AND DEFENSIVE PROGRAMMING
543
+
544
+ @pytest.mark.asyncio
545
+ async def test_create_journal_empty_summary(self, setup_managers):
546
+ """Test create_journal with empty summary"""
547
+ with patch(
548
+ "chronos_mcp.tools.journals.InputValidator.validate_text_field"
549
+ ) as mock_validate:
550
+ mock_validate.side_effect = ValidationError("Summary is required")
551
+
552
+ result = await create_journal.fn(
553
+ calendar_uid="cal-123",
554
+ summary="",
555
+ description=None,
556
+ entry_date=None,
557
+ related_to=None,
558
+ account=None,
559
+ )
560
+
561
+ assert result["success"] is False
562
+ assert "Summary is required" in result["error"]
563
+
564
+ @pytest.mark.asyncio
565
+ async def test_list_journals_limit_none(self, setup_managers, sample_journal):
566
+ """Test list_journals with limit as None"""
567
+ _managers["journal_manager"].list_journals.return_value = [sample_journal]
568
+
569
+ result = await list_journals.fn(
570
+ calendar_uid="cal-123", account=None, limit=None
571
+ )
572
+
573
+ assert len(result["journals"]) == 1
574
+ _managers["journal_manager"].list_journals.assert_called_once_with(
575
+ calendar_uid="cal-123", limit=None, account_alias=None
576
+ )
577
+
578
+ @pytest.mark.asyncio
579
+ async def test_managers_not_initialized(self):
580
+ """Test behavior when _managers is not properly initialized"""
581
+ # Clear managers to simulate uninitialized state
582
+ original = _managers.copy()
583
+ _managers.clear()
584
+
585
+ try:
586
+ result = await create_journal.fn(
587
+ calendar_uid="cal-123",
588
+ summary="Test Journal",
589
+ description=None,
590
+ entry_date=None,
591
+ related_to=None,
592
+ account=None,
593
+ )
594
+ # Should get an error response, not an exception
595
+ assert result["success"] is False
596
+ assert "Failed to create journal" in result["error"]
597
+ finally:
598
+ _managers.update(original)
599
+
600
+ @pytest.mark.asyncio
601
+ async def test_create_journal_empty_description_not_validated(
602
+ self, setup_managers, sample_journal
603
+ ):
604
+ """Test create_journal with empty description (should not be validated)"""
605
+ _managers["journal_manager"].create_journal.return_value = sample_journal
606
+
607
+ result = await create_journal.fn(
608
+ calendar_uid="cal-123",
609
+ summary="Test Journal",
610
+ description="", # Empty description should be ignored
611
+ entry_date=None,
612
+ related_to=None,
613
+ account=None,
614
+ )
615
+
616
+ # Empty description should not trigger validation
617
+ assert result["success"] is True