use db::{Db, DeleteOutcome, catalog, fields, vocab}; use domain::{ AuditActor, AuthorityKind, FieldType, LocalizedLabel, NewFieldDefinition, ObjectInput, Visibility, }; use sqlx::PgPool; fn sample_object_input() -> ObjectInput { ObjectInput { object_number: "X.1".into(), object_name: "Test".into(), number_of_objects: 1, brief_description: None, current_location: None, current_owner: None, recorder: None, recording_date: None, visibility: Visibility::Draft, } } fn labels() -> Vec { vec![ LocalizedLabel { lang: "sv".into(), label: "material".into(), }, LocalizedLabel { lang: "en".into(), label: "material".into(), }, ] } #[sqlx::test] async fn text_field_round_trips(pool: PgPool) { let db = Db::from_pool(pool); let mut tx = db.pool().begin().await.unwrap(); let id = fields::create_field_definition( &mut tx, &NewFieldDefinition { key: "comments".into(), field_type: FieldType::Text, required: false, group_key: Some("identification".into()), labels: labels(), }, ) .await .unwrap(); tx.commit().await.unwrap(); let def = fields::field_definition_by_key(db.pool(), "comments") .await .unwrap() .unwrap(); assert_eq!(def.id, id); assert_eq!(def.field_type, FieldType::Text); assert_eq!(def.group_key.as_deref(), Some("identification")); assert_eq!(def.labels.len(), 2); assert!( fields::field_definition_by_key(db.pool(), "nope") .await .unwrap() .is_none() ); } #[sqlx::test] async fn term_and_authority_fields_round_trip_their_binding(pool: PgPool) { let db = Db::from_pool(pool); let mut tx = db.pool().begin().await.unwrap(); let material = vocab::create_vocabulary(&mut tx, AuditActor::System, "material") .await .unwrap(); tx.commit().await.unwrap(); let mut tx = db.pool().begin().await.unwrap(); fields::create_field_definition( &mut tx, &NewFieldDefinition { key: "material".into(), field_type: FieldType::Term { vocabulary_id: material.id, }, required: true, group_key: None, labels: labels(), }, ) .await .unwrap(); fields::create_field_definition( &mut tx, &NewFieldDefinition { key: "maker".into(), field_type: FieldType::Authority { kind: Some(AuthorityKind::Person), }, required: false, group_key: None, labels: vec![LocalizedLabel { lang: "en".into(), label: "maker".into(), }], }, ) .await .unwrap(); tx.commit().await.unwrap(); let material_def = fields::field_definition_by_key(db.pool(), "material") .await .unwrap() .unwrap(); assert_eq!( material_def.field_type, FieldType::Term { vocabulary_id: material.id } ); assert!(material_def.required); let maker_def = fields::field_definition_by_key(db.pool(), "maker") .await .unwrap() .unwrap(); assert_eq!( maker_def.field_type, FieldType::Authority { kind: Some(AuthorityKind::Person) } ); let all = fields::list_field_definitions(db.pool()).await.unwrap(); assert_eq!(all.len(), 2); } #[sqlx::test] async fn any_authority_scalar_and_zero_labels_round_trip(pool: PgPool) { let db = Db::from_pool(pool); let mut tx = db.pool().begin().await.unwrap(); fields::create_field_definition( &mut tx, &NewFieldDefinition { key: "donor".into(), field_type: FieldType::Authority { kind: None }, required: false, group_key: None, labels: vec![], }, ) .await .unwrap(); fields::create_field_definition( &mut tx, &NewFieldDefinition { key: "on_display".into(), field_type: FieldType::Boolean, required: false, group_key: None, labels: vec![LocalizedLabel { lang: "en".into(), label: "on display".into(), }], }, ) .await .unwrap(); tx.commit().await.unwrap(); let donor = fields::field_definition_by_key(db.pool(), "donor") .await .unwrap() .unwrap(); assert_eq!(donor.field_type, FieldType::Authority { kind: None }); assert!(donor.labels.is_empty()); let on_display = fields::field_definition_by_key(db.pool(), "on_display") .await .unwrap() .unwrap(); assert_eq!(on_display.field_type, FieldType::Boolean); // list_field_definitions is ordered by key. let all = fields::list_field_definitions(db.pool()).await.unwrap(); let keys: Vec<&str> = all.iter().map(|d| d.key.as_str()).collect(); assert_eq!(keys, vec!["donor", "on_display"]); } #[sqlx::test(migrations = "../db/migrations")] async fn update_field_definition_edits_labels_group_required(pool: PgPool) { let db = Db::from_pool(pool); let mut tx = db.pool().begin().await.unwrap(); fields::create_field_definition( &mut tx, &NewFieldDefinition { key: "weight".into(), field_type: FieldType::Integer, required: false, group_key: None, labels: vec![LocalizedLabel { lang: "sv".into(), label: "Vikt".into(), }], }, ) .await .unwrap(); let existed = fields::update_field_definition( &mut tx, AuditActor::System, "weight", true, Some("Mått"), &[LocalizedLabel { lang: "sv".into(), label: "Vikt (g)".into(), }], ) .await .unwrap(); assert!(existed); tx.commit().await.unwrap(); let def = fields::field_definition_by_key(db.pool(), "weight") .await .unwrap() .unwrap(); assert!(def.required); assert_eq!(def.group_key.as_deref(), Some("Mått")); assert_eq!(def.labels[0].label, "Vikt (g)"); } #[sqlx::test(migrations = "../db/migrations")] async fn delete_field_definition_blocks_when_objects_use_it(pool: PgPool) { let db = Db::from_pool(pool); let mut tx = db.pool().begin().await.unwrap(); fields::create_field_definition( &mut tx, &NewFieldDefinition { key: "weight".into(), field_type: FieldType::Integer, required: false, group_key: None, labels: vec![LocalizedLabel { lang: "sv".into(), label: "Vikt".into(), }], }, ) .await .unwrap(); let obj = catalog::create_object(&mut tx, AuditActor::System, &sample_object_input()) .await .unwrap(); let mut map = serde_json::Map::new(); map.insert("weight".into(), serde_json::Value::from(42)); catalog::set_object_fields(&mut tx, AuditActor::System, obj, &map) .await .unwrap(); assert_eq!( fields::delete_field_definition(&mut tx, AuditActor::System, "weight") .await .unwrap(), DeleteOutcome::InUse { count: 1 } ); catalog::set_object_fields(&mut tx, AuditActor::System, obj, &serde_json::Map::new()) .await .unwrap(); assert_eq!( fields::delete_field_definition(&mut tx, AuditActor::System, "weight") .await .unwrap(), DeleteOutcome::Deleted ); assert_eq!( fields::delete_field_definition(&mut tx, AuditActor::System, "weight") .await .unwrap(), DeleteOutcome::NotFound ); }