177 lines
5.1 KiB
Rust
177 lines
5.1 KiB
Rust
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 missing = domain::ObjectId::new();
|
|
|
|
let mut tx = db.pool().begin().await.unwrap();
|
|
let updated = catalog::update_object(&mut tx, AuditActor::System, missing, &base())
|
|
.await
|
|
.unwrap();
|
|
tx.commit().await.unwrap();
|
|
assert!(!updated);
|
|
|
|
let history = audit::history_for(db.pool(), "object", missing.to_uuid())
|
|
.await
|
|
.unwrap();
|
|
assert!(
|
|
history.is_empty(),
|
|
"updating a missing object records no audit"
|
|
);
|
|
}
|
|
|
|
#[sqlx::test]
|
|
async fn delete_missing_returns_false(pool: PgPool) {
|
|
let db = Db::from_pool(pool);
|
|
let mut tx = db.pool().begin().await.unwrap();
|
|
let deleted = catalog::delete_object(&mut tx, AuditActor::System, domain::ObjectId::new())
|
|
.await
|
|
.unwrap();
|
|
tx.commit().await.unwrap();
|
|
assert!(!deleted);
|
|
}
|
|
|
|
#[sqlx::test]
|
|
async fn clearing_a_field_is_audited(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 cleared = base();
|
|
cleared.current_location = None;
|
|
|
|
let mut tx = db.pool().begin().await.unwrap();
|
|
catalog::update_object(&mut tx, AuditActor::System, id, &cleared)
|
|
.await
|
|
.unwrap();
|
|
tx.commit().await.unwrap();
|
|
|
|
let history = audit::history_for(db.pool(), "object", id.to_uuid())
|
|
.await
|
|
.unwrap();
|
|
let update = history.last().unwrap();
|
|
let loc = update
|
|
.changes
|
|
.iter()
|
|
.find(|c| c.field == "current_location")
|
|
.expect("location change recorded");
|
|
assert!(
|
|
loc.before.is_some(),
|
|
"cleared field should have before = Some"
|
|
);
|
|
assert!(
|
|
loc.after.is_none(),
|
|
"cleared field should have after = None"
|
|
);
|
|
}
|
|
|
|
#[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);
|
|
}
|