cognee 0.2.3.dev1__py3-none-any.whl → 0.2.4__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 (126) hide show
  1. cognee/__main__.py +4 -0
  2. cognee/api/v1/add/add.py +18 -6
  3. cognee/api/v1/cognify/code_graph_pipeline.py +7 -1
  4. cognee/api/v1/cognify/cognify.py +22 -107
  5. cognee/api/v1/cognify/routers/get_cognify_router.py +11 -3
  6. cognee/api/v1/datasets/routers/get_datasets_router.py +1 -1
  7. cognee/api/v1/responses/default_tools.py +4 -0
  8. cognee/api/v1/responses/dispatch_function.py +6 -1
  9. cognee/api/v1/responses/models.py +1 -1
  10. cognee/api/v1/search/search.py +6 -0
  11. cognee/cli/__init__.py +10 -0
  12. cognee/cli/_cognee.py +180 -0
  13. cognee/cli/commands/__init__.py +1 -0
  14. cognee/cli/commands/add_command.py +80 -0
  15. cognee/cli/commands/cognify_command.py +128 -0
  16. cognee/cli/commands/config_command.py +225 -0
  17. cognee/cli/commands/delete_command.py +80 -0
  18. cognee/cli/commands/search_command.py +149 -0
  19. cognee/cli/config.py +33 -0
  20. cognee/cli/debug.py +21 -0
  21. cognee/cli/echo.py +45 -0
  22. cognee/cli/exceptions.py +23 -0
  23. cognee/cli/minimal_cli.py +97 -0
  24. cognee/cli/reference.py +26 -0
  25. cognee/cli/suppress_logging.py +12 -0
  26. cognee/eval_framework/corpus_builder/corpus_builder_executor.py +2 -2
  27. cognee/eval_framework/eval_config.py +1 -1
  28. cognee/infrastructure/databases/graph/get_graph_engine.py +4 -9
  29. cognee/infrastructure/databases/graph/kuzu/adapter.py +64 -2
  30. cognee/infrastructure/databases/graph/neo4j_driver/adapter.py +49 -0
  31. cognee/infrastructure/databases/vector/embeddings/FastembedEmbeddingEngine.py +5 -3
  32. cognee/infrastructure/databases/vector/embeddings/LiteLLMEmbeddingEngine.py +16 -7
  33. cognee/infrastructure/databases/vector/embeddings/OllamaEmbeddingEngine.py +5 -5
  34. cognee/infrastructure/databases/vector/embeddings/config.py +2 -2
  35. cognee/infrastructure/databases/vector/embeddings/get_embedding_engine.py +6 -6
  36. cognee/infrastructure/files/utils/get_data_file_path.py +14 -9
  37. cognee/infrastructure/files/utils/get_file_metadata.py +2 -1
  38. cognee/infrastructure/llm/LLMGateway.py +14 -5
  39. cognee/infrastructure/llm/config.py +5 -5
  40. cognee/infrastructure/llm/structured_output_framework/baml/baml_src/extraction/knowledge_graph/extract_content_graph.py +16 -5
  41. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/extraction/knowledge_graph/extract_content_graph.py +19 -15
  42. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/anthropic/adapter.py +3 -3
  43. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/gemini/adapter.py +3 -3
  44. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/generic_llm_api/adapter.py +2 -2
  45. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/get_llm_client.py +14 -8
  46. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/ollama/adapter.py +6 -4
  47. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/openai/adapter.py +3 -3
  48. cognee/infrastructure/llm/tokenizer/Gemini/adapter.py +2 -2
  49. cognee/infrastructure/llm/tokenizer/HuggingFace/adapter.py +3 -3
  50. cognee/infrastructure/llm/tokenizer/Mistral/adapter.py +3 -3
  51. cognee/infrastructure/llm/tokenizer/TikToken/adapter.py +6 -6
  52. cognee/infrastructure/llm/utils.py +7 -7
  53. cognee/modules/data/methods/__init__.py +2 -0
  54. cognee/modules/data/methods/create_authorized_dataset.py +19 -0
  55. cognee/modules/data/methods/get_authorized_dataset.py +11 -5
  56. cognee/modules/data/methods/get_authorized_dataset_by_name.py +16 -0
  57. cognee/modules/data/methods/load_or_create_datasets.py +2 -20
  58. cognee/modules/graph/methods/get_formatted_graph_data.py +3 -2
  59. cognee/modules/pipelines/__init__.py +1 -1
  60. cognee/modules/pipelines/exceptions/tasks.py +18 -0
  61. cognee/modules/pipelines/layers/__init__.py +1 -0
  62. cognee/modules/pipelines/layers/check_pipeline_run_qualification.py +59 -0
  63. cognee/modules/pipelines/layers/pipeline_execution_mode.py +127 -0
  64. cognee/modules/pipelines/layers/reset_dataset_pipeline_run_status.py +12 -0
  65. cognee/modules/pipelines/layers/resolve_authorized_user_dataset.py +34 -0
  66. cognee/modules/pipelines/layers/resolve_authorized_user_datasets.py +55 -0
  67. cognee/modules/pipelines/layers/setup_and_check_environment.py +41 -0
  68. cognee/modules/pipelines/layers/validate_pipeline_tasks.py +20 -0
  69. cognee/modules/pipelines/methods/__init__.py +2 -0
  70. cognee/modules/pipelines/methods/get_pipeline_runs_by_dataset.py +34 -0
  71. cognee/modules/pipelines/methods/reset_pipeline_run_status.py +16 -0
  72. cognee/modules/pipelines/operations/__init__.py +0 -1
  73. cognee/modules/pipelines/operations/log_pipeline_run_initiated.py +1 -1
  74. cognee/modules/pipelines/operations/pipeline.py +23 -138
  75. cognee/modules/retrieval/base_feedback.py +11 -0
  76. cognee/modules/retrieval/cypher_search_retriever.py +1 -9
  77. cognee/modules/retrieval/graph_completion_context_extension_retriever.py +9 -2
  78. cognee/modules/retrieval/graph_completion_cot_retriever.py +13 -6
  79. cognee/modules/retrieval/graph_completion_retriever.py +89 -5
  80. cognee/modules/retrieval/graph_summary_completion_retriever.py +2 -0
  81. cognee/modules/retrieval/natural_language_retriever.py +0 -4
  82. cognee/modules/retrieval/user_qa_feedback.py +83 -0
  83. cognee/modules/retrieval/utils/extract_uuid_from_node.py +18 -0
  84. cognee/modules/retrieval/utils/models.py +40 -0
  85. cognee/modules/search/methods/search.py +46 -5
  86. cognee/modules/search/types/SearchType.py +1 -0
  87. cognee/modules/settings/get_settings.py +2 -2
  88. cognee/shared/CodeGraphEntities.py +1 -0
  89. cognee/shared/logging_utils.py +142 -31
  90. cognee/shared/utils.py +0 -1
  91. cognee/tasks/graph/extract_graph_from_data.py +6 -2
  92. cognee/tasks/repo_processor/get_local_dependencies.py +2 -0
  93. cognee/tasks/repo_processor/get_repo_file_dependencies.py +120 -48
  94. cognee/tasks/storage/add_data_points.py +33 -3
  95. cognee/tests/integration/cli/__init__.py +3 -0
  96. cognee/tests/integration/cli/test_cli_integration.py +331 -0
  97. cognee/tests/integration/documents/PdfDocument_test.py +2 -2
  98. cognee/tests/integration/documents/TextDocument_test.py +2 -4
  99. cognee/tests/integration/documents/UnstructuredDocument_test.py +5 -8
  100. cognee/tests/{test_deletion.py → test_delete_hard.py} +0 -37
  101. cognee/tests/test_delete_soft.py +85 -0
  102. cognee/tests/test_kuzu.py +2 -2
  103. cognee/tests/test_neo4j.py +2 -2
  104. cognee/tests/test_search_db.py +126 -7
  105. cognee/tests/unit/cli/__init__.py +3 -0
  106. cognee/tests/unit/cli/test_cli_commands.py +483 -0
  107. cognee/tests/unit/cli/test_cli_edge_cases.py +625 -0
  108. cognee/tests/unit/cli/test_cli_main.py +173 -0
  109. cognee/tests/unit/cli/test_cli_runner.py +62 -0
  110. cognee/tests/unit/cli/test_cli_utils.py +127 -0
  111. cognee/tests/unit/modules/retrieval/graph_completion_retriever_context_extension_test.py +3 -3
  112. cognee/tests/unit/modules/retrieval/graph_completion_retriever_cot_test.py +3 -3
  113. cognee/tests/unit/modules/retrieval/graph_completion_retriever_test.py +3 -3
  114. cognee/tests/unit/modules/search/search_methods_test.py +2 -0
  115. {cognee-0.2.3.dev1.dist-info → cognee-0.2.4.dist-info}/METADATA +7 -5
  116. {cognee-0.2.3.dev1.dist-info → cognee-0.2.4.dist-info}/RECORD +120 -83
  117. cognee-0.2.4.dist-info/entry_points.txt +2 -0
  118. cognee/infrastructure/databases/graph/networkx/__init__.py +0 -0
  119. cognee/infrastructure/databases/graph/networkx/adapter.py +0 -1017
  120. cognee/infrastructure/pipeline/models/Operation.py +0 -60
  121. cognee/infrastructure/pipeline/models/__init__.py +0 -0
  122. cognee/notebooks/github_analysis_step_by_step.ipynb +0 -37
  123. cognee/tests/tasks/descriptive_metrics/networkx_metrics_test.py +0 -7
  124. {cognee-0.2.3.dev1.dist-info → cognee-0.2.4.dist-info}/WHEEL +0 -0
  125. {cognee-0.2.3.dev1.dist-info → cognee-0.2.4.dist-info}/licenses/LICENSE +0 -0
  126. {cognee-0.2.3.dev1.dist-info → cognee-0.2.4.dist-info}/licenses/NOTICE.md +0 -0
