test: add test for thread safety; remove mut for upsert and persist

This commit is contained in:
2025-10-29 12:17:33 +01:00
parent 6f6df06be2
commit a38b96a667
13 changed files with 23 additions and 32 deletions

View File

@@ -59,7 +59,7 @@ fn main() {
// Define the path for the SQLite database file. // Define the path for the SQLite database file.
let db_path = "./simple_product.sqlite3"; let db_path = "./simple_product.sqlite3";
// Create a new `Catalog` instance with the specified database path. // Create a new `Catalog` instance with the specified database path.
let mut catalog = Catalog::new(db_path); let catalog = Catalog::new(db_path);
// Initialize the catalog, which sets up the database. // Initialize the catalog, which sets up the database.
catalog.init().unwrap(); catalog.init().unwrap();
// Create a new `Product` instance representing a laptop. // Create a new `Product` instance representing a laptop.

View File

@@ -18,7 +18,7 @@ impl Catalog {
/// removed from the in-memory catalog. /// removed from the in-memory catalog.
/// - All other entities that were successfully persisted (new or updated) have /// - All other entities that were successfully persisted (new or updated) have
/// their state changed to `EntityState::Loaded`. /// their state changed to `EntityState::Loaded`.
pub fn persist(&mut self) -> result::Result<(), FailedTo> { pub fn persist(&self) -> result::Result<(), FailedTo> {
let path = path::Path::new(&self.path); let path = path::Path::new(&self.path);
self.on_items(|items| { self.on_items(|items| {
sqlite::persist::catalog(path, items).map_err(|_| FailedTo::PersistCatalog)?; sqlite::persist::catalog(path, items).map_err(|_| FailedTo::PersistCatalog)?;

View File

@@ -18,7 +18,7 @@ impl Catalog {
/// # Arguments /// # Arguments
/// ///
/// * `object` - The object to insert or update, which must implement the `EAV` trait. /// * `object` - The object to insert or update, which must implement the `EAV` trait.
pub fn upsert(&mut self, object: impl EAV) -> Result<(), FailedTo> { pub fn upsert(&self, object: impl EAV) -> Result<(), FailedTo> {
let mut entity = object.try_into().map_err(|_| FailedTo::ConvertObject)?; let mut entity = object.try_into().map_err(|_| FailedTo::ConvertObject)?;
self.on_items(|items| { self.on_items(|items| {
if items.contains_key(&entity.id) { if items.contains_key(&entity.id) {

View File

@@ -4,7 +4,7 @@ mod tests {
#[test] #[test]
fn get_should_retrieve_and_convert_entity_by_id() { fn get_should_retrieve_and_convert_entity_by_id() {
// Should retrieve an entity by its ID and correctly convert it to the target type 'T'. // Should retrieve an entity by its ID and correctly convert it to the target type 'T'.
let mut catalog = Catalog::new("dummy.db"); let catalog = Catalog::new("dummy.db");
let item = Item { let item = Item {
id: "item-123".to_string(), id: "item-123".to_string(),
subclass: Some("subitem".to_string()), subclass: Some("subitem".to_string()),
@@ -21,7 +21,7 @@ mod tests {
#[test] #[test]
fn get_should_return_none_for_nonexistent_id() { fn get_should_return_none_for_nonexistent_id() {
// Should return 'None' if the ID does not exist. // Should return 'None' if the ID does not exist.
let mut catalog = Catalog::new("dummy.db"); let catalog = Catalog::new("dummy.db");
let item = Item { let item = Item {
id: "item-123".to_string(), id: "item-123".to_string(),
name: "Test Item".to_string(), name: "Test Item".to_string(),

View File

@@ -11,7 +11,7 @@ mod tests {
std::fs::remove_file(path).unwrap(); std::fs::remove_file(path).unwrap();
} }
// 1. 'init' -> 'insert' -> 'persist' // 1. 'init' -> 'insert' -> 'persist'
let mut catalog1 = Catalog::new(db_path); let catalog1 = Catalog::new(db_path);
catalog1.init().unwrap(); catalog1.init().unwrap();
let item_to_insert = Item { let item_to_insert = Item {
id: "item-1".to_string(), id: "item-1".to_string(),
@@ -96,7 +96,7 @@ mod tests {
std::fs::remove_file(path).unwrap(); std::fs::remove_file(path).unwrap();
} }
// 1. 'insert' -> 'persist' // 1. 'insert' -> 'persist'
let mut catalog1 = Catalog::new(db_path); let catalog1 = Catalog::new(db_path);
catalog1.init().unwrap(); catalog1.init().unwrap();
let item_to_delete = Item { let item_to_delete = Item {
id: "item-to-delete".to_string(), id: "item-to-delete".to_string(),
@@ -136,7 +136,7 @@ mod tests {
std::fs::remove_file(path).unwrap(); std::fs::remove_file(path).unwrap();
} }
// 1. Initial setup // 1. Initial setup
let mut catalog_setup = Catalog::new(db_path); let catalog_setup = Catalog::new(db_path);
catalog_setup.init().unwrap(); catalog_setup.init().unwrap();
let initial_item = Item { let initial_item = Item {
id: "item-1".to_string(), id: "item-1".to_string(),

View File

@@ -4,7 +4,7 @@ mod tests {
#[test] #[test]
fn list_by_class_should_return_all_entities_of_class() { fn list_by_class_should_return_all_entities_of_class() {
// Should return an iterator with all entities of a specific class. // Should return an iterator with all entities of a specific class.
let mut catalog = Catalog::new("dummy.db"); let catalog = Catalog::new("dummy.db");
let item1 = Item { let item1 = Item {
id: "item-1".to_string(), id: "item-1".to_string(),
subclass: Some("subitem".to_string()), subclass: Some("subitem".to_string()),

View File

@@ -3,7 +3,7 @@ mod tests {
use crate::*; use crate::*;
#[test] #[test]
fn list_by_class_and_subclass_should_return_matching_entities() { fn list_by_class_and_subclass_should_return_matching_entities() {
let mut catalog = Catalog::new("dummy.db"); let catalog = Catalog::new("dummy.db");
let item1 = Item { let item1 = Item {
id: "item-1".to_string(), id: "item-1".to_string(),
subclass: Some("electronics".to_string()), subclass: Some("electronics".to_string()),
@@ -35,7 +35,7 @@ mod tests {
} }
#[test] #[test]
fn list_by_class_and_subclass_should_return_empty_if_no_match() { fn list_by_class_and_subclass_should_return_empty_if_no_match() {
let mut catalog = Catalog::new("dummy.db"); let catalog = Catalog::new("dummy.db");
let item1 = Item { let item1 = Item {
id: "item-1".to_string(), id: "item-1".to_string(),
subclass: Some("electronics".to_string()), subclass: Some("electronics".to_string()),

View File

@@ -11,7 +11,7 @@ mod tests {
std::fs::remove_file(path).unwrap(); std::fs::remove_file(path).unwrap();
} }
// 1. Setup DB with a few items of the same class // 1. Setup DB with a few items of the same class
let mut catalog1 = Catalog::new(db_path); let catalog1 = Catalog::new(db_path);
catalog1.init().unwrap(); catalog1.init().unwrap();
let item1 = Item { let item1 = Item {
id: "item-1".to_string(), id: "item-1".to_string(),
@@ -70,7 +70,7 @@ mod tests {
std::fs::remove_file(path).unwrap(); std::fs::remove_file(path).unwrap();
} }
// 1. Persist an item to the database. // 1. Persist an item to the database.
let mut catalog1 = Catalog::new(db_path); let catalog1 = Catalog::new(db_path);
catalog1.init().unwrap(); catalog1.init().unwrap();
let item_in_db = Item { let item_in_db = Item {
id: "item-1".to_string(), id: "item-1".to_string(),

View File

@@ -133,7 +133,7 @@ mod tests {
if path.exists() { if path.exists() {
std::fs::remove_file(path).unwrap(); std::fs::remove_file(path).unwrap();
} }
let mut catalog_setup = Catalog::new(db_path); let catalog_setup = Catalog::new(db_path);
catalog_setup.init().unwrap(); catalog_setup.init().unwrap();
let item_in_db = Item { let item_in_db = Item {
id: "item-1".to_string(), id: "item-1".to_string(),
@@ -1421,7 +1421,7 @@ mod tests {
if path.exists() { if path.exists() {
std::fs::remove_file(path).unwrap(); std::fs::remove_file(path).unwrap();
} }
let mut catalog_setup = Catalog::new(db_path); let catalog_setup = Catalog::new(db_path);
catalog_setup.init().unwrap(); catalog_setup.init().unwrap();
// Define a second struct with a different class // Define a second struct with a different class
#[derive(Debug, Default, PartialEq, Clone)] #[derive(Debug, Default, PartialEq, Clone)]

View File

@@ -11,7 +11,7 @@ mod tests {
std::fs::remove_file(path).unwrap(); std::fs::remove_file(path).unwrap();
} }
// 1. Create a catalog, insert an item, and persist it to the DB. // 1. Create a catalog, insert an item, and persist it to the DB.
let mut catalog1 = Catalog::new(db_path); let catalog1 = Catalog::new(db_path);
catalog1.init().unwrap(); catalog1.init().unwrap();
let item_to_persist = Item { let item_to_persist = Item {
id: "item-1".to_string(), id: "item-1".to_string(),
@@ -66,7 +66,7 @@ mod tests {
std::fs::remove_file(path).unwrap(); std::fs::remove_file(path).unwrap();
} }
// 1. Persist an item to the database. // 1. Persist an item to the database.
let mut catalog1 = Catalog::new(db_path); let catalog1 = Catalog::new(db_path);
catalog1.init().unwrap(); catalog1.init().unwrap();
let item_in_db = Item { let item_in_db = Item {
id: "item-1".to_string(), id: "item-1".to_string(),

View File

@@ -11,7 +11,7 @@ mod tests {
std::fs::remove_file(path).unwrap(); std::fs::remove_file(path).unwrap();
} }
// 1. Create catalog, insert an item, and persist // 1. Create catalog, insert an item, and persist
let mut catalog1 = Catalog::new(db_path); let catalog1 = Catalog::new(db_path);
catalog1.init().unwrap(); catalog1.init().unwrap();
let item1 = Item { let item1 = Item {
id: "item-1".to_string(), id: "item-1".to_string(),
@@ -77,7 +77,7 @@ mod tests {
std::fs::remove_file(path).unwrap(); std::fs::remove_file(path).unwrap();
} }
// 1. Insert an entity and persist it. // 1. Insert an entity and persist it.
let mut catalog1 = Catalog::new(db_path); let catalog1 = Catalog::new(db_path);
catalog1.init().unwrap(); catalog1.init().unwrap();
let original_item = Item { let original_item = Item {
id: "item-1".to_string(), id: "item-1".to_string(),
@@ -132,7 +132,7 @@ mod tests {
std::fs::remove_file(path).unwrap(); std::fs::remove_file(path).unwrap();
} }
// 1. Setup: Pre-populate the database with some items. // 1. Setup: Pre-populate the database with some items.
let mut catalog_setup = Catalog::new(db_path); let catalog_setup = Catalog::new(db_path);
catalog_setup.init().unwrap(); catalog_setup.init().unwrap();
let item_to_update_original = Item { let item_to_update_original = Item {
id: "update-me".to_string(), id: "update-me".to_string(),
@@ -221,7 +221,7 @@ mod tests {
// Using a directory as a path should cause a failure. // Using a directory as a path should cause a failure.
let invalid_path = "target/test_dbs/a_directory_for_persist_fail"; let invalid_path = "target/test_dbs/a_directory_for_persist_fail";
std::fs::create_dir_all(invalid_path).unwrap(); std::fs::create_dir_all(invalid_path).unwrap();
let mut catalog = Catalog::new(invalid_path); let catalog = Catalog::new(invalid_path);
let item = Item { let item = Item {
id: "item-1".to_string(), id: "item-1".to_string(),
name: "Test".to_string(), name: "Test".to_string(),

View File

@@ -3,7 +3,6 @@ mod tests {
use crate::*; use crate::*;
use std::sync::*; use std::sync::*;
use std::thread; use std::thread;
#[test] #[test]
fn thread_safety_test() { fn thread_safety_test() {
let db_path = "target/test_dbs/thread_safety_test.db"; let db_path = "target/test_dbs/thread_safety_test.db";
@@ -12,10 +11,8 @@ mod tests {
std::fs::remove_file(path).unwrap(); std::fs::remove_file(path).unwrap();
} }
std::fs::create_dir_all(path.parent().unwrap()).unwrap(); std::fs::create_dir_all(path.parent().unwrap()).unwrap();
let catalog = Arc::new(Catalog::new(db_path)); let catalog = Arc::new(Catalog::new(db_path));
catalog.init().unwrap(); catalog.init().unwrap();
let mut handles = vec![]; let mut handles = vec![];
for i in 0..10 { for i in 0..10 {
let catalog = Arc::clone(&catalog); let catalog = Arc::clone(&catalog);
@@ -36,22 +33,16 @@ mod tests {
}); });
handles.push(handle); handles.push(handle);
} }
for handle in handles { for handle in handles {
handle.join().unwrap(); handle.join().unwrap();
} }
let total_items = catalog.with_items(|items| Ok(items.len())).unwrap(); let total_items = catalog.with_items(|items| Ok(items.len())).unwrap();
assert_eq!(total_items, 1000); assert_eq!(total_items, 1000);
catalog.persist().unwrap(); catalog.persist().unwrap();
let mut new_catalog = Catalog::new(db_path); let mut new_catalog = Catalog::new(db_path);
new_catalog.load_by_class::<Item>().unwrap(); new_catalog.load_by_class::<Item>().unwrap();
let total_items_after_load = new_catalog.with_items(|items| Ok(items.len())).unwrap(); let total_items_after_load = new_catalog.with_items(|items| Ok(items.len())).unwrap();
assert_eq!(total_items_after_load, 1000); assert_eq!(total_items_after_load, 1000);
std::fs::remove_file(path).unwrap(); std::fs::remove_file(path).unwrap();
} }
} }

View File

@@ -4,7 +4,7 @@ mod tests {
#[test] #[test]
fn upsert_should_add_single_entity_as_new() { fn upsert_should_add_single_entity_as_new() {
// 'upsert()': Should add a single entity to the 'items' map with 'EntityState::New'. // 'upsert()': Should add a single entity to the 'items' map with 'EntityState::New'.
let mut catalog = Catalog::new("dummy.db"); let catalog = Catalog::new("dummy.db");
let item = Item { let item = Item {
id: "item-123".to_string(), id: "item-123".to_string(),
name: "Test Item".to_string(), name: "Test Item".to_string(),
@@ -29,7 +29,7 @@ mod tests {
#[test] #[test]
fn upsert_should_overwrite_existing_entity() { fn upsert_should_overwrite_existing_entity() {
// 'upsert()': Should overwrite an existing entity with the same ID. // 'upsert()': Should overwrite an existing entity with the same ID.
let mut catalog = Catalog::new("dummy.db"); let catalog = Catalog::new("dummy.db");
let item1 = Item { let item1 = Item {
id: "item-123".to_string(), id: "item-123".to_string(),
name: "First Item".to_string(), name: "First Item".to_string(),