test: add integration tests to catalog
This commit is contained in:
@@ -1172,36 +1172,235 @@ mod tests {
|
||||
#[test]
|
||||
fn integration_test_init_insert_persist_load_get() {
|
||||
// Scenario: 'init' -> 'insert' -> 'persist' -> create a new catalog instance -> 'load_by_id' -> 'get' -> verify data integrity.
|
||||
todo!();
|
||||
let db_path = "target/test_dbs/integration_test_init_insert_persist_load_get.db";
|
||||
let path = std::path::Path::new(db_path);
|
||||
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
if path.exists() {
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
// 1. 'init' -> 'insert' -> 'persist'
|
||||
let mut catalog1 = Catalog::new(db_path);
|
||||
catalog1.init().unwrap();
|
||||
let item_to_insert = Item {
|
||||
id: "item-1".to_string(),
|
||||
name: "Integration Test Item".to_string(),
|
||||
price: 999,
|
||||
in_stock: true,
|
||||
};
|
||||
catalog1.upsert(item_to_insert.clone());
|
||||
catalog1.persist().unwrap();
|
||||
// 2. create a new catalog instance -> 'load_by_id' -> 'get'
|
||||
let mut catalog2 = Catalog::new(db_path);
|
||||
catalog2.load_by_id("item-1").unwrap();
|
||||
let loaded_item: Option<Item> = catalog2.get("item-1");
|
||||
// 3. verify data integrity
|
||||
assert_eq!(loaded_item, Some(item_to_insert));
|
||||
// Clean up
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integration_test_init_insert_many_persist_load_list() {
|
||||
// Scenario: 'init' -> 'insert_many' -> 'persist' -> new catalog -> 'load_by_class' -> 'list_by_class' -> verify all items are loaded.
|
||||
todo!();
|
||||
let db_path = "target/test_dbs/integration_test_init_insert_many_persist_load_list.db";
|
||||
let path = std::path::Path::new(db_path);
|
||||
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
if path.exists() {
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
// 1. 'init' -> 'insert_many' -> 'persist'
|
||||
let mut catalog1 = Catalog::new(db_path);
|
||||
catalog1.init().unwrap();
|
||||
let items_to_insert = vec![
|
||||
Item {
|
||||
id: "item-1".to_string(),
|
||||
name: "Item One".to_string(),
|
||||
price: 100,
|
||||
in_stock: true,
|
||||
},
|
||||
Item {
|
||||
id: "item-2".to_string(),
|
||||
name: "Item Two".to_string(),
|
||||
price: 200,
|
||||
in_stock: false,
|
||||
},
|
||||
];
|
||||
catalog1.insert_many(items_to_insert.clone());
|
||||
catalog1.persist().unwrap();
|
||||
// 2. new catalog -> 'load_by_class' -> 'list_by_class'
|
||||
let mut catalog2 = Catalog::new(db_path);
|
||||
catalog2.load_by_class::<Item>().unwrap();
|
||||
let mut loaded_items: Vec<Item> = catalog2.list_by_class::<Item>().collect();
|
||||
// Sort by ID to ensure consistent order for comparison
|
||||
let mut expected_items = items_to_insert;
|
||||
loaded_items.sort_by(|a, b| a.id.cmp(&b.id));
|
||||
expected_items.sort_by(|a, b| a.id.cmp(&b.id));
|
||||
// 3. verify all items are loaded
|
||||
assert_eq!(loaded_items.len(), 2);
|
||||
assert_eq!(loaded_items, expected_items);
|
||||
// Clean up
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integration_test_insert_persist_load_delete_persist_load() {
|
||||
// Scenario: 'insert' -> 'persist' -> 'load_by_id' -> 'delete' -> 'persist' -> 'load_by_id' should now return nothing for the deleted ID.
|
||||
todo!();
|
||||
let db_path = "target/test_dbs/integration_test_insert_persist_load_delete_persist_load.db";
|
||||
let path = std::path::Path::new(db_path);
|
||||
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
if path.exists() {
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
// 1. 'insert' -> 'persist'
|
||||
let mut catalog1 = Catalog::new(db_path);
|
||||
catalog1.init().unwrap();
|
||||
let item_to_delete = Item {
|
||||
id: "item-to-delete".to_string(),
|
||||
name: "Test Item".to_string(),
|
||||
price: 123,
|
||||
in_stock: true,
|
||||
};
|
||||
catalog1.upsert(item_to_delete.clone());
|
||||
catalog1.persist().unwrap();
|
||||
// 2. 'load_by_id' to confirm it's there
|
||||
let mut catalog2 = Catalog::new(db_path);
|
||||
catalog2.load_by_id("item-to-delete").unwrap();
|
||||
assert!(catalog2.get::<Item>("item-to-delete").is_some());
|
||||
// 3. 'delete' -> 'persist'
|
||||
catalog2.delete("item-to-delete");
|
||||
catalog2.persist().unwrap();
|
||||
// 4. 'load_by_id' should now return nothing
|
||||
let mut catalog3 = Catalog::new(db_path);
|
||||
catalog3.load_by_id("item-to-delete").unwrap();
|
||||
let loaded_item: Option<Item> = catalog3.get("item-to-delete");
|
||||
assert!(loaded_item.is_none());
|
||||
// Clean up
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integration_test_insert_get_by_class_and_attribute() {
|
||||
// Scenario: 'insert' -> 'get_by_class_and_attribute' should return the correct item.
|
||||
todo!();
|
||||
// This test focuses on in-memory functionality.
|
||||
let mut catalog = Catalog::new("dummy.db");
|
||||
let item1 = Item {
|
||||
id: "item-1".to_string(),
|
||||
name: "First Item".to_string(),
|
||||
price: 100,
|
||||
in_stock: true,
|
||||
};
|
||||
let item2 = Item {
|
||||
id: "item-2".to_string(),
|
||||
name: "Second Item".to_string(),
|
||||
price: 200,
|
||||
in_stock: false,
|
||||
};
|
||||
catalog.upsert(item1.clone());
|
||||
catalog.upsert(item2.clone());
|
||||
// Retrieve by a unique attribute
|
||||
let retrieved_item: Option<Item> =
|
||||
catalog.get_by_class_and_attribute("name", "Second Item");
|
||||
// Verify the correct item was retrieved
|
||||
assert_eq!(retrieved_item, Some(item2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integration_test_insert_many_list_by_class_and_attribute() {
|
||||
// Scenario: 'insert_many' -> 'list_by_class_and_attribute' should return all matching items.
|
||||
todo!();
|
||||
// This test focuses on in-memory functionality.
|
||||
let mut catalog = Catalog::new("dummy.db");
|
||||
let item1 = Item {
|
||||
id: "item-1".to_string(),
|
||||
name: "Item One".to_string(),
|
||||
price: 100,
|
||||
in_stock: true,
|
||||
};
|
||||
let item2 = Item {
|
||||
id: "item-2".to_string(),
|
||||
name: "Item Two".to_string(),
|
||||
price: 200,
|
||||
in_stock: false,
|
||||
};
|
||||
let item3 = Item {
|
||||
id: "item-3".to_string(),
|
||||
name: "Item Three".to_string(),
|
||||
price: 150,
|
||||
in_stock: true,
|
||||
};
|
||||
catalog.insert_many(vec![item1.clone(), item2.clone(), item3.clone()]);
|
||||
// List all items that are in stock
|
||||
let mut results: Vec<Item> = catalog
|
||||
.list_by_class_and_attribute("in_stock", true)
|
||||
.collect();
|
||||
// Sort for deterministic comparison
|
||||
results.sort_by(|a, b| a.id.cmp(&b.id));
|
||||
let mut expected = vec![item1, item3];
|
||||
expected.sort_by(|a, b| a.id.cmp(&b.id));
|
||||
// Verify the correct items were retrieved
|
||||
assert_eq!(results.len(), 2);
|
||||
assert_eq!(results, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // This test can be flaky as it depends on thread scheduling.
|
||||
fn integration_test_concurrency() {
|
||||
// Scenario: Concurrency - what happens if two 'Catalog' instances point to the same file? (e.g., one reads while the other writes).
|
||||
todo!();
|
||||
// Scenario: Concurrency - what happens if two 'Catalog' instances point to the same file?
|
||||
// This test demonstrates that without application-level locking, a "last-write-wins"
|
||||
// race condition can occur, leading to lost updates.
|
||||
let db_path = "target/test_dbs/integration_test_concurrency.db";
|
||||
let path = std::path::Path::new(db_path);
|
||||
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
if path.exists() {
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
// 1. Initial setup
|
||||
let mut catalog_setup = Catalog::new(db_path);
|
||||
catalog_setup.init().unwrap();
|
||||
let initial_item = Item {
|
||||
id: "item-1".to_string(),
|
||||
name: "Original".to_string(),
|
||||
price: 100,
|
||||
in_stock: true,
|
||||
};
|
||||
catalog_setup.upsert(initial_item);
|
||||
catalog_setup.persist().unwrap();
|
||||
let db_path_arc = std::sync::Arc::new(String::from(db_path));
|
||||
// 2. Thread 1: Loads, updates name, and persists.
|
||||
let db_path_arc1 = std::sync::Arc::clone(&db_path_arc);
|
||||
let handle1 = std::thread::spawn(move || {
|
||||
let mut catalog1 = Catalog::new(&db_path_arc1);
|
||||
catalog1.load_by_id("item-1").unwrap();
|
||||
let mut item = catalog1.get::<Item>("item-1").unwrap();
|
||||
item.name = "Updated by Thread 1".to_string();
|
||||
catalog1.upsert(item);
|
||||
catalog1.persist().unwrap();
|
||||
});
|
||||
// 3. Thread 2: Loads, updates price, and persists.
|
||||
let db_path_arc2 = std::sync::Arc::clone(&db_path_arc);
|
||||
let handle2 = std::thread::spawn(move || {
|
||||
let mut catalog2 = Catalog::new(&db_path_arc2);
|
||||
catalog2.load_by_id("item-1").unwrap();
|
||||
let mut item = catalog2.get::<Item>("item-1").unwrap();
|
||||
item.price = 200;
|
||||
catalog2.upsert(item);
|
||||
catalog2.persist().unwrap();
|
||||
});
|
||||
handle1.join().unwrap();
|
||||
handle2.join().unwrap();
|
||||
// 4. Verification: Load the data and check the final state.
|
||||
let mut catalog_verify = Catalog::new(db_path);
|
||||
catalog_verify.load_by_id("item-1").unwrap();
|
||||
let final_item: Item = catalog_verify.get("item-1").unwrap();
|
||||
// The final state depends on which thread persisted last. One update will have been lost.
|
||||
let thread1_won =
|
||||
final_item.name == "Updated by Thread 1" && final_item.price == 100;
|
||||
let thread2_won =
|
||||
final_item.name == "Original" && final_item.price == 200;
|
||||
assert!(
|
||||
thread1_won || thread2_won,
|
||||
"Final state must be the result of one of the threads winning the race."
|
||||
);
|
||||
// Clean up
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user