@@ -0,0 +1,625 @@
1
+ """
2
+ Tests for CLI edge cases and error scenarios with proper mocking.
3
+ """
4
+
5
+ import pytest
6
+ import sys
7
+ import asyncio
8
+ import argparse
9
+ from unittest.mock import patch, MagicMock, AsyncMock, ANY, call
10
+ from cognee.cli.commands.add_command import AddCommand
11
+ from cognee.cli.commands.search_command import SearchCommand
12
+ from cognee.cli.commands.cognify_command import CognifyCommand
13
+ from cognee.cli.commands.delete_command import DeleteCommand
14
+ from cognee.cli.commands.config_command import ConfigCommand
15
+ from cognee.cli.exceptions import CliCommandException, CliCommandInnerException
16
+
17
+
18
+ # Mock asyncio.run to properly handle coroutines
19
+ def _mock_run(coro):
20
+ # Create an event loop and run the coroutine
21
+ loop = asyncio.new_event_loop()
22
+ try:
23
+ return loop.run_until_complete(coro)
24
+ finally:
25
+ loop.close()
26
+
27
+
28
+ class TestAddCommandEdgeCases:
29
+ """Test edge cases for AddCommand"""
30
+
31
+ @patch("cognee.cli.commands.add_command.asyncio.run", side_effect=_mock_run)
32
+ def test_add_empty_data_list(self, mock_asyncio_run):
33
+ mock_cognee = MagicMock()
34
+ mock_cognee.add = AsyncMock()
35
+
36
+ with patch.dict(sys.modules, {"cognee": mock_cognee}):
37
+ command = AddCommand()
38
+ args = argparse.Namespace(data=[], dataset_name="test_dataset")
39
+ command.execute(args)
40
+
41
+ mock_asyncio_run.assert_called_once()
42
+ assert asyncio.iscoroutine(mock_asyncio_run.call_args[0][0])
43
+ mock_cognee.add.assert_awaited_once_with(data=[], dataset_name="test_dataset")
44
+
45
+ @patch("cognee.cli.commands.add_command.asyncio.run")
46
+ def test_add_asyncio_run_exception(self, mock_asyncio_run):
47
+ """Test add command when asyncio.run itself fails"""
48
+ command = AddCommand()
49
+ args = argparse.Namespace(data=["test.txt"], dataset_name="test_dataset")
50
+
51
+ mock_asyncio_run.side_effect = RuntimeError("Event loop error")
52
+
53
+ with pytest.raises(CliCommandException):
54
+ command.execute(args)
55
+
56
+ def test_add_special_characters_in_data(self):
57
+ """Test add command with special characters in file paths"""
58
+ command = AddCommand()
59
+
60
+ # Create parser to test argument parsing with special characters
61
+ parser = argparse.ArgumentParser()
62
+ command.configure_parser(parser)
63
+
64
+ # Test parsing with special characters
65
+ special_paths = [
66
+ "file with spaces.txt",
67
+ "file-with-dashes.txt",
68
+ "file_with_underscores.txt",
69
+ "file.with.dots.txt",
70
+ ]
71
+
72
+ args = parser.parse_args(special_paths + ["--dataset-name", "test"])
73
+ assert args.data == special_paths
74
+ assert args.dataset_name == "test"
75
+
76
+
77
+ class TestSearchCommandEdgeCases:
78
+ """Test edge cases for SearchCommand"""
79
+
80
+ @patch("cognee.cli.commands.search_command.asyncio.run", side_effect=_mock_run)
81
+ def test_search_empty_results(self, mock_asyncio_run):
82
+ """Test search command with empty results"""
83
+ # Mock the cognee module and SearchType
84
+ mock_cognee = MagicMock()
85
+ mock_cognee.search = AsyncMock(return_value=[])
86
+ mock_search_type = MagicMock()
87
+ mock_search_type.__getitem__.return_value = "GRAPH_COMPLETION"
88
+
89
+ with patch.dict(sys.modules, {"cognee": mock_cognee}):
90
+ command = SearchCommand()
91
+ args = argparse.Namespace(
92
+ query_text="nonexistent query",
93
+ query_type="GRAPH_COMPLETION",
94
+ datasets=None,
95
+ top_k=10,
96
+ system_prompt=None,
97
+ output_format="pretty",
98
+ )
99
+
100
+ # Should handle empty results gracefully
101
+ command.execute(args)
102
+
103
+ mock_asyncio_run.return_value = []
104
+ mock_asyncio_run.assert_called_once()
105
+ assert asyncio.iscoroutine(mock_asyncio_run.call_args[0][0])
106
+ mock_cognee.search.assert_awaited_once_with(
107
+ query_text="nonexistent query",
108
+ query_type=ANY,
109
+ datasets=None,
110
+ top_k=10,
111
+ system_prompt_path="answer_simple_question.txt",
112
+ )
113
+ # verify the enum’s name separately
114
+ called_enum = mock_cognee.search.await_args.kwargs["query_type"]
115
+ assert called_enum.name == "GRAPH_COMPLETION"
116
+
117
+ @patch("cognee.cli.commands.search_command.asyncio.run", side_effect=_mock_run)
118
+ def test_search_very_large_top_k(self, mock_asyncio_run):
119
+ """Test search command with very large top-k value"""
120
+ # Mock the cognee module and SearchType
121
+ mock_cognee = MagicMock()
122
+ mock_cognee.search = AsyncMock(return_value=["result1"])
123
+ mock_search_type = MagicMock()
124
+ mock_search_type.__getitem__.return_value = "CHUNKS"
125
+
126
+ mock_asyncio_run.return_value = ["result1"]
127
+
128
+ with patch.dict(sys.modules, {"cognee": mock_cognee}):
129
+ command = SearchCommand()
130
+ args = argparse.Namespace(
131
+ query_text="test query",
132
+ query_type="CHUNKS",
133
+ datasets=None,
134
+ top_k=999999, # Very large value
135
+ system_prompt=None,
136
+ output_format="json",
137
+ )
138
+
139
+ command.execute(args)
140
+
141
+ mock_asyncio_run.assert_called_once()
142
+ assert asyncio.iscoroutine(mock_asyncio_run.call_args[0][0])
143
+ mock_cognee.search.assert_awaited_once_with(
144
+ query_text="test query",
145
+ query_type=ANY,
146
+ datasets=None,
147
+ top_k=999999,
148
+ system_prompt_path="answer_simple_question.txt",
149
+ )
150
+ # verify the enum’s name separately
151
+ called_enum = mock_cognee.search.await_args.kwargs["query_type"]
152
+ assert called_enum.name == "CHUNKS"
153
+
154
+ @patch("builtins.__import__")
155
+ def test_search_invalid_search_type_enum(self, mock_import):
156
+ """Test search command with invalid SearchType enum conversion"""
157
+ # Mock SearchType to raise KeyError
158
+ mock_search_type = MagicMock()
159
+ mock_search_type.__getitem__.side_effect = KeyError("INVALID_TYPE")
160
+
161
+ def mock_import_func(name, fromlist=None, *args, **kwargs):
162
+ if name == "cognee.modules.search.types":
163
+ module = MagicMock()
164
+ module.SearchType = mock_search_type
165
+ return module
166
+ return MagicMock()
167
+
168
+ mock_import.side_effect = mock_import_func
169
+
170
+ command = SearchCommand()
171
+ args = argparse.Namespace(
172
+ query_text="test query",
173
+ query_type="INVALID_TYPE", # This would fail enum conversion
174
+ datasets=None,
175
+ top_k=10,
176
+ system_prompt=None,
177
+ output_format="pretty",
178
+ )
179
+
180
+ with pytest.raises(CliCommandException):
181
+ command.execute(args)
182
+
183
+ def test_search_unicode_query(self):
184
+ """Test search command with unicode characters in query"""
185
+ command = SearchCommand()
186
+ parser = argparse.ArgumentParser()
187
+ command.configure_parser(parser)
188
+
189
+ unicode_query = "测试查询 🔍 émojis and spéciál chars"
190
+ args = parser.parse_args([unicode_query])
191
+ assert args.query_text == unicode_query
192
+
193
+ @patch("cognee.cli.commands.search_command.asyncio.run", side_effect=_mock_run)
194
+ def test_search_results_with_none_values(self, mock_asyncio_run):
195
+ """Test search command when results contain None values"""
196
+ # Mock the cognee module and SearchType
197
+ mock_cognee = MagicMock()
198
+ mock_cognee.search = AsyncMock(return_value=[None, "valid result", None])
199
+ mock_search_type = MagicMock()
200
+ mock_search_type.__getitem__.return_value = "CHUNKS"
201
+
202
+ with patch.dict(sys.modules, {"cognee": mock_cognee}):
203
+ command = SearchCommand()
204
+ args = argparse.Namespace(
205
+ query_text="test query",
206
+ query_type="CHUNKS",
207
+ datasets=None,
208
+ top_k=10,
209
+ system_prompt=None,
210
+ output_format="pretty",
211
+ )
212
+
213
+ # Should handle None values gracefully
214
+ command.execute(args)
215
+
216
+ mock_asyncio_run.assert_called_once()
217
+ assert asyncio.iscoroutine(mock_asyncio_run.call_args[0][0])
218
+ mock_cognee.search.assert_awaited_once_with(
219
+ query_text="test query",
220
+ query_type=ANY,
221
+ datasets=None,
222
+ top_k=10,
223
+ system_prompt_path="answer_simple_question.txt",
224
+ )
225
+ # verify the enum’s name separately
226
+ called_enum = mock_cognee.search.await_args.kwargs["query_type"]
227
+ assert called_enum.name == "CHUNKS"
228
+
229
+
230
+ class TestCognifyCommandEdgeCases:
231
+ """Test edge cases for CognifyCommand"""
232
+
233
+ @patch("cognee.cli.commands.cognify_command.asyncio.run", side_effect=_mock_run)
234
+ def test_cognify_invalid_chunk_size(self, mock_asyncio_run):
235
+ """Test cognify command with invalid chunk size"""
236
+ # Mock the cognee module
237
+ mock_cognee = MagicMock()
238
+ mock_cognee.cognify = AsyncMock()
239
+
240
+ with patch.dict(sys.modules, {"cognee": mock_cognee}):
241
+ command = CognifyCommand()
242
+ args = argparse.Namespace(
243
+ datasets=None,
244
+ chunk_size=-100, # Invalid negative chunk size
245
+ ontology_file=None,
246
+ chunker="TextChunker",
247
+ background=False,
248
+ verbose=False,
249
+ )
250
+
251
+ # Should pass the invalid value to cognify and let it handle the validation
252
+ command.execute(args)
253
+
254
+ mock_asyncio_run.assert_called_once()
255
+ assert asyncio.iscoroutine(mock_asyncio_run.call_args[0][0])
256
+ from cognee.modules.chunking.TextChunker import TextChunker
257
+
258
+ mock_cognee.cognify.assert_awaited_once_with(
259
+ datasets=None,
260
+ chunk_size=-100,
261
+ ontology_file_path=None,
262
+ chunker=TextChunker,
263
+ run_in_background=False,
264
+ )
265
+
266
+ @patch("cognee.cli.commands.cognify_command.asyncio.run", side_effect=_mock_run)
267
+ def test_cognify_nonexistent_ontology_file(self, mock_asyncio_run):
268
+ """Test cognify command with nonexistent ontology file"""
269
+ # Mock the cognee module
270
+ mock_cognee = MagicMock()
271
+ mock_cognee.cognify = AsyncMock()
272
+
273
+ with patch.dict(sys.modules, {"cognee": mock_cognee}):
274
+ command = CognifyCommand()
275
+ args = argparse.Namespace(
276
+ datasets=None,
277
+ chunk_size=None,
278
+ ontology_file="/nonexistent/path/ontology.owl",
279
+ chunker="TextChunker",
280
+ background=False,
281
+ verbose=False,
282
+ )
283
+
284
+ # Should pass the path to cognify and let it handle file validation
285
+ command.execute(args)
286
+
287
+ mock_asyncio_run.assert_called_once()
288
+ assert asyncio.iscoroutine(mock_asyncio_run.call_args[0][0])
289
+ from cognee.modules.chunking.TextChunker import TextChunker
290
+
291
+ mock_cognee.cognify.assert_awaited_once_with(
292
+ datasets=None,
293
+ chunk_size=None,
294
+ ontology_file_path="/nonexistent/path/ontology.owl",
295
+ chunker=TextChunker,
296
+ run_in_background=False,
297
+ )
298
+
299
+ @patch("cognee.cli.commands.cognify_command.asyncio.run")
300
+ def test_cognify_langchain_chunker_import_error(self, mock_asyncio_run):
301
+ """Test cognify command when LangchainChunker import fails"""
302
+ # Mock the cognee module
303
+ mock_cognee = MagicMock()
304
+ mock_cognee.cognify = AsyncMock()
305
+
306
+ def mock_import_func(name, fromlist=None, *args, **kwargs):
307
+ if name == "cognee":
308
+ return mock_cognee
309
+ elif (
310
+ name == "cognee.modules.chunking.LangchainChunker"
311
+ and fromlist
312
+ and "LangchainChunker" in fromlist
313
+ ):
314
+ raise ImportError("LangchainChunker not available")
315
+ elif (
316
+ name == "cognee.modules.chunking.TextChunker"
317
+ and fromlist
318
+ and "TextChunker" in fromlist
319
+ ):
320
+ module = MagicMock()
321
+ module.TextChunker = MagicMock()
322
+ return module
323
+ return MagicMock()
324
+
325
+ with (
326
+ patch("builtins.__import__", side_effect=mock_import_func),
327
+ patch.dict(sys.modules, {"cognee": mock_cognee}),
328
+ ):
329
+ command = CognifyCommand()
330
+ args = argparse.Namespace(
331
+ datasets=None,
332
+ chunk_size=None,
333
+ ontology_file=None,
334
+ chunker="LangchainChunker",
335
+ background=False,
336
+ verbose=True,
337
+ )
338
+
339
+ # Should fall back to TextChunker and show warning
340
+ command.execute(args)
341
+
342
+ mock_asyncio_run.assert_called_once()
343
+ assert asyncio.iscoroutine(mock_asyncio_run.call_args[0][0])
344
+
345
+ @patch("cognee.cli.commands.cognify_command.asyncio.run", side_effect=_mock_run)
346
+ def test_cognify_empty_datasets_list(self, mock_asyncio_run):
347
+ """Test cognify command with nonexistent ontology file"""
348
+ # Mock the cognee module
349
+ mock_cognee = MagicMock()
350
+ mock_cognee.cognify = AsyncMock()
351
+
352
+ with patch.dict(sys.modules, {"cognee": mock_cognee}):
353
+ command = CognifyCommand()
354
+ args = argparse.Namespace(
355
+ datasets=[],
356
+ chunk_size=None,
357
+ ontology_file=None,
358
+ chunker="TextChunker",
359
+ background=False,
360
+ verbose=False,
361
+ )
362
+
363
+ command.execute(args)
364
+
365
+ mock_asyncio_run.assert_called_once()
366
+ assert asyncio.iscoroutine(mock_asyncio_run.call_args[0][0])
367
+ from cognee.modules.chunking.TextChunker import TextChunker
368
+
369
+ mock_cognee.cognify.assert_awaited_once_with(
370
+ datasets=None,
371
+ chunk_size=None,
372
+ ontology_file_path=None,
373
+ chunker=TextChunker,
374
+ run_in_background=False,
375
+ )
376
+
377
+
378
+ class TestDeleteCommandEdgeCases:
379
+ """Test edge cases for DeleteCommand"""
380
+
381
+ @patch("cognee.cli.commands.delete_command.fmt.confirm")
382
+ @patch("cognee.cli.commands.delete_command.asyncio.run", side_effect=_mock_run)
383
+ def test_delete_all_with_user_id(self, mock_asyncio_run, mock_confirm):
384
+ """Test delete command with both --all and --user-id"""
385
+ # Mock the cognee module
386
+ mock_cognee = MagicMock()
387
+ mock_cognee.delete = AsyncMock()
388
+
389
+ with patch.dict(sys.modules, {"cognee": mock_cognee}):
390
+ command = DeleteCommand()
391
+ args = argparse.Namespace(dataset_name=None, user_id="test_user", all=True, force=False)
392
+
393
+ mock_confirm.return_value = True
394
+
395
+ # Should handle both flags being set
396
+ command.execute(args)
397
+
398
+ mock_confirm.assert_called_once_with("Delete ALL data from cognee?")
399
+ mock_asyncio_run.assert_called_once()
400
+ assert asyncio.iscoroutine(mock_asyncio_run.call_args[0][0])
401
+ mock_cognee.delete.assert_awaited_once_with(dataset_name=None, user_id="test_user")
402
+
403
+ @patch("cognee.cli.commands.delete_command.fmt.confirm")
404
+ def test_delete_confirmation_keyboard_interrupt(self, mock_confirm):
405
+ """Test delete command when user interrupts confirmation"""
406
+ command = DeleteCommand()
407
+ args = argparse.Namespace(dataset_name="test_dataset", user_id=None, all=False, force=False)
408
+
409
+ mock_confirm.side_effect = KeyboardInterrupt()
410
+
411
+ # Should handle KeyboardInterrupt gracefully
412
+ with pytest.raises(KeyboardInterrupt):
413
+ command.execute(args)
414
+
415
+ @patch("cognee.cli.commands.delete_command.asyncio.run")
416
+ def test_delete_async_exception_handling(self, mock_asyncio_run):
417
+ """Test delete command async exception handling"""
418
+ command = DeleteCommand()
419
+ args = argparse.Namespace(dataset_name="test_dataset", user_id=None, all=False, force=True)
420
+
421
+ # Mock asyncio.run to raise exception directly
422
+ mock_asyncio_run.side_effect = ValueError("Database connection failed")
423
+
424
+ with pytest.raises(CliCommandException):
425
+ command.execute(args)
426
+
427
+ def test_delete_special_characters_in_dataset_name(self):
428
+ """Test delete command with special characters in dataset name"""
429
+ command = DeleteCommand()
430
+ parser = argparse.ArgumentParser()
431
+ command.configure_parser(parser)
432
+
433
+ special_names = [
434
+ "dataset with spaces",
435
+ "dataset-with-dashes",
436
+ "dataset_with_underscores",
437
+ "dataset.with.dots",
438
+ "dataset/with/slashes",
439
+ ]
440
+
441
+ for name in special_names:
442
+ args = parser.parse_args(["--dataset-name", name])
443
+ assert args.dataset_name == name
444
+
445
+
446
+ class TestConfigCommandEdgeCases:
447
+ """Test edge cases for ConfigCommand"""
448
+
449
+ def test_config_no_subcommand_specified(self):
450
+ """Test config command when no subcommand is specified"""
451
+ command = ConfigCommand()
452
+ parser = argparse.ArgumentParser()
453
+ command.configure_parser(parser)
454
+
455
+ # Parse with no subcommand - should set config_action to None
456
+ args = parser.parse_args([])
457
+ assert not hasattr(args, "config_action") or args.config_action is None
458
+
459
+ @patch("builtins.__import__")
460
+ def test_config_get_nonexistent_key(self, mock_import):
461
+ """Test config get with nonexistent key"""
462
+ # Mock config.get to raise exception for nonexistent key
463
+ mock_cognee = MagicMock()
464
+ mock_cognee.config.get = MagicMock(side_effect=KeyError("Key not found"))
465
+ mock_import.return_value = mock_cognee
466
+
467
+ command = ConfigCommand()
468
+ args = argparse.Namespace(config_action="get", key="nonexistent_key")
469
+
470
+ # Should handle the exception gracefully
471
+ command.execute(args)
472
+ mock_cognee.config.get.assert_called_once_with("nonexistent_key")
473
+
474
+ @patch("builtins.__import__")
475
+ def test_config_set_complex_json_value(self, mock_import):
476
+ """Test config set with complex JSON value"""
477
+ # Mock the cognee module
478
+ mock_cognee = MagicMock()
479
+ mock_cognee.config.set = MagicMock()
480
+ mock_import.return_value = mock_cognee
481
+
482
+ command = ConfigCommand()
483
+ complex_json = '{"nested": {"key": "value"}, "array": [1, 2, 3]}'
484
+ complex_json_expected_value = {"nested": {"key": "value"}, "array": [1, 2, 3]}
485
+ args = argparse.Namespace(config_action="set", key="complex_config", value=complex_json)
486
+
487
+ command.execute(args)
488
+ mock_cognee.config.set.assert_called_once_with(
489
+ "complex_config", complex_json_expected_value
490
+ )
491
+
492
+ @patch("builtins.__import__")
493
+ def test_config_set_invalid_json_value(self, mock_import):
494
+ """Test config set with invalid JSON value"""
495
+ # Mock the cognee module
496
+ mock_cognee = MagicMock()
497
+ mock_cognee.config.set = MagicMock()
498
+ mock_import.return_value = mock_cognee
499
+
500
+ command = ConfigCommand()
501
+ invalid_json = '{"invalid": json}'
502
+ args = argparse.Namespace(config_action="set", key="test_key", value=invalid_json)
503
+
504
+ command.execute(args)
505
+ mock_cognee.config.set.assert_called_once_with("test_key", invalid_json)
506
+
507
+ @patch("cognee.cli.commands.config_command.fmt.confirm")
508
+ def test_config_unset_unknown_key(self, mock_confirm):
509
+ """Test config unset with unknown key"""
510
+ # Mock the cognee module
511
+ mock_cognee = MagicMock()
512
+
513
+ with patch.dict(sys.modules, {"cognee": mock_cognee}):
514
+ command = ConfigCommand()
515
+ args = argparse.Namespace(config_action="unset", key="unknown_key", force=False)
516
+
517
+ mock_confirm.return_value = True
518
+
519
+ # Should show error for unknown key
520
+ command.execute(args)
521
+
522
+ mock_confirm.assert_called_once()
523
+
524
+ @patch("builtins.__import__")
525
+ def test_config_unset_method_not_found(self, mock_import):
526
+ """Test config unset when method doesn't exist on config object"""
527
+ # Mock config object without the expected method
528
+ mock_cognee = MagicMock()
529
+ mock_cognee.config = MagicMock()
530
+ # Don't set the set_llm_provider method
531
+ mock_import.return_value = mock_cognee
532
+
533
+ command = ConfigCommand()
534
+ args = argparse.Namespace(config_action="unset", key="llm_provider", force=True)
535
+
536
+ # Should handle AttributeError gracefully
537
+ command.execute(args)
538
+ mock_cognee.config.unset.assert_not_called()
539
+
540
+ def test_config_invalid_subcommand(self):
541
+ """Test config command with invalid subcommand"""
542
+ command = ConfigCommand()
543
+ args = argparse.Namespace(config_action="invalid_action")
544
+
545
+ # Should handle unknown subcommand gracefully
546
+ command.execute(args)
547
+
548
+
549
+ class TestGeneralEdgeCases:
550
+ """Test general edge cases that apply to multiple commands"""
551
+
552
+ def test_command_with_none_args(self):
553
+ """Test command execution with None args"""
554
+ commands = [
555
+ AddCommand(),
556
+ SearchCommand(),
557
+ CognifyCommand(),
558
+ DeleteCommand(),
559
+ ConfigCommand(),
560
+ ]
561
+
562
+ for command in commands:
563
+ # Should not crash with None args, though it might raise exceptions
564
+ try:
565
+ command.execute(None)
566
+ except (AttributeError, CliCommandException):
567
+ # Expected behavior for None args
568
+ pass
569
+
570
+ def test_parser_configuration_with_none_parser(self):
571
+ """Test parser configuration with None parser"""
572
+ commands = [
573
+ AddCommand(),
574
+ SearchCommand(),
575
+ CognifyCommand(),
576
+ DeleteCommand(),
577
+ ConfigCommand(),
578
+ ]
579
+
580
+ for command in commands:
581
+ # Should not crash, though it might raise AttributeError
582
+ try:
583
+ command.configure_parser(None)
584
+ except AttributeError:
585
+ # Expected behavior for None parser
586
+ pass
587
+
588
+ def test_command_properties_are_strings(self):
589
+ """Test that all command properties are proper strings"""
590
+ commands = [
591
+ AddCommand(),
592
+ SearchCommand(),
593
+ CognifyCommand(),
594
+ DeleteCommand(),
595
+ ConfigCommand(),
596
+ ]
597
+
598
+ for command in commands:
599
+ assert isinstance(command.command_string, str)
600
+ assert len(command.command_string) > 0
601
+
602
+ assert isinstance(command.help_string, str)
603
+ assert len(command.help_string) > 0
604
+
605
+ if hasattr(command, "description") and command.description:
606
+ assert isinstance(command.description, str)
607
+
608
+ if hasattr(command, "docs_url") and command.docs_url:
609
+ assert isinstance(command.docs_url, str)
610
+
611
+ @patch("tempfile.NamedTemporaryFile")
612
+ def test_commands_with_temp_files(self, mock_temp_file):
613
+ """Test commands that might work with temporary files"""
614
+ # Mock a temporary file
615
+ mock_file = MagicMock()
616
+ mock_file.name = "/tmp/test_file.txt"
617
+ mock_temp_file.return_value.__enter__.return_value = mock_file
618
+
619
+ # Test AddCommand with temp file
620
+ command = AddCommand()
621
+ parser = argparse.ArgumentParser()
622
+ command.configure_parser(parser)
623
+
624
+ args = parser.parse_args([mock_file.name])
625
+ assert args.data == [mock_file.name]