#![allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "test code — panics are acceptable failures"
)]
use std::sync::Arc;
use cognee_database::{self as database, DatabaseConnection, DeleteDb};
use cognee_delete::{DeleteError, DeleteMode, DeleteRequest, DeleteScope, DeleteService};
use cognee_models::{Data, Dataset};
use cognee_storage::{MockStorage, StorageTrait};
use uuid::Uuid;
async fn setup() -> (Arc<DatabaseConnection>, Arc<MockStorage>) {
let db = database::connect("sqlite::memory:").await.unwrap();
database::initialize(&db).await.unwrap();
let storage = Arc::new(MockStorage::new());
(Arc::new(db), storage)
}
#[tokio::test]
async fn delete_nonexistent_data_returns_error() {
let (db, storage) = setup().await;
let svc = DeleteService::new(
storage.clone() as Arc<dyn StorageTrait>,
db.clone() as Arc<dyn DeleteDb>,
);
let owner_id = Uuid::new_v4();
let data_id = Uuid::new_v4();
let result = svc
.execute(&DeleteRequest {
scope: DeleteScope::Data {
owner_id,
data_id,
dataset_name: None,
delete_dataset_if_empty: false,
},
mode: DeleteMode::Soft,
memory_only: false,
})
.await;
let delete_result = result.expect("missing data row should succeed with best-effort cleanup");
assert!(
delete_result.deleted_data <= 1,
"unexpected large deletion count: {}",
delete_result.deleted_data
);
assert_eq!(
delete_result.deleted_datasets, 0,
"no dataset rows should be deleted for a ghost data_id"
);
}
#[tokio::test]
async fn delete_nonexistent_dataset_returns_error() {
let (db, storage) = setup().await;
let svc = DeleteService::new(
storage.clone() as Arc<dyn StorageTrait>,
db.clone() as Arc<dyn DeleteDb>,
);
let owner_id = Uuid::new_v4();
let result = svc
.execute(&DeleteRequest {
scope: DeleteScope::Dataset {
owner_id,
dataset_name: "no_such_ds".to_string(),
},
mode: DeleteMode::Soft,
memory_only: false,
})
.await;
let err = result.expect_err("should fail for nonexistent dataset");
match &err {
DeleteError::Validation(msg) => {
assert!(
msg.to_lowercase().contains("not found"),
"expected 'not found' in message, got: {msg}"
);
}
other => panic!("expected DeleteError::Validation, got: {other:?}"),
}
}
#[tokio::test]
async fn delete_data_not_in_specified_dataset_returns_error() {
let (db, storage) = setup().await;
let owner_id = Uuid::new_v4();
let dataset = Dataset::new("real_ds".to_string(), owner_id, None, Uuid::new_v4());
database::ops::datasets::create_dataset(&db, dataset)
.await
.unwrap();
let location = storage
.store(b"orphan content", "orphan.txt")
.await
.unwrap();
let data_id = Uuid::new_v4();
let data = Data::builder(
data_id,
"orphan.txt",
&location,
"file://orphan.txt",
"txt",
"text/plain",
"orphan_hash",
owner_id,
)
.build();
database::ops::data::create_data(&db, data).await.unwrap();
let svc = DeleteService::new(
storage.clone() as Arc<dyn StorageTrait>,
db.clone() as Arc<dyn DeleteDb>,
);
let result = svc
.execute(&DeleteRequest {
scope: DeleteScope::Data {
owner_id,
data_id,
dataset_name: Some("real_ds".to_string()),
delete_dataset_if_empty: false,
},
mode: DeleteMode::Soft,
memory_only: false,
})
.await;
let err = result.expect_err("should fail when data is not attached to the dataset");
match &err {
DeleteError::Validation(msg) => {
assert!(
msg.to_lowercase().contains("not attached"),
"expected 'not attached' in message, got: {msg}"
);
}
other => panic!("expected DeleteError::Validation, got: {other:?}"),
}
}
#[tokio::test]
async fn delete_user_with_no_datasets_succeeds_with_zero_deletions() {
let (db, storage) = setup().await;
let svc = DeleteService::new(
storage.clone() as Arc<dyn StorageTrait>,
db.clone() as Arc<dyn DeleteDb>,
);
let owner_id = Uuid::new_v4();
let result = svc
.execute(&DeleteRequest {
scope: DeleteScope::User { owner_id },
mode: DeleteMode::Soft,
memory_only: false,
})
.await
.expect("deleting a user with no datasets should succeed");
assert_eq!(
result.deleted_datasets, 0,
"no datasets should be deleted for a new owner"
);
assert_eq!(
result.deleted_data, 0,
"no data should be deleted for a new owner"
);
}