From a5dcf7b54ac6a4818198ee91464c5df35d2cffec Mon Sep 17 00:00:00 2001 From: davidemazzocchi Date: Thu, 16 Oct 2025 16:15:49 +0200 Subject: [PATCH] test: add tests to sqlite_persist_catalog function --- .../heave/src/fun/sqlite_persist_catalog.rs | 134 +++++++++++++++++- 1 file changed, 129 insertions(+), 5 deletions(-) diff --git a/01.workspace/heave/src/fun/sqlite_persist_catalog.rs b/01.workspace/heave/src/fun/sqlite_persist_catalog.rs index fc33228..403ce33 100644 --- a/01.workspace/heave/src/fun/sqlite_persist_catalog.rs +++ b/01.workspace/heave/src/fun/sqlite_persist_catalog.rs @@ -89,29 +89,153 @@ pub fn run(path: &path::Path, catalog: &Catalog) -> result::Result<(), FailedTo> #[cfg(test)] mod tests { use super::*; + use std::{collections::HashMap, fs, path::Path}; + fn setup_db(db_path: &Path) -> Connection { + let _ = fs::remove_file(db_path); + fun::sqlite_init_db::run(db_path).unwrap(); + Connection::open(db_path).unwrap() + } + fn count_rows(conn: &Connection, table: &str, where_clause: &str) -> i64 { + let mut stmt = conn + .prepare(&format!( + "SELECT COUNT(*) FROM {} WHERE {}", + table, where_clause + )) + .unwrap(); + stmt.query_row([], |row| row.get(0)).unwrap() + } #[test] fn persist_should_insert_new_entities_and_attributes() { // Verifies that entities marked as 'New' in the catalog are inserted into the database, along with all their attributes. - todo!(); + let db_path = Path::new("test_insert.db"); + let conn = setup_db(db_path); + let mut catalog = Catalog::new(db_path.to_str().unwrap()); + let mut entity = Entity { + id: "e1".to_string(), + class: "c1".to_string(), + attributes: HashMap::new(), + state: EntityState::New, + ref_date: None, + }; + entity.attributes.insert( + "a1".to_string(), + Attribute { + id: "a1".to_string(), + value: Value::Text("v1".to_string()), + }, + ); + catalog.items.insert("e1".to_string(), entity); + assert!(run(db_path, &catalog).is_ok()); + assert_eq!(count_rows(&conn, "entity", "id = 'e1'"), 1); + assert_eq!(count_rows(&conn, "attribute", "entity_id = 'e1'"), 1); + fs::remove_file(db_path).unwrap(); } #[test] fn persist_should_delete_entities_marked_for_deletion() { // Ensures that entities marked as 'ToDelete' are correctly removed from the database. - todo!(); + let db_path = Path::new("test_delete.db"); + let conn = setup_db(db_path); + conn.execute("INSERT INTO entity (id, class) VALUES ('e1', 'c1')", []) + .unwrap(); + let mut catalog = Catalog::new(db_path.to_str().unwrap()); + let entity = Entity { + id: "e1".to_string(), + class: "c1".to_string(), + attributes: HashMap::new(), + state: EntityState::ToDelete, + ref_date: None, + }; + catalog.items.insert("e1".to_string(), entity); + assert!(run(db_path, &catalog).is_ok()); + assert_eq!(count_rows(&conn, "entity", "id = 'e1'"), 0); + fs::remove_file(db_path).unwrap(); } #[test] fn persist_should_handle_a_mix_of_new_and_deleted_entities() { // Tests the function's ability to handle a batch operation involving both new entities to be inserted and existing ones to be deleted. - todo!(); + let db_path = Path::new("test_mix.db"); + let conn = setup_db(db_path); + conn.execute("INSERT INTO entity (id, class) VALUES ('e1', 'c1')", []) + .unwrap(); + let mut catalog = Catalog::new(db_path.to_str().unwrap()); + let to_delete = Entity { + id: "e1".to_string(), + class: "c1".to_string(), + attributes: HashMap::new(), + state: EntityState::ToDelete, + ref_date: None, + }; + let to_add = Entity { + id: "e2".to_string(), + class: "c2".to_string(), + attributes: HashMap::new(), + state: EntityState::New, + ref_date: None, + }; + catalog.items.insert("e1".to_string(), to_delete); + catalog.items.insert("e2".to_string(), to_add); + assert!(run(db_path, &catalog).is_ok()); + assert_eq!(count_rows(&conn, "entity", "id = 'e1'"), 0); + assert_eq!(count_rows(&conn, "entity", "id = 'e2'"), 1); + fs::remove_file(db_path).unwrap(); } #[test] fn persist_should_not_affect_unmodified_entities() { // Verifies that entities in the catalog that are not marked as 'New' or 'ToDelete' remain untouched in the database. - todo!(); + let db_path = Path::new("test_unmodified.db"); + let conn = setup_db(db_path); + conn.execute("INSERT INTO entity (id, class) VALUES ('e1', 'c1')", []) + .unwrap(); + let mut catalog = Catalog::new(db_path.to_str().unwrap()); + let unmodified = Entity { + id: "e1".to_string(), + class: "c1".to_string(), + attributes: HashMap::new(), + state: EntityState::Loaded, + ref_date: None, + }; + catalog.items.insert("e1".to_string(), unmodified); + assert!(run(db_path, &catalog).is_ok()); + assert_eq!(count_rows(&conn, "entity", "id = 'e1'"), 1); + fs::remove_file(db_path).unwrap(); } #[test] fn persist_should_rollback_transaction_on_failure() { // Ensures that if any part of the persistence process fails, the entire transaction is rolled back, leaving the database state unchanged. - todo!(); + let db_path = Path::new("test_rollback.db"); + let conn = setup_db(db_path); + conn.execute( + "INSERT INTO entity (id, class) VALUES ('e_existing', 'c1')", + [], + ) + .unwrap(); + let mut catalog = Catalog::new(db_path.to_str().unwrap()); + let mut new_entity = Entity { + id: "e_new".to_string(), + class: "c1".to_string(), + attributes: HashMap::new(), + state: EntityState::New, + ref_date: None, + }; + new_entity.attributes.insert( + "a1".to_string(), + Attribute { + id: "a1".to_string(), + value: Value::Text("v1".to_string()), + }, + ); + catalog.items.insert("e_new".to_string(), new_entity); + // Corrupt the DB to cause a failure during the transaction + conn.execute("DROP TABLE attribute", []).unwrap(); + drop(conn); + let result = run(db_path, &catalog); + assert!(result.is_err()); + // Re-open connection to check state + let conn = Connection::open(db_path).unwrap(); + // The new entity should not have been inserted due to rollback + assert_eq!(count_rows(&conn, "entity", "id = 'e_new'"), 0); + // The existing entity should still be there + assert_eq!(count_rows(&conn, "entity", "id = 'e_existing'"), 1); + fs::remove_file(db_path).unwrap(); } }