test(db): cover authority-kind, cross-vocabulary, localized text, replace/remove, no-op, missing object
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -163,3 +163,212 @@ async fn unknown_field_and_type_mismatch_are_rejected(pool: PgPool) {
|
||||
));
|
||||
drop(tx);
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn authority_field_enforces_kind(pool: PgPool) {
|
||||
let db = Db::from_pool(pool);
|
||||
let id = setup_object(&db).await;
|
||||
define(
|
||||
&db,
|
||||
"maker",
|
||||
FieldType::Authority {
|
||||
kind: Some(domain::AuthorityKind::Person),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut tx = db.pool().begin().await.unwrap();
|
||||
let person = db::authority::create_authority(
|
||||
&mut tx,
|
||||
&domain::NewAuthority {
|
||||
kind: domain::AuthorityKind::Person,
|
||||
external_uri: None,
|
||||
labels: label("Carl"),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let place = db::authority::create_authority(
|
||||
&mut tx,
|
||||
&domain::NewAuthority {
|
||||
kind: domain::AuthorityKind::Place,
|
||||
external_uri: None,
|
||||
labels: label("Stockholm"),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
tx.commit().await.unwrap();
|
||||
|
||||
let ok = serde_json::json!({ "maker": person.to_string() });
|
||||
let mut tx = db.pool().begin().await.unwrap();
|
||||
catalog::set_object_fields(&mut tx, AuditActor::System, id, ok.as_object().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
tx.commit().await.unwrap();
|
||||
|
||||
let bad = serde_json::json!({ "maker": place.to_string() });
|
||||
let mut tx = db.pool().begin().await.unwrap();
|
||||
assert!(matches!(
|
||||
catalog::set_object_fields(&mut tx, AuditActor::System, id, bad.as_object().unwrap()).await,
|
||||
Err(FieldError::Unresolved { .. })
|
||||
));
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn term_from_wrong_vocabulary_is_rejected(pool: PgPool) {
|
||||
let db = Db::from_pool(pool);
|
||||
let id = setup_object(&db).await;
|
||||
let material = vocab::create_vocabulary(db.pool(), "material")
|
||||
.await
|
||||
.unwrap();
|
||||
let technique = vocab::create_vocabulary(db.pool(), "technique")
|
||||
.await
|
||||
.unwrap();
|
||||
define(
|
||||
&db,
|
||||
"material",
|
||||
FieldType::Term {
|
||||
vocabulary_id: material.id,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
// a real term, but in the WRONG vocabulary
|
||||
let mut tx = db.pool().begin().await.unwrap();
|
||||
let other = vocab::add_term(
|
||||
&mut tx,
|
||||
&domain::NewTerm {
|
||||
vocabulary_id: technique.id,
|
||||
external_uri: None,
|
||||
labels: label("forged"),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
tx.commit().await.unwrap();
|
||||
|
||||
let bad = serde_json::json!({ "material": other.to_string() });
|
||||
let mut tx = db.pool().begin().await.unwrap();
|
||||
assert!(matches!(
|
||||
catalog::set_object_fields(&mut tx, AuditActor::System, id, bad.as_object().unwrap()).await,
|
||||
Err(FieldError::Unresolved { .. })
|
||||
));
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn localized_text_round_trips(pool: PgPool) {
|
||||
let db = Db::from_pool(pool);
|
||||
let id = setup_object(&db).await;
|
||||
define(&db, "title", FieldType::LocalizedText).await;
|
||||
|
||||
let values = serde_json::json!({ "title": { "sv": "Vas", "en": "Vase" } });
|
||||
let mut tx = db.pool().begin().await.unwrap();
|
||||
catalog::set_object_fields(&mut tx, AuditActor::System, id, values.as_object().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
tx.commit().await.unwrap();
|
||||
|
||||
let obj = catalog::object_by_id(db.pool(), id).await.unwrap().unwrap();
|
||||
assert_eq!(obj.fields["title"]["sv"], "Vas");
|
||||
assert_eq!(obj.fields["title"]["en"], "Vase");
|
||||
|
||||
let bad = serde_json::json!({ "title": { "sv": 5 } });
|
||||
let mut tx = db.pool().begin().await.unwrap();
|
||||
assert!(matches!(
|
||||
catalog::set_object_fields(&mut tx, AuditActor::System, id, bad.as_object().unwrap()).await,
|
||||
Err(FieldError::TypeMismatch { .. })
|
||||
));
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn replace_semantics_remove_a_field_and_audit_it(pool: PgPool) {
|
||||
let db = Db::from_pool(pool);
|
||||
let id = setup_object(&db).await;
|
||||
define(&db, "comments", FieldType::Text).await;
|
||||
define(&db, "year", FieldType::Integer).await;
|
||||
|
||||
let mut tx = db.pool().begin().await.unwrap();
|
||||
catalog::set_object_fields(
|
||||
&mut tx,
|
||||
AuditActor::System,
|
||||
id,
|
||||
serde_json::json!({ "comments": "x", "year": 1850 })
|
||||
.as_object()
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
tx.commit().await.unwrap();
|
||||
|
||||
let mut tx = db.pool().begin().await.unwrap();
|
||||
catalog::set_object_fields(
|
||||
&mut tx,
|
||||
AuditActor::System,
|
||||
id,
|
||||
serde_json::json!({ "comments": "x" }).as_object().unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
tx.commit().await.unwrap();
|
||||
|
||||
let obj = catalog::object_by_id(db.pool(), id).await.unwrap().unwrap();
|
||||
assert!(obj.fields.get("year").is_none());
|
||||
|
||||
let history = audit::history_for(db.pool(), "object", id.to_uuid())
|
||||
.await
|
||||
.unwrap();
|
||||
let last = history.last().unwrap();
|
||||
let year = last
|
||||
.changes
|
||||
.iter()
|
||||
.find(|c| c.field == "year")
|
||||
.expect("year removal recorded");
|
||||
assert!(year.before.is_some());
|
||||
assert!(year.after.is_none());
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn no_op_set_records_no_audit(pool: PgPool) {
|
||||
let db = Db::from_pool(pool);
|
||||
let id = setup_object(&db).await;
|
||||
define(&db, "comments", FieldType::Text).await;
|
||||
|
||||
let values = serde_json::json!({ "comments": "x" });
|
||||
let mut tx = db.pool().begin().await.unwrap();
|
||||
catalog::set_object_fields(&mut tx, AuditActor::System, id, values.as_object().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
tx.commit().await.unwrap();
|
||||
|
||||
let before = audit::history_for(db.pool(), "object", id.to_uuid())
|
||||
.await
|
||||
.unwrap()
|
||||
.len();
|
||||
|
||||
let mut tx = db.pool().begin().await.unwrap();
|
||||
catalog::set_object_fields(&mut tx, AuditActor::System, id, values.as_object().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
tx.commit().await.unwrap();
|
||||
|
||||
let after = audit::history_for(db.pool(), "object", id.to_uuid())
|
||||
.await
|
||||
.unwrap()
|
||||
.len();
|
||||
assert_eq!(before, after, "a no-op set must not add an audit entry");
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn set_on_missing_object_errors(pool: PgPool) {
|
||||
let db = Db::from_pool(pool);
|
||||
let mut tx = db.pool().begin().await.unwrap();
|
||||
let err = catalog::set_object_fields(
|
||||
&mut tx,
|
||||
AuditActor::System,
|
||||
domain::ObjectId::new(),
|
||||
serde_json::json!({}).as_object().unwrap(),
|
||||
)
|
||||
.await;
|
||||
assert!(matches!(err, Err(FieldError::ObjectNotFound)));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user