use datafold::fold_db_core::FoldDB;
use serde_json::json;
use std::thread;
use std::time::Duration;
use tempfile::TempDir;
#[tokio::test]
async fn test_transform_registration_persistence_across_restart() {
let temp_dir_1 = TempDir::new().expect("Failed to create temp directory 1");
let temp_dir_2 = TempDir::new().expect("Failed to create temp directory 2");
let temp_dir_3 = TempDir::new().expect("Failed to create temp directory 3");
let test_db_path_1 = temp_dir_1
.path()
.to_str()
.expect("Failed to convert path to string");
let _test_db_path_2 = temp_dir_2
.path()
.to_str()
.expect("Failed to convert path to string");
let _test_db_path_3 = temp_dir_3
.path()
.to_str()
.expect("Failed to convert path to string");
let fold_db_1 = FoldDB::new(test_db_path_1)
.await
.expect("Failed to create first FoldDB instance");
let blogpost_schema_json = json!({
"name": "BlogPost",
"key": {
"range_field": "publish_date"
},
"fields": {
"title": {},
"content": {},
"author": {},
"publish_date": {},
"tags": {}
}
});
let blogpost_schema_str =
serde_json::to_string(&blogpost_schema_json).expect("Failed to serialize BlogPost schema");
fold_db_1
.schema_manager()
.load_schema_from_json(&blogpost_schema_str)
.await
.expect("Failed to load BlogPost schema");
let wordindex_schema_json = json!({
"name": "BlogPostWordIndex",
"key": {
"hash_field": "word",
"range_field": "publish_date"
},
"fields": {
"word": {},
"publish_date": {},
"content": {},
"author": {},
"title": {},
"tags": {}
},
"transform_fields": {
"word": "BlogPost.content.split(' ').map(w => w.toLowerCase().replace(/[^a-zA-Z0-9]/g, '')).filter(w => w.length > 0)",
"publish_date": "BlogPost.publish_date",
"content": "BlogPost.content",
"author": "BlogPost.author",
"title": "BlogPost.title",
"tags": "BlogPost.tags"
}
});
let wordindex_schema_str = serde_json::to_string(&wordindex_schema_json)
.expect("Failed to serialize BlogPostWordIndex schema");
fold_db_1
.schema_manager()
.load_schema_from_json(&wordindex_schema_str)
.await
.expect("Failed to load BlogPostWordIndex schema");
thread::sleep(Duration::from_millis(50));
let transform_manager_1 = fold_db_1.transform_manager();
let registered_transforms_1 = transform_manager_1
.list_transforms()
.expect("Failed to list transforms from first instance");
let expected_transform_id = "BlogPostWordIndex";
assert!(
registered_transforms_1.contains_key(expected_transform_id),
"Transform '{}' should be registered in first instance",
expected_transform_id
);
assert_eq!(
registered_transforms_1.len(),
1,
"Should have exactly 1 registered transform in first instance"
);
let expected_trigger_fields = vec![
"BlogPost.content",
"BlogPost.publish_date",
"BlogPost.author",
"BlogPost.title",
"BlogPost.tags",
];
for field in &expected_trigger_fields {
let transforms_for_field = transform_manager_1
.get_transforms_for_field("BlogPost", field.strip_prefix("BlogPost.").unwrap())
.expect("Failed to get transforms for field");
assert!(
transforms_for_field.contains(expected_transform_id),
"Field '{}' should trigger transform '{}'",
field,
expected_transform_id
);
}
let db_ops = fold_db_1.get_db_ops();
let (loaded_transforms, loaded_mappings) = db_ops
.load_transform_state()
.await
.expect("Failed to load transform state");
assert!(
!loaded_transforms.is_empty(),
"Transforms should be loaded from storage"
);
assert!(
!loaded_mappings.is_empty(),
"Field mappings should be loaded from storage"
);
assert!(
loaded_transforms.contains_key("BlogPostWordIndex"),
"BlogPostWordIndex should be loaded from storage"
);
let content_mappings = loaded_mappings.get("BlogPost.content");
assert!(
content_mappings.is_some(),
"BlogPost.content should have field mappings"
);
assert!(
content_mappings.unwrap().contains("BlogPostWordIndex"),
"BlogPost.content should map to BlogPostWordIndex"
);
let authorindex_schema_json = json!({
"name": "BlogPostAuthorIndex",
"key": {
"hash_field": "author",
"range_field": "publish_date"
},
"fields": {
"author": {},
"publish_date": {},
"title": {},
"content": {}
},
"transform_fields": {
"author": "BlogPost.author",
"publish_date": "BlogPost.publish_date",
"title": "BlogPost.title",
"content": "BlogPost.content"
}
});
let authorindex_schema_str = serde_json::to_string(&authorindex_schema_json)
.expect("Failed to serialize BlogPostAuthorIndex schema");
fold_db_1
.schema_manager()
.load_schema_from_json(&authorindex_schema_str)
.await
.expect("Failed to load BlogPostAuthorIndex schema");
thread::sleep(Duration::from_millis(50));
let final_transforms = transform_manager_1
.list_transforms()
.expect("Failed to list final transforms");
assert_eq!(
final_transforms.len(),
2,
"Should have exactly 2 registered transforms after adding second schema"
);
assert!(
final_transforms.contains_key("BlogPostWordIndex"),
"BlogPostWordIndex transform should still be present"
);
assert!(
final_transforms.contains_key("BlogPostAuthorIndex"),
"BlogPostAuthorIndex transform should be registered"
);
let (final_loaded_transforms, final_loaded_mappings) = db_ops
.load_transform_state()
.await
.expect("Failed to load final transform state");
assert_eq!(
final_loaded_transforms.len(),
2,
"Should have exactly 2 transforms in database"
);
assert!(
final_loaded_transforms.contains_key("BlogPostWordIndex"),
"BlogPostWordIndex should be in database"
);
assert!(
final_loaded_transforms.contains_key("BlogPostAuthorIndex"),
"BlogPostAuthorIndex should be in database"
);
let content_mappings = final_loaded_mappings.get("BlogPost.content");
assert!(
content_mappings.is_some(),
"BlogPost.content should have field mappings"
);
assert!(
content_mappings.unwrap().contains("BlogPostWordIndex"),
"BlogPost.content should map to BlogPostWordIndex"
);
let author_mappings = final_loaded_mappings.get("BlogPost.author");
assert!(
author_mappings.is_some(),
"BlogPost.author should have field mappings"
);
assert!(
author_mappings.unwrap().contains("BlogPostAuthorIndex"),
"BlogPost.author should map to BlogPostAuthorIndex"
);
fold_db_1
.close()
.expect("Failed to close first FoldDB instance");
}
#[tokio::test]
async fn test_transform_persistence_with_direct_db_verification() {
let temp_dir_1 = TempDir::new().expect("Failed to create temp directory 1");
let temp_dir_2 = TempDir::new().expect("Failed to create temp directory 2");
let test_db_path_1 = temp_dir_1
.path()
.to_str()
.expect("Failed to convert path to string");
let _test_db_path_2 = temp_dir_2
.path()
.to_str()
.expect("Failed to convert path to string");
let fold_db = FoldDB::new(test_db_path_1)
.await
.expect("Failed to create FoldDB instance");
let blogpost_schema_json = json!({
"name": "BlogPost",
"key": {
"range_field": "publish_date"
},
"fields": {
"title": {},
"content": {},
"author": {},
"publish_date": {},
"tags": {}
}
});
let wordindex_schema_json = json!({
"name": "BlogPostWordIndex",
"key": {
"hash_field": "word",
"range_field": "publish_date"
},
"fields": {
"word": {},
"publish_date": {},
"content": {},
"author": {},
"title": {},
"tags": {}
},
"transform_fields": {
"word": "BlogPost.content.split(' ').map(w => w.toLowerCase().replace(/[^a-zA-Z0-9]/g, '')).filter(w => w.length > 0)",
"publish_date": "BlogPost.publish_date",
"content": "BlogPost.content",
"author": "BlogPost.author",
"title": "BlogPost.title",
"tags": "BlogPost.tags"
}
});
fold_db
.schema_manager()
.load_schema_from_json(&serde_json::to_string(&blogpost_schema_json).unwrap())
.await
.expect("Failed to load BlogPost schema");
fold_db
.schema_manager()
.load_schema_from_json(&serde_json::to_string(&wordindex_schema_json).unwrap())
.await
.expect("Failed to load BlogPostWordIndex schema");
thread::sleep(Duration::from_millis(200));
let transform_manager = fold_db.transform_manager();
let registered_transforms = transform_manager
.list_transforms()
.expect("Failed to list transforms");
assert!(
registered_transforms.contains_key("BlogPostWordIndex"),
"Transform should be registered"
);
let db_ops = fold_db.get_db_ops();
let (loaded_transforms, loaded_mappings) = db_ops
.load_transform_state()
.await
.expect("Failed to load transform state");
assert!(
!loaded_transforms.is_empty(),
"Transforms should be loaded from storage"
);
assert!(
!loaded_mappings.is_empty(),
"Field mappings should be loaded from storage"
);
assert!(
loaded_transforms.contains_key("BlogPostWordIndex"),
"BlogPostWordIndex should be loaded from storage"
);
let content_mappings = loaded_mappings.get("BlogPost.content");
assert!(
content_mappings.is_some(),
"BlogPost.content should have field mappings"
);
assert!(
content_mappings.unwrap().contains("BlogPostWordIndex"),
"BlogPost.content should map to BlogPostWordIndex"
);
let (final_loaded_transforms, final_loaded_mappings) = db_ops
.load_transform_state()
.await
.expect("Failed to load transform state again");
assert!(
!final_loaded_transforms.is_empty(),
"Transforms should persist after sync"
);
assert!(
!final_loaded_mappings.is_empty(),
"Field mappings should persist after sync"
);
assert!(
final_loaded_transforms.contains_key("BlogPostWordIndex"),
"BlogPostWordIndex should persist after sync"
);
let content_mappings = final_loaded_mappings.get("BlogPost.content");
assert!(
content_mappings.is_some(),
"BlogPost.content should have persistent field mappings"
);
assert!(
content_mappings.unwrap().contains("BlogPostWordIndex"),
"BlogPost.content should map to BlogPostWordIndex"
);
fold_db.close().expect("Failed to close FoldDB");
}