c889a57b6b
Test Suites / Build CI Environment (push) Has been cancelled
Test Suites / Basic Tests (push) Has been cancelled
Test Suites / End-to-End Tests (push) Has been cancelled
Test Suites / CLI Tests (push) Has been cancelled
Test Suites / Slow End-to-End Tests (push) Has been cancelled
Test Suites / Graph Database Tests (push) Has been cancelled
Test Suites / Vector DB Tests (push) Has been cancelled
Test Suites / Temporal Graph Test (push) Has been cancelled
Test Suites / Search Test on Different DBs (push) Has been cancelled
Test Suites / Example Tests (push) Has been cancelled
Test Suites / Notebook Tests (push) Has been cancelled
Test Suites / OS and Python Tests Ubuntu (push) Has been cancelled
Test Suites / OS and Python Tests Extended (push) Has been cancelled
Test Suites / LLM Test Suite (push) Has been cancelled
Test Suites / S3 File Storage Test (push) Has been cancelled
Test Suites / Run Integration Tests (push) Has been cancelled
Test Suites / MCP Tests (push) Has been cancelled
Test Suites / Docker Compose Test (push) Has been cancelled
Test Suites / Docker CI test (push) Has been cancelled
Test Suites / Relational DB Migration Tests (push) Has been cancelled
Test Suites / Distributed Cognee Test (push) Has been cancelled
Test Suites / DB Examples Tests (push) Has been cancelled
Test Suites / Test Completion Status (push) Has been cancelled
Test Suites / Claude Code Review (push) Has been cancelled
Test Suites / basic checks (push) Has been cancelled
build | Build and Push Cognee MCP Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
build | Build and Push Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.11) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.12) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (kuzu, kuzu) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (neo4j, neo4j) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Examples (push) Has been cancelled
Weighted Edges Tests / Code Quality for Weighted Edges (push) Has been cancelled
364 lines
12 KiB
Python
364 lines
12 KiB
Python
"""
|
|
Integration Tests: Delete Edge Cases
|
|
|
|
Tests for edge cases in the delete feature:
|
|
1. last_accessed field updates when retriever accesses data
|
|
2. cleanup_unused_data dry_run behavior
|
|
3. cleanup_unused_data actual deletion behavior
|
|
|
|
Test Coverage:
|
|
- test_last_accessed_updates_on_search: Verify last_accessed is updated during retrieval
|
|
- test_cleanup_unused_data_dry_run: Verify dry_run doesn't delete but reports correctly
|
|
- test_cleanup_actual_deletion: Verify actual cleanup deletes old documents
|
|
"""
|
|
|
|
import os
|
|
import pathlib
|
|
import pytest
|
|
from datetime import datetime, timezone, timedelta
|
|
from sqlalchemy import select, update
|
|
|
|
import cognee
|
|
from cognee.infrastructure.databases.relational import get_relational_engine
|
|
from cognee.modules.data.models import Data, DatasetData
|
|
from cognee.modules.engine.operations.setup import setup
|
|
from cognee.modules.search.types import SearchType
|
|
from cognee.modules.users.methods import get_default_user
|
|
from cognee.shared.logging_utils import get_logger
|
|
from cognee.tasks.cleanup.cleanup_unused_data import cleanup_unused_data
|
|
|
|
logger = get_logger()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_last_accessed_updates_on_search():
|
|
"""
|
|
Test that last_accessed field is updated when retriever accesses data.
|
|
|
|
Setup:
|
|
- Enable ENABLE_LAST_ACCESSED
|
|
- Add and cognify a document
|
|
- Verify last_accessed is initially None or recent
|
|
|
|
Operation:
|
|
- Perform search query
|
|
|
|
Expected:
|
|
- last_accessed is updated to recent timestamp
|
|
"""
|
|
# Enable last accessed tracking
|
|
os.environ["ENABLE_LAST_ACCESSED"] = "true"
|
|
|
|
data_directory_path = os.path.join(
|
|
pathlib.Path(__file__).parent.parent, ".data_storage/test_last_accessed_updates"
|
|
)
|
|
cognee.config.data_root_directory(data_directory_path)
|
|
|
|
cognee_directory_path = os.path.join(
|
|
pathlib.Path(__file__).parent.parent, ".cognee_system/test_last_accessed_updates"
|
|
)
|
|
cognee.config.system_root_directory(cognee_directory_path)
|
|
|
|
await cognee.prune.prune_data()
|
|
await cognee.prune.prune_system(metadata=True)
|
|
await setup()
|
|
|
|
user = await get_default_user()
|
|
|
|
# Add and cognify a document
|
|
dataset_name = "test_last_accessed"
|
|
doc_text = "Apple is a technology company that makes smartphones and computers."
|
|
|
|
await cognee.add([doc_text], dataset_name=dataset_name, user=user)
|
|
cognify_result = await cognee.cognify([dataset_name], user=user)
|
|
|
|
dataset_id = list(cognify_result.keys())[0]
|
|
|
|
# Get the data_id
|
|
db_engine = get_relational_engine()
|
|
async with db_engine.get_async_session() as session:
|
|
result = await session.execute(
|
|
select(Data, DatasetData)
|
|
.join(DatasetData, Data.id == DatasetData.data_id)
|
|
.where(DatasetData.dataset_id == dataset_id)
|
|
)
|
|
data_records = result.all()
|
|
assert len(data_records) > 0, "Should have at least one data record"
|
|
|
|
data_before = data_records[0][0]
|
|
data_id = data_before.id
|
|
|
|
# Record timestamp before search
|
|
last_accessed_before = data_before.last_accessed
|
|
logger.info(f"last_accessed before search: {last_accessed_before}")
|
|
|
|
# Wait a moment to ensure timestamp difference
|
|
import asyncio
|
|
|
|
await asyncio.sleep(0.1)
|
|
|
|
# Perform search to trigger last_accessed update
|
|
logger.info("Performing search to trigger last_accessed update...")
|
|
search_results = await cognee.search(
|
|
query_type=SearchType.GRAPH_COMPLETION,
|
|
query_text="Apple technology",
|
|
datasets=[dataset_name],
|
|
user=user,
|
|
)
|
|
|
|
logger.info(f"Search returned {len(search_results)} results")
|
|
|
|
# Check last_accessed after search
|
|
async with db_engine.get_async_session() as session:
|
|
result = await session.execute(select(Data).where(Data.id == data_id))
|
|
data_after = result.scalar_one_or_none()
|
|
|
|
assert data_after is not None, "Data should still exist"
|
|
|
|
last_accessed_after = data_after.last_accessed
|
|
logger.info(f"last_accessed after search: {last_accessed_after}")
|
|
|
|
# Verify last_accessed was updated
|
|
assert last_accessed_after is not None, "last_accessed should be set after search operation"
|
|
|
|
# Verify timestamp is recent (within last 30 seconds)
|
|
if last_accessed_after.tzinfo is None:
|
|
last_accessed_after = last_accessed_after.replace(tzinfo=timezone.utc)
|
|
|
|
time_diff = (datetime.now(timezone.utc) - last_accessed_after).total_seconds()
|
|
assert time_diff < 30, (
|
|
f"last_accessed should be recent (within 30 seconds), but was {time_diff}s ago"
|
|
)
|
|
|
|
logger.info(f"✅ last_accessed updated successfully (time_diff={time_diff:.2f}s)")
|
|
|
|
logger.info("✅ test_last_accessed_updates_on_search PASSED")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cleanup_unused_data_dry_run():
|
|
"""
|
|
Test that cleanup_unused_data dry_run doesn't delete but reports correctly.
|
|
|
|
Setup:
|
|
- Enable ENABLE_LAST_ACCESSED
|
|
- Create 5 documents
|
|
- Age 3 documents to be "old" (last_accessed > threshold)
|
|
- Keep 2 documents "new" (last_accessed < threshold)
|
|
|
|
Operation:
|
|
- Run cleanup_unused_data with dry_run=True
|
|
|
|
Expected:
|
|
- Status: "dry_run"
|
|
- unused_count: 3
|
|
- deleted_count: 0
|
|
- All 5 documents still exist
|
|
"""
|
|
# Enable last accessed tracking
|
|
os.environ["ENABLE_LAST_ACCESSED"] = "true"
|
|
|
|
data_directory_path = os.path.join(
|
|
pathlib.Path(__file__).parent.parent, ".data_storage/test_cleanup_dry_run"
|
|
)
|
|
cognee.config.data_root_directory(data_directory_path)
|
|
|
|
cognee_directory_path = os.path.join(
|
|
pathlib.Path(__file__).parent.parent, ".cognee_system/test_cleanup_dry_run"
|
|
)
|
|
cognee.config.system_root_directory(cognee_directory_path)
|
|
|
|
await cognee.prune.prune_data()
|
|
await cognee.prune.prune_system(metadata=True)
|
|
await setup()
|
|
|
|
user = await get_default_user()
|
|
|
|
# Create dataset with multiple documents
|
|
dataset_name = "test_cleanup_dry_run"
|
|
|
|
docs = [
|
|
"Document 1: Old document about cats",
|
|
"Document 2: Old document about dogs",
|
|
"Document 3: Old document about birds",
|
|
"Document 4: New document about fish",
|
|
"Document 5: New document about rabbits",
|
|
]
|
|
|
|
data_ids = []
|
|
for doc in docs:
|
|
add_result = await cognee.add([doc], dataset_name=dataset_name, user=user)
|
|
data_ids.append(add_result.data_ingestion_info[0]["data_id"])
|
|
|
|
cognify_result = await cognee.cognify([dataset_name], user=user)
|
|
list(cognify_result.keys())[0]
|
|
|
|
# Age the first 3 documents to be "old"
|
|
db_engine = get_relational_engine()
|
|
threshold_minutes = 30
|
|
aged_timestamp = datetime.now(timezone.utc) - timedelta(minutes=threshold_minutes + 10)
|
|
recent_timestamp = datetime.now(timezone.utc) - timedelta(minutes=10)
|
|
|
|
async with db_engine.get_async_session() as session:
|
|
# Age first 3 documents
|
|
for i in range(3):
|
|
stmt = update(Data).where(Data.id == data_ids[i]).values(last_accessed=aged_timestamp)
|
|
await session.execute(stmt)
|
|
|
|
# Set last 2 documents as recently accessed
|
|
for i in range(3, 5):
|
|
stmt = update(Data).where(Data.id == data_ids[i]).values(last_accessed=recent_timestamp)
|
|
await session.execute(stmt)
|
|
|
|
await session.commit()
|
|
|
|
logger.info("Aged 3 documents to be old, kept 2 as recent")
|
|
|
|
# Run dry run cleanup
|
|
logger.info("Running cleanup with dry_run=True...")
|
|
result = await cleanup_unused_data(
|
|
minutes_threshold=threshold_minutes, dry_run=True, user_id=user.id
|
|
)
|
|
|
|
logger.info(f"Cleanup result: {result}")
|
|
|
|
# Assertions for dry run
|
|
assert result["status"] == "dry_run", f"Status should be 'dry_run', got {result['status']}"
|
|
assert result["unused_count"] == 3, (
|
|
f"Should find 3 unused documents, found {result['unused_count']}"
|
|
)
|
|
assert result["deleted_count"]["documents"] == 0, (
|
|
f"Dry run should not delete anything, deleted {result['deleted_count']['documents']}"
|
|
)
|
|
|
|
# Verify all documents still exist
|
|
async with db_engine.get_async_session() as session:
|
|
result = await session.execute(select(Data).where(Data.id.in_(data_ids)))
|
|
remaining_data = result.all()
|
|
assert len(remaining_data) == 5, (
|
|
f"All 5 documents should still exist, found {len(remaining_data)}"
|
|
)
|
|
|
|
logger.info("✅ test_cleanup_unused_data_dry_run PASSED")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cleanup_actual_deletion():
|
|
"""
|
|
Test that cleanup_unused_data actually deletes old documents.
|
|
|
|
Setup:
|
|
- Enable ENABLE_LAST_ACCESSED
|
|
- Create 5 documents
|
|
- Age 3 documents to be "old"
|
|
- Keep 2 documents "new"
|
|
|
|
Operation:
|
|
- Run cleanup_unused_data with dry_run=False
|
|
|
|
Expected:
|
|
- Status: "completed"
|
|
- deleted_count: 3
|
|
- Only 2 new documents remain
|
|
"""
|
|
# Enable last accessed tracking
|
|
os.environ["ENABLE_LAST_ACCESSED"] = "true"
|
|
|
|
data_directory_path = os.path.join(
|
|
pathlib.Path(__file__).parent.parent, ".data_storage/test_cleanup_actual"
|
|
)
|
|
cognee.config.data_root_directory(data_directory_path)
|
|
|
|
cognee_directory_path = os.path.join(
|
|
pathlib.Path(__file__).parent.parent, ".cognee_system/test_cleanup_actual"
|
|
)
|
|
cognee.config.system_root_directory(cognee_directory_path)
|
|
|
|
await cognee.prune.prune_data()
|
|
await cognee.prune.prune_system(metadata=True)
|
|
await setup()
|
|
|
|
user = await get_default_user()
|
|
|
|
# Create dataset with multiple documents
|
|
dataset_name = "test_cleanup_actual"
|
|
|
|
docs = [
|
|
"Document 1: Old document about mathematics",
|
|
"Document 2: Old document about physics",
|
|
"Document 3: Old document about chemistry",
|
|
"Document 4: New document about biology",
|
|
"Document 5: New document about geology",
|
|
]
|
|
|
|
data_ids = []
|
|
for doc in docs:
|
|
add_result = await cognee.add([doc], dataset_name=dataset_name, user=user)
|
|
data_ids.append(add_result.data_ingestion_info[0]["data_id"])
|
|
|
|
cognify_result = await cognee.cognify([dataset_name], user=user)
|
|
list(cognify_result.keys())[0]
|
|
|
|
# Age the first 3 documents to be "old"
|
|
db_engine = get_relational_engine()
|
|
threshold_minutes = 30
|
|
aged_timestamp = datetime.now(timezone.utc) - timedelta(minutes=threshold_minutes + 10)
|
|
recent_timestamp = datetime.now(timezone.utc) - timedelta(minutes=10)
|
|
|
|
async with db_engine.get_async_session() as session:
|
|
# Age first 3 documents
|
|
for i in range(3):
|
|
stmt = update(Data).where(Data.id == data_ids[i]).values(last_accessed=aged_timestamp)
|
|
await session.execute(stmt)
|
|
|
|
# Set last 2 documents as recently accessed
|
|
for i in range(3, 5):
|
|
stmt = update(Data).where(Data.id == data_ids[i]).values(last_accessed=recent_timestamp)
|
|
await session.execute(stmt)
|
|
|
|
await session.commit()
|
|
|
|
logger.info("Aged 3 documents to be old, kept 2 as recent")
|
|
|
|
# Run actual cleanup
|
|
logger.info("Running cleanup with dry_run=False...")
|
|
result = await cleanup_unused_data(
|
|
minutes_threshold=threshold_minutes, dry_run=False, user_id=user.id
|
|
)
|
|
|
|
logger.info(f"Cleanup result: {result}")
|
|
|
|
# Assertions for actual deletion
|
|
assert result["status"] == "completed", f"Status should be 'completed', got {result['status']}"
|
|
assert result["deleted_count"]["documents"] == 3, (
|
|
f"Should delete 3 documents, deleted {result['deleted_count']['documents']}"
|
|
)
|
|
|
|
# Verify only 2 documents remain
|
|
async with db_engine.get_async_session() as session:
|
|
result = await session.execute(select(Data).where(Data.id.in_(data_ids)))
|
|
remaining_data = result.all()
|
|
assert len(remaining_data) == 2, (
|
|
f"Should have 2 remaining documents, found {len(remaining_data)}"
|
|
)
|
|
|
|
# Verify the remaining documents are the recent ones (data_ids[3] and data_ids[4])
|
|
remaining_ids = [data[0].id for data in remaining_data]
|
|
assert data_ids[3] in remaining_ids, "Document 4 should remain"
|
|
assert data_ids[4] in remaining_ids, "Document 5 should remain"
|
|
|
|
# Verify old documents are deleted
|
|
assert data_ids[0] not in remaining_ids, "Document 1 should be deleted"
|
|
assert data_ids[1] not in remaining_ids, "Document 2 should be deleted"
|
|
assert data_ids[2] not in remaining_ids, "Document 3 should be deleted"
|
|
|
|
logger.info("✅ test_cleanup_actual_deletion PASSED")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import asyncio
|
|
|
|
asyncio.run(test_last_accessed_updates_on_search())
|
|
asyncio.run(test_cleanup_unused_data_dry_run())
|
|
asyncio.run(test_cleanup_actual_deletion())
|