cognee 0.2.3.dev1__py3-none-any.whl → 0.3.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.
- cognee/__init__.py +2 -0
- cognee/__main__.py +4 -0
- cognee/api/client.py +28 -3
- cognee/api/health.py +10 -13
- cognee/api/v1/add/add.py +20 -6
- cognee/api/v1/add/routers/get_add_router.py +12 -37
- cognee/api/v1/cloud/routers/__init__.py +1 -0
- cognee/api/v1/cloud/routers/get_checks_router.py +23 -0
- cognee/api/v1/cognify/code_graph_pipeline.py +14 -3
- cognee/api/v1/cognify/cognify.py +67 -105
- cognee/api/v1/cognify/routers/get_cognify_router.py +11 -3
- cognee/api/v1/datasets/routers/get_datasets_router.py +16 -5
- cognee/api/v1/memify/routers/__init__.py +1 -0
- cognee/api/v1/memify/routers/get_memify_router.py +100 -0
- cognee/api/v1/notebooks/routers/__init__.py +1 -0
- cognee/api/v1/notebooks/routers/get_notebooks_router.py +96 -0
- cognee/api/v1/responses/default_tools.py +4 -0
- cognee/api/v1/responses/dispatch_function.py +6 -1
- cognee/api/v1/responses/models.py +1 -1
- cognee/api/v1/search/routers/get_search_router.py +20 -1
- cognee/api/v1/search/search.py +17 -4
- cognee/api/v1/sync/__init__.py +17 -0
- cognee/api/v1/sync/routers/__init__.py +3 -0
- cognee/api/v1/sync/routers/get_sync_router.py +241 -0
- cognee/api/v1/sync/sync.py +877 -0
- cognee/api/v1/ui/__init__.py +1 -0
- cognee/api/v1/ui/ui.py +529 -0
- cognee/api/v1/users/routers/get_auth_router.py +13 -1
- cognee/base_config.py +10 -1
- cognee/cli/__init__.py +10 -0
- cognee/cli/_cognee.py +273 -0
- cognee/cli/commands/__init__.py +1 -0
- cognee/cli/commands/add_command.py +80 -0
- cognee/cli/commands/cognify_command.py +128 -0
- cognee/cli/commands/config_command.py +225 -0
- cognee/cli/commands/delete_command.py +80 -0
- cognee/cli/commands/search_command.py +149 -0
- cognee/cli/config.py +33 -0
- cognee/cli/debug.py +21 -0
- cognee/cli/echo.py +45 -0
- cognee/cli/exceptions.py +23 -0
- cognee/cli/minimal_cli.py +97 -0
- cognee/cli/reference.py +26 -0
- cognee/cli/suppress_logging.py +12 -0
- cognee/eval_framework/corpus_builder/corpus_builder_executor.py +2 -2
- cognee/eval_framework/eval_config.py +1 -1
- cognee/infrastructure/databases/graph/config.py +10 -4
- cognee/infrastructure/databases/graph/get_graph_engine.py +4 -9
- cognee/infrastructure/databases/graph/kuzu/adapter.py +199 -2
- cognee/infrastructure/databases/graph/neo4j_driver/adapter.py +138 -0
- cognee/infrastructure/databases/relational/__init__.py +2 -0
- cognee/infrastructure/databases/relational/get_async_session.py +15 -0
- cognee/infrastructure/databases/relational/sqlalchemy/SqlAlchemyAdapter.py +6 -1
- cognee/infrastructure/databases/relational/with_async_session.py +25 -0
- cognee/infrastructure/databases/vector/chromadb/ChromaDBAdapter.py +1 -1
- cognee/infrastructure/databases/vector/config.py +13 -6
- cognee/infrastructure/databases/vector/embeddings/FastembedEmbeddingEngine.py +6 -4
- cognee/infrastructure/databases/vector/embeddings/LiteLLMEmbeddingEngine.py +16 -7
- cognee/infrastructure/databases/vector/embeddings/OllamaEmbeddingEngine.py +5 -5
- cognee/infrastructure/databases/vector/embeddings/config.py +2 -2
- cognee/infrastructure/databases/vector/embeddings/embedding_rate_limiter.py +2 -6
- cognee/infrastructure/databases/vector/embeddings/get_embedding_engine.py +10 -7
- cognee/infrastructure/files/storage/LocalFileStorage.py +9 -0
- cognee/infrastructure/files/storage/S3FileStorage.py +5 -0
- cognee/infrastructure/files/storage/StorageManager.py +7 -1
- cognee/infrastructure/files/storage/storage.py +16 -0
- cognee/infrastructure/files/utils/get_data_file_path.py +14 -9
- cognee/infrastructure/files/utils/get_file_metadata.py +2 -1
- cognee/infrastructure/llm/LLMGateway.py +32 -5
- cognee/infrastructure/llm/config.py +6 -4
- cognee/infrastructure/llm/prompts/extract_query_time.txt +15 -0
- cognee/infrastructure/llm/prompts/generate_event_entity_prompt.txt +25 -0
- cognee/infrastructure/llm/prompts/generate_event_graph_prompt.txt +30 -0
- cognee/infrastructure/llm/structured_output_framework/baml/baml_src/extraction/knowledge_graph/extract_content_graph.py +16 -5
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/extraction/__init__.py +2 -0
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/extraction/extract_event_entities.py +44 -0
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/extraction/knowledge_graph/__init__.py +1 -0
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/extraction/knowledge_graph/extract_content_graph.py +19 -15
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/extraction/knowledge_graph/extract_event_graph.py +46 -0
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/anthropic/adapter.py +3 -3
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/gemini/adapter.py +3 -3
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/generic_llm_api/adapter.py +2 -2
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/get_llm_client.py +14 -8
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/ollama/adapter.py +6 -4
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/openai/adapter.py +28 -4
- cognee/infrastructure/llm/tokenizer/Gemini/adapter.py +2 -2
- cognee/infrastructure/llm/tokenizer/HuggingFace/adapter.py +3 -3
- cognee/infrastructure/llm/tokenizer/Mistral/adapter.py +3 -3
- cognee/infrastructure/llm/tokenizer/TikToken/adapter.py +6 -6
- cognee/infrastructure/llm/utils.py +7 -7
- cognee/infrastructure/utils/run_sync.py +8 -1
- cognee/modules/chunking/models/DocumentChunk.py +4 -3
- cognee/modules/cloud/exceptions/CloudApiKeyMissingError.py +15 -0
- cognee/modules/cloud/exceptions/CloudConnectionError.py +15 -0
- cognee/modules/cloud/exceptions/__init__.py +2 -0
- cognee/modules/cloud/operations/__init__.py +1 -0
- cognee/modules/cloud/operations/check_api_key.py +25 -0
- cognee/modules/data/deletion/prune_system.py +1 -1
- cognee/modules/data/methods/__init__.py +2 -0
- cognee/modules/data/methods/check_dataset_name.py +1 -1
- cognee/modules/data/methods/create_authorized_dataset.py +19 -0
- cognee/modules/data/methods/get_authorized_dataset.py +11 -5
- cognee/modules/data/methods/get_authorized_dataset_by_name.py +16 -0
- cognee/modules/data/methods/get_dataset_data.py +1 -1
- cognee/modules/data/methods/load_or_create_datasets.py +2 -20
- cognee/modules/engine/models/Event.py +16 -0
- cognee/modules/engine/models/Interval.py +8 -0
- cognee/modules/engine/models/Timestamp.py +13 -0
- cognee/modules/engine/models/__init__.py +3 -0
- cognee/modules/engine/utils/__init__.py +2 -0
- cognee/modules/engine/utils/generate_event_datapoint.py +46 -0
- cognee/modules/engine/utils/generate_timestamp_datapoint.py +51 -0
- cognee/modules/graph/cognee_graph/CogneeGraph.py +2 -2
- cognee/modules/graph/methods/get_formatted_graph_data.py +3 -2
- cognee/modules/graph/utils/__init__.py +1 -0
- cognee/modules/graph/utils/resolve_edges_to_text.py +71 -0
- cognee/modules/memify/__init__.py +1 -0
- cognee/modules/memify/memify.py +118 -0
- cognee/modules/notebooks/methods/__init__.py +5 -0
- cognee/modules/notebooks/methods/create_notebook.py +26 -0
- cognee/modules/notebooks/methods/delete_notebook.py +13 -0
- cognee/modules/notebooks/methods/get_notebook.py +21 -0
- cognee/modules/notebooks/methods/get_notebooks.py +18 -0
- cognee/modules/notebooks/methods/update_notebook.py +17 -0
- cognee/modules/notebooks/models/Notebook.py +53 -0
- cognee/modules/notebooks/models/__init__.py +1 -0
- cognee/modules/notebooks/operations/__init__.py +1 -0
- cognee/modules/notebooks/operations/run_in_local_sandbox.py +55 -0
- cognee/modules/pipelines/__init__.py +1 -1
- cognee/modules/pipelines/exceptions/tasks.py +18 -0
- cognee/modules/pipelines/layers/__init__.py +1 -0
- cognee/modules/pipelines/layers/check_pipeline_run_qualification.py +59 -0
- cognee/modules/pipelines/layers/pipeline_execution_mode.py +127 -0
- cognee/modules/pipelines/layers/reset_dataset_pipeline_run_status.py +28 -0
- cognee/modules/pipelines/layers/resolve_authorized_user_dataset.py +34 -0
- cognee/modules/pipelines/layers/resolve_authorized_user_datasets.py +55 -0
- cognee/modules/pipelines/layers/setup_and_check_environment.py +41 -0
- cognee/modules/pipelines/layers/validate_pipeline_tasks.py +20 -0
- cognee/modules/pipelines/methods/__init__.py +2 -0
- cognee/modules/pipelines/methods/get_pipeline_runs_by_dataset.py +34 -0
- cognee/modules/pipelines/methods/reset_pipeline_run_status.py +16 -0
- cognee/modules/pipelines/operations/__init__.py +0 -1
- cognee/modules/pipelines/operations/log_pipeline_run_initiated.py +1 -1
- cognee/modules/pipelines/operations/pipeline.py +24 -138
- cognee/modules/pipelines/operations/run_tasks.py +17 -41
- cognee/modules/retrieval/base_feedback.py +11 -0
- cognee/modules/retrieval/base_graph_retriever.py +18 -0
- cognee/modules/retrieval/base_retriever.py +1 -1
- cognee/modules/retrieval/code_retriever.py +8 -0
- cognee/modules/retrieval/coding_rules_retriever.py +31 -0
- cognee/modules/retrieval/completion_retriever.py +9 -3
- cognee/modules/retrieval/context_providers/TripletSearchContextProvider.py +1 -0
- cognee/modules/retrieval/cypher_search_retriever.py +1 -9
- cognee/modules/retrieval/graph_completion_context_extension_retriever.py +29 -13
- cognee/modules/retrieval/graph_completion_cot_retriever.py +30 -13
- cognee/modules/retrieval/graph_completion_retriever.py +107 -56
- cognee/modules/retrieval/graph_summary_completion_retriever.py +5 -1
- cognee/modules/retrieval/insights_retriever.py +14 -3
- cognee/modules/retrieval/natural_language_retriever.py +0 -4
- cognee/modules/retrieval/summaries_retriever.py +1 -1
- cognee/modules/retrieval/temporal_retriever.py +152 -0
- cognee/modules/retrieval/user_qa_feedback.py +83 -0
- cognee/modules/retrieval/utils/brute_force_triplet_search.py +7 -32
- cognee/modules/retrieval/utils/completion.py +10 -3
- cognee/modules/retrieval/utils/extract_uuid_from_node.py +18 -0
- cognee/modules/retrieval/utils/models.py +40 -0
- cognee/modules/search/methods/get_search_type_tools.py +168 -0
- cognee/modules/search/methods/no_access_control_search.py +47 -0
- cognee/modules/search/methods/search.py +239 -118
- cognee/modules/search/types/SearchResult.py +21 -0
- cognee/modules/search/types/SearchType.py +3 -0
- cognee/modules/search/types/__init__.py +1 -0
- cognee/modules/search/utils/__init__.py +2 -0
- cognee/modules/search/utils/prepare_search_result.py +41 -0
- cognee/modules/search/utils/transform_context_to_graph.py +38 -0
- cognee/modules/settings/get_settings.py +2 -2
- cognee/modules/sync/__init__.py +1 -0
- cognee/modules/sync/methods/__init__.py +23 -0
- cognee/modules/sync/methods/create_sync_operation.py +53 -0
- cognee/modules/sync/methods/get_sync_operation.py +107 -0
- cognee/modules/sync/methods/update_sync_operation.py +248 -0
- cognee/modules/sync/models/SyncOperation.py +142 -0
- cognee/modules/sync/models/__init__.py +3 -0
- cognee/modules/users/__init__.py +0 -1
- cognee/modules/users/methods/__init__.py +4 -1
- cognee/modules/users/methods/create_user.py +26 -1
- cognee/modules/users/methods/get_authenticated_user.py +36 -42
- cognee/modules/users/methods/get_default_user.py +3 -1
- cognee/modules/users/permissions/methods/get_specific_user_permission_datasets.py +2 -1
- cognee/root_dir.py +19 -0
- cognee/shared/CodeGraphEntities.py +1 -0
- cognee/shared/logging_utils.py +143 -32
- cognee/shared/utils.py +0 -1
- cognee/tasks/codingagents/coding_rule_associations.py +127 -0
- cognee/tasks/graph/extract_graph_from_data.py +6 -2
- cognee/tasks/ingestion/save_data_item_to_storage.py +23 -0
- cognee/tasks/memify/__init__.py +2 -0
- cognee/tasks/memify/extract_subgraph.py +7 -0
- cognee/tasks/memify/extract_subgraph_chunks.py +11 -0
- cognee/tasks/repo_processor/get_local_dependencies.py +2 -0
- cognee/tasks/repo_processor/get_repo_file_dependencies.py +144 -47
- cognee/tasks/storage/add_data_points.py +33 -3
- cognee/tasks/temporal_graph/__init__.py +1 -0
- cognee/tasks/temporal_graph/add_entities_to_event.py +85 -0
- cognee/tasks/temporal_graph/enrich_events.py +34 -0
- cognee/tasks/temporal_graph/extract_events_and_entities.py +32 -0
- cognee/tasks/temporal_graph/extract_knowledge_graph_from_events.py +41 -0
- cognee/tasks/temporal_graph/models.py +49 -0
- cognee/tests/integration/cli/__init__.py +3 -0
- cognee/tests/integration/cli/test_cli_integration.py +331 -0
- cognee/tests/integration/documents/PdfDocument_test.py +2 -2
- cognee/tests/integration/documents/TextDocument_test.py +2 -4
- cognee/tests/integration/documents/UnstructuredDocument_test.py +5 -8
- cognee/tests/{test_deletion.py → test_delete_hard.py} +0 -37
- cognee/tests/test_delete_soft.py +85 -0
- cognee/tests/test_kuzu.py +2 -2
- cognee/tests/test_neo4j.py +2 -2
- cognee/tests/test_permissions.py +3 -3
- cognee/tests/test_relational_db_migration.py +7 -5
- cognee/tests/test_search_db.py +136 -23
- cognee/tests/test_temporal_graph.py +167 -0
- cognee/tests/unit/api/__init__.py +1 -0
- cognee/tests/unit/api/test_conditional_authentication_endpoints.py +246 -0
- cognee/tests/unit/cli/__init__.py +3 -0
- cognee/tests/unit/cli/test_cli_commands.py +483 -0
- cognee/tests/unit/cli/test_cli_edge_cases.py +625 -0
- cognee/tests/unit/cli/test_cli_main.py +173 -0
- cognee/tests/unit/cli/test_cli_runner.py +62 -0
- cognee/tests/unit/cli/test_cli_utils.py +127 -0
- cognee/tests/unit/modules/retrieval/chunks_retriever_test.py +18 -2
- cognee/tests/unit/modules/retrieval/graph_completion_retriever_context_extension_test.py +12 -15
- cognee/tests/unit/modules/retrieval/graph_completion_retriever_cot_test.py +10 -15
- cognee/tests/unit/modules/retrieval/graph_completion_retriever_test.py +4 -3
- cognee/tests/unit/modules/retrieval/insights_retriever_test.py +4 -2
- cognee/tests/unit/modules/retrieval/rag_completion_retriever_test.py +18 -2
- cognee/tests/unit/modules/retrieval/temporal_retriever_test.py +225 -0
- cognee/tests/unit/modules/users/__init__.py +1 -0
- cognee/tests/unit/modules/users/test_conditional_authentication.py +277 -0
- cognee/tests/unit/processing/utils/utils_test.py +20 -1
- {cognee-0.2.3.dev1.dist-info → cognee-0.3.0.dist-info}/METADATA +13 -9
- {cognee-0.2.3.dev1.dist-info → cognee-0.3.0.dist-info}/RECORD +247 -135
- cognee-0.3.0.dist-info/entry_points.txt +2 -0
- cognee/infrastructure/databases/graph/networkx/adapter.py +0 -1017
- cognee/infrastructure/pipeline/models/Operation.py +0 -60
- cognee/notebooks/github_analysis_step_by_step.ipynb +0 -37
- cognee/tests/tasks/descriptive_metrics/networkx_metrics_test.py +0 -7
- cognee/tests/unit/modules/search/search_methods_test.py +0 -223
- /cognee/{infrastructure/databases/graph/networkx → api/v1/memify}/__init__.py +0 -0
- /cognee/{infrastructure/pipeline/models → tasks/codingagents}/__init__.py +0 -0
- {cognee-0.2.3.dev1.dist-info → cognee-0.3.0.dist-info}/WHEEL +0 -0
- {cognee-0.2.3.dev1.dist-info → cognee-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {cognee-0.2.3.dev1.dist-info → cognee-0.3.0.dist-info}/licenses/NOTICE.md +0 -0
cognee/tests/test_search_db.py
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import pathlib
|
|
3
|
-
|
|
4
|
-
from dns.e164 import query
|
|
5
|
-
|
|
6
1
|
import cognee
|
|
2
|
+
from cognee.infrastructure.databases.graph import get_graph_engine
|
|
7
3
|
from cognee.modules.graph.cognee_graph.CogneeGraphElements import Edge
|
|
4
|
+
from cognee.modules.graph.utils import resolve_edges_to_text
|
|
8
5
|
from cognee.modules.retrieval.graph_completion_retriever import GraphCompletionRetriever
|
|
9
6
|
from cognee.modules.retrieval.graph_completion_context_extension_retriever import (
|
|
10
7
|
GraphCompletionContextExtensionRetriever,
|
|
@@ -13,11 +10,9 @@ from cognee.modules.retrieval.graph_completion_cot_retriever import GraphComplet
|
|
|
13
10
|
from cognee.modules.retrieval.graph_summary_completion_retriever import (
|
|
14
11
|
GraphSummaryCompletionRetriever,
|
|
15
12
|
)
|
|
16
|
-
from cognee.modules.search.operations import get_history
|
|
17
|
-
from cognee.modules.users.methods import get_default_user
|
|
18
13
|
from cognee.shared.logging_utils import get_logger
|
|
19
14
|
from cognee.modules.search.types import SearchType
|
|
20
|
-
from
|
|
15
|
+
from collections import Counter
|
|
21
16
|
|
|
22
17
|
logger = get_logger()
|
|
23
18
|
|
|
@@ -63,9 +58,11 @@ async def main():
|
|
|
63
58
|
("GraphCompletionContextExtensionRetriever", context_gk_ext),
|
|
64
59
|
("GraphSummaryCompletionRetriever", context_gk_sum),
|
|
65
60
|
]:
|
|
66
|
-
assert isinstance(context,
|
|
67
|
-
assert context
|
|
68
|
-
|
|
61
|
+
assert isinstance(context, list), f"{name}: Context should be a list"
|
|
62
|
+
assert len(context) > 0, f"{name}: Context should not be empty"
|
|
63
|
+
|
|
64
|
+
context_text = await resolve_edges_to_text(context)
|
|
65
|
+
lower = context_text.lower()
|
|
69
66
|
assert "germany" in lower or "netherlands" in lower, (
|
|
70
67
|
f"{name}: Context did not contain 'germany' or 'netherlands'; got: {context!r}"
|
|
71
68
|
)
|
|
@@ -111,35 +108,151 @@ async def main():
|
|
|
111
108
|
|
|
112
109
|
completion_gk = await cognee.search(
|
|
113
110
|
query_type=SearchType.GRAPH_COMPLETION,
|
|
114
|
-
query_text="
|
|
111
|
+
query_text="Where is germany located, next to which country?",
|
|
112
|
+
save_interaction=True,
|
|
115
113
|
)
|
|
116
114
|
completion_cot = await cognee.search(
|
|
117
115
|
query_type=SearchType.GRAPH_COMPLETION_COT,
|
|
118
|
-
query_text="
|
|
116
|
+
query_text="What is the country next to germany??",
|
|
117
|
+
save_interaction=True,
|
|
119
118
|
)
|
|
120
119
|
completion_ext = await cognee.search(
|
|
121
120
|
query_type=SearchType.GRAPH_COMPLETION_CONTEXT_EXTENSION,
|
|
122
|
-
query_text="
|
|
121
|
+
query_text="What is the name of the country next to germany",
|
|
122
|
+
save_interaction=True,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
await cognee.search(
|
|
126
|
+
query_type=SearchType.FEEDBACK, query_text="This was not the best answer", last_k=1
|
|
123
127
|
)
|
|
128
|
+
|
|
124
129
|
completion_sum = await cognee.search(
|
|
125
130
|
query_type=SearchType.GRAPH_SUMMARY_COMPLETION,
|
|
126
131
|
query_text="Next to which country is Germany located?",
|
|
132
|
+
save_interaction=True,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
await cognee.search(
|
|
136
|
+
query_type=SearchType.FEEDBACK,
|
|
137
|
+
query_text="This answer was great",
|
|
138
|
+
last_k=1,
|
|
127
139
|
)
|
|
128
140
|
|
|
129
|
-
for name,
|
|
141
|
+
for name, search_results in [
|
|
130
142
|
("GRAPH_COMPLETION", completion_gk),
|
|
131
143
|
("GRAPH_COMPLETION_COT", completion_cot),
|
|
132
144
|
("GRAPH_COMPLETION_CONTEXT_EXTENSION", completion_ext),
|
|
133
145
|
("GRAPH_SUMMARY_COMPLETION", completion_sum),
|
|
134
146
|
]:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
147
|
+
for search_result in search_results:
|
|
148
|
+
completion = search_result.search_result
|
|
149
|
+
assert isinstance(completion, str), f"{name}: should return a string"
|
|
150
|
+
assert completion.strip(), f"{name}: string should not be empty"
|
|
151
|
+
assert "netherlands" in completion.lower(), (
|
|
152
|
+
f"{name}: expected 'netherlands' in result, got: {completion!r}"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
graph_engine = await get_graph_engine()
|
|
156
|
+
graph = await graph_engine.get_graph_data()
|
|
157
|
+
|
|
158
|
+
type_counts = Counter(node_data[1].get("type", {}) for node_data in graph[0])
|
|
159
|
+
|
|
160
|
+
edge_type_counts = Counter(edge_type[2] for edge_type in graph[1])
|
|
161
|
+
|
|
162
|
+
# Assert there are exactly 4 CogneeUserInteraction nodes.
|
|
163
|
+
assert type_counts.get("CogneeUserInteraction", 0) == 4, (
|
|
164
|
+
f"Expected exactly four DCogneeUserInteraction nodes, but found {type_counts.get('CogneeUserInteraction', 0)}"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Assert there is exactly two CogneeUserFeedback nodes.
|
|
168
|
+
assert type_counts.get("CogneeUserFeedback", 0) == 2, (
|
|
169
|
+
f"Expected exactly two CogneeUserFeedback nodes, but found {type_counts.get('CogneeUserFeedback', 0)}"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Assert there is exactly two NodeSet.
|
|
173
|
+
assert type_counts.get("NodeSet", 0) == 2, (
|
|
174
|
+
f"Expected exactly two NodeSet nodes, but found {type_counts.get('NodeSet', 0)}"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Assert that there are at least 10 'used_graph_element_to_answer' edges.
|
|
178
|
+
assert edge_type_counts.get("used_graph_element_to_answer", 0) >= 10, (
|
|
179
|
+
f"Expected at least ten 'used_graph_element_to_answer' edges, but found {edge_type_counts.get('used_graph_element_to_answer', 0)}"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Assert that there are exactly 2 'gives_feedback_to' edges.
|
|
183
|
+
assert edge_type_counts.get("gives_feedback_to", 0) == 2, (
|
|
184
|
+
f"Expected exactly two 'gives_feedback_to' edges, but found {edge_type_counts.get('gives_feedback_to', 0)}"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# Assert that there are at least 6 'belongs_to_set' edges.
|
|
188
|
+
assert edge_type_counts.get("belongs_to_set", 0) == 6, (
|
|
189
|
+
f"Expected at least six 'belongs_to_set' edges, but found {edge_type_counts.get('belongs_to_set', 0)}"
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
nodes = graph[0]
|
|
193
|
+
|
|
194
|
+
required_fields_user_interaction = {"question", "answer", "context"}
|
|
195
|
+
required_fields_feedback = {"feedback", "sentiment"}
|
|
196
|
+
|
|
197
|
+
for node_id, data in nodes:
|
|
198
|
+
if data.get("type") == "CogneeUserInteraction":
|
|
199
|
+
assert required_fields_user_interaction.issubset(data.keys()), (
|
|
200
|
+
f"Node {node_id} is missing fields: {required_fields_user_interaction - set(data.keys())}"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
for field in required_fields_user_interaction:
|
|
204
|
+
value = data[field]
|
|
205
|
+
assert isinstance(value, str) and value.strip(), (
|
|
206
|
+
f"Node {node_id} has invalid value for '{field}': {value!r}"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if data.get("type") == "CogneeUserFeedback":
|
|
210
|
+
assert required_fields_feedback.issubset(data.keys()), (
|
|
211
|
+
f"Node {node_id} is missing fields: {required_fields_feedback - set(data.keys())}"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
for field in required_fields_feedback:
|
|
215
|
+
value = data[field]
|
|
216
|
+
assert isinstance(value, str) and value.strip(), (
|
|
217
|
+
f"Node {node_id} has invalid value for '{field}': {value!r}"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
await cognee.prune.prune_data()
|
|
221
|
+
await cognee.prune.prune_system(metadata=True)
|
|
222
|
+
|
|
223
|
+
await cognee.add(text_1, dataset_name)
|
|
224
|
+
|
|
225
|
+
await cognee.add([text], dataset_name)
|
|
226
|
+
|
|
227
|
+
await cognee.cognify([dataset_name])
|
|
228
|
+
|
|
229
|
+
await cognee.search(
|
|
230
|
+
query_type=SearchType.GRAPH_COMPLETION,
|
|
231
|
+
query_text="Next to which country is Germany located?",
|
|
232
|
+
save_interaction=True,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
await cognee.search(
|
|
236
|
+
query_type=SearchType.FEEDBACK,
|
|
237
|
+
query_text="This was the best answer I've ever seen",
|
|
238
|
+
last_k=1,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
await cognee.search(
|
|
242
|
+
query_type=SearchType.FEEDBACK,
|
|
243
|
+
query_text="Wow the correctness of this answer blows my mind",
|
|
244
|
+
last_k=1,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
graph = await graph_engine.get_graph_data()
|
|
248
|
+
|
|
249
|
+
edges = graph[1]
|
|
250
|
+
|
|
251
|
+
for from_node, to_node, relationship_name, properties in edges:
|
|
252
|
+
if relationship_name == "used_graph_element_to_answer":
|
|
253
|
+
assert properties["feedback_weight"] >= 6, (
|
|
254
|
+
"Feedback weight calculation is not correct, it should be more then 6."
|
|
255
|
+
)
|
|
143
256
|
|
|
144
257
|
|
|
145
258
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import cognee
|
|
3
|
+
from cognee.modules.retrieval.temporal_retriever import TemporalRetriever
|
|
4
|
+
|
|
5
|
+
from cognee.shared.logging_utils import setup_logging, INFO
|
|
6
|
+
from cognee.tasks.temporal_graph.models import Timestamp
|
|
7
|
+
from cognee.api.v1.search import SearchType
|
|
8
|
+
from cognee.shared.logging_utils import get_logger
|
|
9
|
+
from cognee.infrastructure.databases.graph.get_graph_engine import get_graph_engine
|
|
10
|
+
from collections import Counter
|
|
11
|
+
from cognee.modules.engine.utils.generate_timestamp_datapoint import date_to_int
|
|
12
|
+
|
|
13
|
+
logger = get_logger()
|
|
14
|
+
|
|
15
|
+
biography_1 = """
|
|
16
|
+
Attaphol Buspakom Attaphol Buspakom ( ; ) , nicknamed Tak ( ; ) ; 1 October 1962 – 16 April 2015 ) was a Thai national and football coach . He was given the role at Muangthong United and Buriram United after TTM Samut Sakhon folded after the 2009 season . He played for the Thailand national football team , appearing in several FIFA World Cup qualifying matches .
|
|
17
|
+
|
|
18
|
+
Club career .
|
|
19
|
+
Attaphol began his career as a player at Thai Port FC Authority of Thailand in 1985 . In his first year , he won his first championship with the club . He played for the club until 1989 and in 1987 also won the Queens Cup . He then moved to Malaysia for two seasons for Pahang FA , then return to Thailand to his former club . His time from 1991 to 1994 was marked by less success than in his first stay at Port Authority . From 1994 to 1996 he played for Pahang again and this time he was able to win with the club , the Malaysia Super League and also reached the final of the Malaysia Cup and the Malaysia FA Cup . Both cup finals but lost . Back in Thailand , he let end his playing career at FC Stock Exchange of Thailand , with which he once again runner‑up in 1996-97 . In 1998 , he finished his career .
|
|
20
|
+
|
|
21
|
+
International career .
|
|
22
|
+
For the Thailand national football team Attaphol played between 1985 and 1998 a total of 85 games and scored 13 results . In 1992 , he participated with the team in the finals of the Asian Cup . He also stood in various cadres to qualifications to FIFA World Cup .
|
|
23
|
+
|
|
24
|
+
Coaching career .
|
|
25
|
+
Bec Tero Sasana .
|
|
26
|
+
In BEC Tero Sasana F.C . began his coaching career in 2001 for him , first as assistant coach . He took over the reigning champions of the Thai League T1 , after his predecessor Pichai Pituwong resigned from his post . It was his first coach station and he had the difficult task of leading the club through the new AFC Champions League . He could accomplish this task with flying colors and even led the club to the finals . The finale , then still played in home and away matches , was lost with 1:2 at the end against Al Ain FC . Attaphol is and was next to Charnwit Polcheewin the only coach who managed a club from Thailand to lead to the final of the AFC Champions League . 2002-03 and 2003-04 he won with the club also two runner‑up . In his team , which reached the final of the Champions League , were a number of exceptional players like Therdsak Chaiman , Worrawoot Srimaka , Dusit Chalermsan and Anurak Srikerd .
|
|
27
|
+
|
|
28
|
+
Geylang United / Krung Thai Bank .
|
|
29
|
+
In 2006 , he went to Singapore in the S‑League to Geylang United He was released after a few months due to lack of success . In 2008 , he took over as coach at Krung Thai Bank F.C. , where he had almost a similar task , as a few years earlier by BEC‑Tero . As vice‑champion of the club was also qualified for the AFC Champions League . However , he failed to lead the team through the group stage of the season 2008 and beyond . With the Kashima Antlers of Japan and Beijing Guoan F.C . athletic competition was too great . One of the highlights was put under his leadership , yet the club . In the group match against the Vietnam club Nam Dinh F.C . his team won with 9-1 , but also lost four weeks later with 1-8 against Kashima Antlers . At the end of the National Football League season , he reached the Krung Thai 6th Table space . The Erstligalizenz the club was sold at the end of the season at the Bangkok Glass F.C. . Attaphol finished his coaching career with the club and accepted an offer of TTM Samutsakorn . After only a short time in office
|
|
30
|
+
|
|
31
|
+
Muangthong United .
|
|
32
|
+
In 2009 , he received an offer from Muangthong United F.C. , which he accepted and changed . He can champion Muang Thong United for 2009 Thai Premier League and Attaphol won Coach of The year for Thai Premier League and he was able to lead Muang Thong United to play AFC Champions League qualifying play‑off for the first in the clubs history .
|
|
33
|
+
|
|
34
|
+
Buriram United .
|
|
35
|
+
In 2010 Buspakom moved from Muangthong United to Buriram United F.C. . He received Coach of the Month in Thai Premier League 2 time in June and October . In 2011 , he led Buriram United win 2011 Thai Premier League second time for club and set a record with the most points in the Thai League T1 for 85 point and He led Buriram win 2011 Thai FA Cup by beat Muangthong United F.C . 1‑0 and he led Buriram win 2011 Thai League Cup by beat Thai Port F.C . 2‑0 . In 2012 , he led Buriram United to the 2012 AFC Champions League group stage . Buriram along with Guangzhou Evergrande F.C . from China , Kashiwa Reysol from Japan and Jeonbuk Hyundai Motors which are all champions from their country . In the first match of Buriram they beat Kashiwa 3‑2 and Second Match they beat Guangzhou 1‑2 at the Tianhe Stadium . Before losing to Jeonbuk 0‑2 and 3‑2 with lose Kashiwa and Guangzhou 1‑0 and 1‑2 respectively and Thai Premier League Attaphol lead Buriram end 4th for table with win 2012 Thai FA Cup and 2012 Thai League Cup .
|
|
36
|
+
|
|
37
|
+
Bangkok Glass .
|
|
38
|
+
In 2013 , he moved from Buriram United to Bangkok Glass F.C. .
|
|
39
|
+
|
|
40
|
+
Individual
|
|
41
|
+
- Thai Premier League Coach of the Year ( 3 ) : 2001-02 , 2009 , 2013
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
biography_2 = """
|
|
46
|
+
Arnulf Øverland Ole Peter Arnulf Øverland ( 27 April 1889 – 25 March 1968 ) was a Norwegian poet and artist . He is principally known for his poetry which served to inspire the Norwegian resistance movement during the German occupation of Norway during World War II .
|
|
47
|
+
|
|
48
|
+
Biography .
|
|
49
|
+
Øverland was born in Kristiansund and raised in Bergen . His parents were Peter Anton Øverland ( 1852–1906 ) and Hanna Hage ( 1854–1939 ) . The early death of his father , left the family economically stressed . He was able to attend Bergen Cathedral School and in 1904 Kristiania Cathedral School . He graduated in 1907 and for a time studied philology at University of Kristiania . Øverland published his first collection of poems ( 1911 ) .
|
|
50
|
+
|
|
51
|
+
Øverland became a communist sympathizer from the early 1920s and became a member of Mot Dag . He also served as chairman of the Norwegian Students Society 1923–28 . He changed his stand in 1937 , partly as an expression of dissent against the ongoing Moscow Trials . He was an avid opponent of Nazism and in 1936 he wrote the poem Du må ikke sove which was printed in the journal Samtiden . It ends with . ( I thought: : Something is imminent . Our era is over – Europe’s on fire! ) . Probably the most famous line of the poem is ( You mustnt endure so well the injustice that doesnt affect you yourself! )
|
|
52
|
+
|
|
53
|
+
During the German occupation of Norway from 1940 in World War II , he wrote to inspire the Norwegian resistance movement . He wrote a series of poems which were clandestinely distributed , leading to the arrest of both him and his future wife Margrete Aamot Øverland in 1941 . Arnulf Øverland was held first in the prison camp of Grini before being transferred to Sachsenhausen concentration camp in Germany . He spent a four‑year imprisonment until the liberation of Norway in 1945 . His poems were later collected in Vi overlever alt and published in 1945 .
|
|
54
|
+
|
|
55
|
+
Øverland played an important role in the Norwegian language struggle in the post‑war era . He became a noted supporter for the conservative written form of Norwegian called Riksmål , he was president of Riksmålsforbundet ( an organization in support of Riksmål ) from 1947 to 1956 . In addition , Øverland adhered to the traditionalist style of writing , criticising modernist poetry on several occasions . His speech Tungetale fra parnasset , published in Arbeiderbladet in 1954 , initiated the so‑called Glossolalia debate .
|
|
56
|
+
|
|
57
|
+
Personal life .
|
|
58
|
+
In 1918 he had married the singer Hildur Arntzen ( 1888–1957 ) . Their marriage was dissolved in 1939 . In 1940 , he married Bartholine Eufemia Leganger ( 1903–1995 ) . They separated shortly after , and were officially divorced in 1945 . Øverland was married to journalist Margrete Aamot Øverland ( 1913–1978 ) during June 1945 . In 1946 , the Norwegian Parliament arranged for Arnulf and Margrete Aamot Øverland to reside at the Grotten . He lived there until his death in 1968 and she lived there for another ten years until her death in 1978 . Arnulf Øverland was buried at Vår Frelsers Gravlund in Oslo . Joseph Grimeland designed the bust of Arnulf Øverland ( bronze , 1970 ) at his grave site .
|
|
59
|
+
|
|
60
|
+
Selected Works .
|
|
61
|
+
- Den ensomme fest ( 1911 )
|
|
62
|
+
- Berget det blå ( 1927 )
|
|
63
|
+
- En Hustavle ( 1929 )
|
|
64
|
+
- Den røde front ( 1937 )
|
|
65
|
+
- Vi overlever alt ( 1945 )
|
|
66
|
+
- Sverdet bak døren ( 1956 )
|
|
67
|
+
- Livets minutter ( 1965 )
|
|
68
|
+
|
|
69
|
+
Awards .
|
|
70
|
+
- Gyldendals Endowment ( 1935 )
|
|
71
|
+
- Dobloug Prize ( 1951 )
|
|
72
|
+
- Mads Wiel Nygaards legat ( 1961 )
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
async def main():
|
|
77
|
+
await cognee.prune.prune_data()
|
|
78
|
+
await cognee.prune.prune_system(metadata=True)
|
|
79
|
+
|
|
80
|
+
await cognee.add([biography_1, biography_2])
|
|
81
|
+
|
|
82
|
+
await cognee.cognify(temporal_cognify=True)
|
|
83
|
+
|
|
84
|
+
graph_engine = await get_graph_engine()
|
|
85
|
+
graph = await graph_engine.get_graph_data()
|
|
86
|
+
|
|
87
|
+
type_counts = Counter(node_data[1].get("type", {}) for node_data in graph[0])
|
|
88
|
+
|
|
89
|
+
edge_type_counts = Counter(edge_type[2] for edge_type in graph[1])
|
|
90
|
+
|
|
91
|
+
# Graph structure test
|
|
92
|
+
assert type_counts.get("TextDocument", 0) == 2, (
|
|
93
|
+
f"Expected exactly one TextDocument, but found {type_counts.get('TextDocument', 0)}"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
assert type_counts.get("DocumentChunk", 0) == 2, (
|
|
97
|
+
f"Expected exactly one DocumentChunk, but found {type_counts.get('DocumentChunk', 0)}"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
assert type_counts.get("Entity", 0) >= 20, (
|
|
101
|
+
f"Expected multiple entities (assert is set to 20), but found {type_counts.get('Entity', 0)}"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
assert type_counts.get("EntityType", 0) >= 2, (
|
|
105
|
+
f"Expected multiple entity types, but found {type_counts.get('EntityType', 0)}"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
assert type_counts.get("Event", 0) >= 20, (
|
|
109
|
+
f"Expected multiple events (assert is set to 20), but found {type_counts.get('Event', 0)}"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
assert type_counts.get("Timestamp", 0) >= 20, (
|
|
113
|
+
f"Expected multiple timestamps (assert is set to 20), but found {type_counts.get('Timestamp', 0)}"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
assert type_counts.get("Interval", 0) >= 2, (
|
|
117
|
+
f"Expected multiple intervals, but found {type_counts.get('Interval', 0)}"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
assert edge_type_counts.get("contains", 0) >= 20, (
|
|
121
|
+
f"Expected multiple 'contains' edge, but found {edge_type_counts.get('contains', 0)}"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
assert edge_type_counts.get("is_a", 0) >= 20, (
|
|
125
|
+
f"Expected multiple 'is_a' edge, but found {edge_type_counts.get('is_a', 0)}"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
assert edge_type_counts.get("during", 0) == type_counts.get("Interval", 0), (
|
|
129
|
+
"Expected the same amount of during and interval objects in the graph"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
assert edge_type_counts.get("during", 0) == type_counts.get("Interval", 0), (
|
|
133
|
+
"Expected the same amount of during and interval objects in the graph"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
assert edge_type_counts.get("time_from", 0) == type_counts.get("Interval", 0), (
|
|
137
|
+
"Expected the same amount of time_from and interval objects in the graph"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
assert edge_type_counts.get("time_to", 0) == type_counts.get("Interval", 0), (
|
|
141
|
+
"Expected the same amount of time_to and interval objects in the graph"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
retriever = TemporalRetriever()
|
|
145
|
+
|
|
146
|
+
result_before = await retriever.extract_time_from_query("What happened before 1890?")
|
|
147
|
+
|
|
148
|
+
assert result_before[0] is None
|
|
149
|
+
|
|
150
|
+
result_after = await retriever.extract_time_from_query("What happened after 1891?")
|
|
151
|
+
|
|
152
|
+
assert result_after[1] is None
|
|
153
|
+
|
|
154
|
+
result_between = await retriever.extract_time_from_query("What happened between 1890 and 1900?")
|
|
155
|
+
|
|
156
|
+
assert result_between[1]
|
|
157
|
+
assert result_between[0]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
if __name__ == "__main__":
|
|
161
|
+
logger = setup_logging(log_level=INFO)
|
|
162
|
+
loop = asyncio.new_event_loop()
|
|
163
|
+
asyncio.set_event_loop(loop)
|
|
164
|
+
try:
|
|
165
|
+
loop.run_until_complete(main())
|
|
166
|
+
finally:
|
|
167
|
+
loop.run_until_complete(loop.shutdown_asyncgens())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Test package for API tests
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import patch, AsyncMock, MagicMock
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
from fastapi.testclient import TestClient
|
|
5
|
+
from types import SimpleNamespace
|
|
6
|
+
import importlib
|
|
7
|
+
|
|
8
|
+
from cognee.api.client import app
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Fixtures for reuse across test classes
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def mock_default_user():
|
|
14
|
+
"""Mock default user for testing."""
|
|
15
|
+
return SimpleNamespace(
|
|
16
|
+
id=uuid4(), email="default@example.com", is_active=True, tenant_id=uuid4()
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture
|
|
21
|
+
def mock_authenticated_user():
|
|
22
|
+
"""Mock authenticated user for testing."""
|
|
23
|
+
from cognee.modules.users.models import User
|
|
24
|
+
|
|
25
|
+
return User(
|
|
26
|
+
id=uuid4(),
|
|
27
|
+
email="auth@example.com",
|
|
28
|
+
hashed_password="hashed",
|
|
29
|
+
is_active=True,
|
|
30
|
+
is_verified=True,
|
|
31
|
+
tenant_id=uuid4(),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
gau_mod = importlib.import_module("cognee.modules.users.methods.get_authenticated_user")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TestConditionalAuthenticationEndpoints:
|
|
39
|
+
"""Test that API endpoints work correctly with conditional authentication."""
|
|
40
|
+
|
|
41
|
+
@pytest.fixture
|
|
42
|
+
def client(self):
|
|
43
|
+
"""Create a test client."""
|
|
44
|
+
return TestClient(app)
|
|
45
|
+
|
|
46
|
+
def test_health_endpoint_no_auth_required(self, client):
|
|
47
|
+
"""Test that health endpoint works without authentication."""
|
|
48
|
+
response = client.get("/health")
|
|
49
|
+
assert response.status_code in [200, 503] # 503 is also acceptable for health checks
|
|
50
|
+
|
|
51
|
+
def test_root_endpoint_no_auth_required(self, client):
|
|
52
|
+
"""Test that root endpoint works without authentication."""
|
|
53
|
+
response = client.get("/")
|
|
54
|
+
assert response.status_code == 200
|
|
55
|
+
assert response.json() == {"message": "Hello, World, I am alive!"}
|
|
56
|
+
|
|
57
|
+
@patch(
|
|
58
|
+
"cognee.api.client.REQUIRE_AUTHENTICATION",
|
|
59
|
+
False,
|
|
60
|
+
)
|
|
61
|
+
def test_openapi_schema_no_global_security(self, client):
|
|
62
|
+
"""Test that OpenAPI schema doesn't require global authentication."""
|
|
63
|
+
response = client.get("/openapi.json")
|
|
64
|
+
assert response.status_code == 200
|
|
65
|
+
|
|
66
|
+
schema = response.json()
|
|
67
|
+
|
|
68
|
+
# Should not have global security requirement
|
|
69
|
+
global_security = schema.get("security", [])
|
|
70
|
+
assert global_security == []
|
|
71
|
+
|
|
72
|
+
# But should still have security schemes defined
|
|
73
|
+
security_schemes = schema.get("components", {}).get("securitySchemes", {})
|
|
74
|
+
assert "BearerAuth" in security_schemes
|
|
75
|
+
assert "CookieAuth" in security_schemes
|
|
76
|
+
|
|
77
|
+
@patch("cognee.api.v1.add.add")
|
|
78
|
+
@patch.object(gau_mod, "get_default_user", new_callable=AsyncMock)
|
|
79
|
+
@patch(
|
|
80
|
+
"cognee.api.client.REQUIRE_AUTHENTICATION",
|
|
81
|
+
False,
|
|
82
|
+
)
|
|
83
|
+
def test_add_endpoint_with_conditional_auth(
|
|
84
|
+
self, mock_get_default_user, mock_add, client, mock_default_user
|
|
85
|
+
):
|
|
86
|
+
"""Test add endpoint works with conditional authentication."""
|
|
87
|
+
mock_get_default_user.return_value = mock_default_user
|
|
88
|
+
mock_add.return_value = MagicMock(
|
|
89
|
+
model_dump=lambda: {"status": "success", "pipeline_run_id": str(uuid4())}
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Test file upload without authentication
|
|
93
|
+
files = {"data": ("test.txt", b"test content", "text/plain")}
|
|
94
|
+
form_data = {"datasetName": "test_dataset"}
|
|
95
|
+
|
|
96
|
+
response = client.post("/api/v1/add", files=files, data=form_data)
|
|
97
|
+
|
|
98
|
+
assert mock_get_default_user.call_count == 1
|
|
99
|
+
|
|
100
|
+
# Core test: authentication is not required (should not get 401)
|
|
101
|
+
assert response.status_code != 401
|
|
102
|
+
|
|
103
|
+
@patch.object(gau_mod, "get_default_user", new_callable=AsyncMock)
|
|
104
|
+
@patch(
|
|
105
|
+
"cognee.api.client.REQUIRE_AUTHENTICATION",
|
|
106
|
+
False,
|
|
107
|
+
)
|
|
108
|
+
def test_conditional_authentication_works_with_current_environment(
|
|
109
|
+
self, mock_get_default_user, client
|
|
110
|
+
):
|
|
111
|
+
"""Test that conditional authentication works with the current environment setup."""
|
|
112
|
+
# Since REQUIRE_AUTHENTICATION defaults to "false", we expect endpoints to work without auth
|
|
113
|
+
# This tests the actual integration behavior
|
|
114
|
+
|
|
115
|
+
mock_get_default_user.return_value = SimpleNamespace(
|
|
116
|
+
id=uuid4(), email="default@example.com", is_active=True, tenant_id=uuid4()
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
files = {"data": ("test.txt", b"test content", "text/plain")}
|
|
120
|
+
form_data = {"datasetName": "test_dataset"}
|
|
121
|
+
|
|
122
|
+
response = client.post("/api/v1/add", files=files, data=form_data)
|
|
123
|
+
|
|
124
|
+
assert mock_get_default_user.call_count == 1
|
|
125
|
+
|
|
126
|
+
# Core test: authentication is not required (should not get 401)
|
|
127
|
+
assert response.status_code != 401
|
|
128
|
+
# Note: This test verifies conditional authentication works in the current environment
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class TestConditionalAuthenticationBehavior:
|
|
132
|
+
"""Test the behavior of conditional authentication across different endpoints."""
|
|
133
|
+
|
|
134
|
+
@pytest.fixture
|
|
135
|
+
def client(self):
|
|
136
|
+
return TestClient(app)
|
|
137
|
+
|
|
138
|
+
@pytest.mark.parametrize(
|
|
139
|
+
"endpoint,method",
|
|
140
|
+
[
|
|
141
|
+
("/api/v1/search", "GET"),
|
|
142
|
+
("/api/v1/datasets", "GET"),
|
|
143
|
+
],
|
|
144
|
+
)
|
|
145
|
+
@patch.object(gau_mod, "get_default_user", new_callable=AsyncMock)
|
|
146
|
+
def test_get_endpoints_work_without_auth(
|
|
147
|
+
self, mock_get_default, client, endpoint, method, mock_default_user
|
|
148
|
+
):
|
|
149
|
+
"""Test that GET endpoints work without authentication (with current environment)."""
|
|
150
|
+
mock_get_default.return_value = mock_default_user
|
|
151
|
+
|
|
152
|
+
if method == "GET":
|
|
153
|
+
response = client.get(endpoint)
|
|
154
|
+
elif method == "POST":
|
|
155
|
+
response = client.post(endpoint, json={})
|
|
156
|
+
|
|
157
|
+
assert mock_get_default.call_count == 1
|
|
158
|
+
|
|
159
|
+
# Should not return 401 Unauthorized (authentication is optional by default)
|
|
160
|
+
assert response.status_code != 401
|
|
161
|
+
|
|
162
|
+
# May return other errors due to missing data/config, but not auth errors
|
|
163
|
+
if response.status_code >= 400:
|
|
164
|
+
# Check that it's not an authentication error
|
|
165
|
+
try:
|
|
166
|
+
error_detail = response.json().get("detail", "")
|
|
167
|
+
assert "authenticate" not in error_detail.lower()
|
|
168
|
+
assert "unauthorized" not in error_detail.lower()
|
|
169
|
+
except Exception:
|
|
170
|
+
pass # If response is not JSON, that's fine
|
|
171
|
+
|
|
172
|
+
gsm_mod = importlib.import_module("cognee.modules.settings.get_settings")
|
|
173
|
+
|
|
174
|
+
@patch.object(gsm_mod, "get_vectordb_config")
|
|
175
|
+
@patch.object(gsm_mod, "get_llm_config")
|
|
176
|
+
@patch.object(gau_mod, "get_default_user", new_callable=AsyncMock)
|
|
177
|
+
def test_settings_endpoint_integration(
|
|
178
|
+
self, mock_get_default, mock_llm_config, mock_vector_config, client, mock_default_user
|
|
179
|
+
):
|
|
180
|
+
"""Test that settings endpoint integration works with conditional authentication."""
|
|
181
|
+
mock_get_default.return_value = mock_default_user
|
|
182
|
+
|
|
183
|
+
# Mock configurations to avoid validation errors
|
|
184
|
+
mock_llm_config.return_value = SimpleNamespace(
|
|
185
|
+
llm_provider="openai",
|
|
186
|
+
llm_model="gpt-4o",
|
|
187
|
+
llm_endpoint=None,
|
|
188
|
+
llm_api_version=None,
|
|
189
|
+
llm_api_key="test_key_1234567890",
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
mock_vector_config.return_value = SimpleNamespace(
|
|
193
|
+
vector_db_provider="lancedb",
|
|
194
|
+
vector_db_url="localhost:5432", # Must be string, not None
|
|
195
|
+
vector_db_key="test_vector_key",
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
response = client.get("/api/v1/settings")
|
|
199
|
+
|
|
200
|
+
assert mock_get_default.call_count == 1
|
|
201
|
+
|
|
202
|
+
# Core test: authentication is not required (should not get 401)
|
|
203
|
+
assert response.status_code != 401
|
|
204
|
+
# Note: This test verifies conditional authentication works for settings endpoint
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class TestConditionalAuthenticationErrorHandling:
|
|
208
|
+
"""Test error handling in conditional authentication."""
|
|
209
|
+
|
|
210
|
+
@pytest.fixture
|
|
211
|
+
def client(self):
|
|
212
|
+
return TestClient(app)
|
|
213
|
+
|
|
214
|
+
@patch.object(gau_mod, "get_default_user", new_callable=AsyncMock)
|
|
215
|
+
def test_get_default_user_fails(self, mock_get_default, client):
|
|
216
|
+
"""Test behavior when get_default_user fails (with current environment)."""
|
|
217
|
+
mock_get_default.side_effect = Exception("Database connection failed")
|
|
218
|
+
|
|
219
|
+
# The error should propagate - either as a 500 error or as an exception
|
|
220
|
+
files = {"data": ("test.txt", b"test content", "text/plain")}
|
|
221
|
+
form_data = {"datasetName": "test_dataset"}
|
|
222
|
+
|
|
223
|
+
# Test that the exception is properly converted to HTTP 500
|
|
224
|
+
response = client.post("/api/v1/add", files=files, data=form_data)
|
|
225
|
+
|
|
226
|
+
# Should return HTTP 500 Internal Server Error when get_default_user fails
|
|
227
|
+
assert response.status_code == 500
|
|
228
|
+
|
|
229
|
+
# Check that the error message is informative
|
|
230
|
+
error_detail = response.json().get("detail", "")
|
|
231
|
+
assert "Failed to create default user" in error_detail
|
|
232
|
+
# The exact error message may vary depending on the actual database connection
|
|
233
|
+
# The important thing is that we get a 500 error when user creation fails
|
|
234
|
+
|
|
235
|
+
def test_current_environment_configuration(self):
|
|
236
|
+
"""Test that current environment configuration is working properly."""
|
|
237
|
+
# This tests the actual module state without trying to change it
|
|
238
|
+
from cognee.modules.users.methods.get_authenticated_user import (
|
|
239
|
+
REQUIRE_AUTHENTICATION,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Should be a boolean value (the parsing logic works)
|
|
243
|
+
assert isinstance(REQUIRE_AUTHENTICATION, bool)
|
|
244
|
+
|
|
245
|
+
# In default environment, should be False
|
|
246
|
+
assert not REQUIRE_AUTHENTICATION
|