use db::{Db, audit, catalog}; use domain::{AuditAction, AuditActor, ObjectInput, Visibility}; use sqlx::PgPool; fn base() -> ObjectInput { ObjectInput { object_number: "LM-1".into(), object_name: "vase".into(), number_of_objects: 1, brief_description: None, current_location: Some("shelf A1".into()), current_owner: None, recorder: None, recording_date: None, visibility: Visibility::Draft, } } #[sqlx::test] async fn update_changes_are_audited_as_diffs(pool: PgPool) { let db = Db::from_pool(pool); let mut tx = db.pool().begin().await.unwrap(); let id = catalog::create_object(&mut tx, AuditActor::System, &base()) .await .unwrap(); tx.commit().await.unwrap(); let mut changed = base(); changed.object_name = "roman vase".into(); changed.visibility = Visibility::Public; let mut tx = db.pool().begin().await.unwrap(); let updated = catalog::update_object(&mut tx, AuditActor::System, id, &changed) .await .unwrap(); tx.commit().await.unwrap(); assert!(updated); let obj = catalog::object_by_id(db.pool(), id).await.unwrap().unwrap(); assert_eq!(obj.object_name, "roman vase"); assert_eq!(obj.visibility, Visibility::Public); let history = audit::history_for(db.pool(), "object", id.to_uuid()) .await .unwrap(); assert_eq!(history.len(), 2); // created + updated let update = &history[1]; assert_eq!(update.action, AuditAction::Updated); let mut fields: Vec<&str> = update.changes.iter().map(|c| c.field.as_str()).collect(); fields.sort_unstable(); assert_eq!(fields, vec!["object_name", "visibility"]); } #[sqlx::test] async fn no_op_update_records_no_audit(pool: PgPool) { let db = Db::from_pool(pool); let mut tx = db.pool().begin().await.unwrap(); let id = catalog::create_object(&mut tx, AuditActor::System, &base()) .await .unwrap(); tx.commit().await.unwrap(); let mut tx = db.pool().begin().await.unwrap(); let updated = catalog::update_object(&mut tx, AuditActor::System, id, &base()) .await .unwrap(); tx.commit().await.unwrap(); assert!(updated); let history = audit::history_for(db.pool(), "object", id.to_uuid()) .await .unwrap(); assert_eq!( history.len(), 1, "a no-op update must not add an audit entry" ); } #[sqlx::test] async fn update_missing_returns_false(pool: PgPool) { let db = Db::from_pool(pool); let mut tx = db.pool().begin().await.unwrap(); let updated = catalog::update_object( &mut tx, AuditActor::System, domain::ObjectId::new(), &base(), ) .await .unwrap(); tx.commit().await.unwrap(); assert!(!updated); } #[sqlx::test] async fn delete_removes_and_audits(pool: PgPool) { let db = Db::from_pool(pool); let mut tx = db.pool().begin().await.unwrap(); let id = catalog::create_object(&mut tx, AuditActor::System, &base()) .await .unwrap(); tx.commit().await.unwrap(); let mut tx = db.pool().begin().await.unwrap(); let deleted = catalog::delete_object(&mut tx, AuditActor::System, id) .await .unwrap(); tx.commit().await.unwrap(); assert!(deleted); assert!( catalog::object_by_id(db.pool(), id) .await .unwrap() .is_none() ); let history = audit::history_for(db.pool(), "object", id.to_uuid()) .await .unwrap(); assert_eq!(history.last().unwrap().action, AuditAction::Deleted); }