use db::{Db, audit, catalog}; use domain::{AuditAction, AuditActor, ObjectInput, Visibility}; use sqlx::PgPool; fn sample_input(number: &str) -> ObjectInput { ObjectInput { object_number: number.into(), object_name: "vase".into(), number_of_objects: 1, brief_description: Some("a small vase".into()), current_location: Some("shelf A1".into()), current_owner: None, recorder: Some("anna".into()), recording_date: None, visibility: Visibility::Draft, } } #[sqlx::test] async fn create_reads_back_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, &sample_input("LM-1")) .await .unwrap(); tx.commit().await.unwrap(); let obj = catalog::object_by_id(db.pool(), id).await.unwrap().unwrap(); assert_eq!(obj.object_number, "LM-1"); assert_eq!(obj.object_name, "vase"); assert_eq!(obj.number_of_objects, 1); assert_eq!(obj.brief_description.as_deref(), Some("a small vase")); assert_eq!(obj.visibility, Visibility::Draft); let history = audit::history_for(db.pool(), "object", id.to_uuid()) .await .unwrap(); assert_eq!(history.len(), 1); assert_eq!(history[0].action, AuditAction::Created); assert_eq!(history[0].actor, AuditActor::System); assert!( history[0] .changes .iter() .any(|c| c.field == "object_number") ); } #[sqlx::test] async fn list_returns_created_objects(pool: PgPool) { let db = Db::from_pool(pool); let mut tx = db.pool().begin().await.unwrap(); catalog::create_object(&mut tx, AuditActor::System, &sample_input("LM-1")) .await .unwrap(); catalog::create_object(&mut tx, AuditActor::System, &sample_input("LM-2")) .await .unwrap(); tx.commit().await.unwrap(); let all = catalog::list_objects(db.pool()).await.unwrap(); assert_eq!(all.len(), 2); assert_eq!(all[0].object_number, "LM-1"); assert_eq!(all[1].object_number, "LM-2"); } fn input(number: &str, name: &str, visibility: Visibility) -> ObjectInput { ObjectInput { object_number: number.into(), object_name: name.into(), number_of_objects: 1, brief_description: None, current_location: None, current_owner: None, recorder: None, recording_date: None, visibility, } } async fn seed(pool: &PgPool, inputs: &[ObjectInput]) { let db = Db::from_pool(pool.clone()); let mut tx = db.pool().begin().await.unwrap(); for it in inputs { catalog::create_object(&mut tx, AuditActor::System, it) .await .unwrap(); } tx.commit().await.unwrap(); } #[sqlx::test] async fn query_orders_by_name_descending(pool: PgPool) { let db = Db::from_pool(pool.clone()); seed( &pool, &[ input("LM-1", "alpha", Visibility::Draft), input("LM-2", "gamma", Visibility::Draft), input("LM-3", "beta", Visibility::Draft), ], ) .await; let query = catalog::ObjectQuery { sort: catalog::ObjectSort::ObjectName, descending: true, visibility: None, q: None, }; let rows = catalog::list_objects_query(db.pool(), &query, 50, 0) .await .unwrap(); let names: Vec<&str> = rows.iter().map(|o| o.object_name.as_str()).collect(); assert_eq!(names, ["gamma", "beta", "alpha"]); } #[sqlx::test] async fn query_filters_by_visibility(pool: PgPool) { let db = Db::from_pool(pool.clone()); seed( &pool, &[ input("LM-1", "draft one", Visibility::Draft), input("LM-2", "internal one", Visibility::Internal), input("LM-3", "draft two", Visibility::Draft), ], ) .await; let query = catalog::ObjectQuery { sort: catalog::ObjectSort::ObjectNumber, descending: false, visibility: Some("draft"), q: None, }; let rows = catalog::list_objects_query(db.pool(), &query, 50, 0) .await .unwrap(); assert_eq!(rows.len(), 2); assert!(rows.iter().all(|o| o.visibility == Visibility::Draft)); let total = catalog::count_objects_query(db.pool(), Some("draft"), None) .await .unwrap(); assert_eq!(total, 2); } #[sqlx::test] async fn query_quick_filter_matches_number_or_name(pool: PgPool) { let db = Db::from_pool(pool.clone()); seed( &pool, &[ input("RED-1", "scarlet vase", Visibility::Draft), input("BLU-1", "azure bowl", Visibility::Draft), input("LM-9", "red kettle", Visibility::Internal), ], ) .await; // Matches the object_number of the first row. let by_number = catalog::ObjectQuery { sort: catalog::ObjectSort::ObjectNumber, descending: false, visibility: None, q: Some("red"), }; let rows = catalog::list_objects_query(db.pool(), &by_number, 50, 0) .await .unwrap(); // ILIKE: "RED-1" by number and "red kettle" by name. assert_eq!(rows.len(), 2); let total = catalog::count_objects_query(db.pool(), None, Some("red")) .await .unwrap(); assert_eq!(total, 2); // A term matching only a name. let by_name = catalog::ObjectQuery { sort: catalog::ObjectSort::ObjectNumber, descending: false, visibility: None, q: Some("azure"), }; let rows = catalog::list_objects_query(db.pool(), &by_name, 50, 0) .await .unwrap(); assert_eq!(rows.len(), 1); assert_eq!(rows[0].object_number, "BLU-1"); } #[sqlx::test] async fn object_by_id_missing_is_none(pool: PgPool) { let db = Db::from_pool(pool); assert!( catalog::object_by_id(db.pool(), domain::ObjectId::new()) .await .unwrap() .is_none() ); } #[sqlx::test] async fn object_with_date_and_all_none_optionals_round_trips(pool: PgPool) { let db = Db::from_pool(pool); let date = time::Date::from_calendar_date(2020, time::Month::January, 28).unwrap(); let input = ObjectInput { object_number: "LM-3".into(), object_name: "drawing".into(), number_of_objects: 1, brief_description: None, current_location: None, current_owner: None, recorder: None, recording_date: Some(date), visibility: Visibility::Internal, }; let mut tx = db.pool().begin().await.unwrap(); let id = catalog::create_object(&mut tx, AuditActor::System, &input) .await .unwrap(); tx.commit().await.unwrap(); let obj = catalog::object_by_id(db.pool(), id).await.unwrap().unwrap(); assert_eq!(obj.recording_date, Some(date)); assert_eq!(obj.brief_description, None); assert_eq!(obj.current_location, None); assert_eq!(obj.current_owner, None); assert_eq!(obj.recorder, None); assert_eq!(obj.visibility, Visibility::Internal); let history = audit::history_for(db.pool(), "object", id.to_uuid()) .await .unwrap(); assert!( history[0] .changes .iter() .any(|c| c.field == "recording_date") ); } #[sqlx::test] async fn new_object_has_empty_fields(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, &sample_input("LM-9")) .await .unwrap(); tx.commit().await.unwrap(); let obj = catalog::object_by_id(db.pool(), id).await.unwrap().unwrap(); assert_eq!(obj.fields, serde_json::json!({})); }