09baf2949f
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
316 lines
8.6 KiB
Rust
316 lines
8.6 KiB
Rust
use db::{Db, catalog, fields, vocab};
|
|
use domain::{
|
|
AuditActor, FieldType, LocalizedLabel, NewFieldDefinition, NewTerm, ObjectInput, Visibility,
|
|
};
|
|
use sqlx::PgPool;
|
|
|
|
#[sqlx::test]
|
|
async fn vocabulary_create_and_lookup(pool: PgPool) {
|
|
let db = Db::from_pool(pool);
|
|
let mut tx = db.pool().begin().await.unwrap();
|
|
let v = vocab::create_vocabulary(&mut tx, AuditActor::System, "material")
|
|
.await
|
|
.unwrap();
|
|
tx.commit().await.unwrap();
|
|
|
|
let found = vocab::vocabulary_by_key(db.pool(), "material")
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
|
|
assert_eq!(found.id, v.id);
|
|
assert_eq!(found.key, "material");
|
|
assert!(
|
|
vocab::vocabulary_by_key(db.pool(), "nope")
|
|
.await
|
|
.unwrap()
|
|
.is_none()
|
|
);
|
|
}
|
|
|
|
#[sqlx::test]
|
|
async fn term_with_multilingual_labels_round_trips(pool: PgPool) {
|
|
let db = Db::from_pool(pool);
|
|
let mut tx = db.pool().begin().await.unwrap();
|
|
let v = vocab::create_vocabulary(&mut tx, AuditActor::System, "material")
|
|
.await
|
|
.unwrap();
|
|
tx.commit().await.unwrap();
|
|
|
|
let mut tx = db.pool().begin().await.unwrap();
|
|
let term_id = vocab::add_term(
|
|
&mut tx,
|
|
AuditActor::System,
|
|
&NewTerm {
|
|
vocabulary_id: v.id,
|
|
external_uri: Some("http://vocab.getty.edu/aat/300011914".into()),
|
|
labels: vec![
|
|
LocalizedLabel {
|
|
lang: "sv".into(),
|
|
label: "trä".into(),
|
|
},
|
|
LocalizedLabel {
|
|
lang: "en".into(),
|
|
label: "wood".into(),
|
|
},
|
|
],
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
tx.commit().await.unwrap();
|
|
|
|
let term = vocab::term_by_id(db.pool(), term_id)
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
|
|
assert_eq!(term.vocabulary_id, v.id);
|
|
assert_eq!(
|
|
term.external_uri.as_deref(),
|
|
Some("http://vocab.getty.edu/aat/300011914")
|
|
);
|
|
assert_eq!(term.labels.len(), 2);
|
|
assert_eq!(domain::pick_label(&term.labels, "sv", "en"), Some("trä"));
|
|
assert_eq!(domain::pick_label(&term.labels, "de", "en"), Some("wood"));
|
|
|
|
let listed = vocab::list_terms(db.pool(), v.id).await.unwrap();
|
|
|
|
assert_eq!(listed.len(), 1);
|
|
assert_eq!(listed[0].id, term_id);
|
|
}
|
|
|
|
#[sqlx::test]
|
|
async fn term_with_no_labels_round_trips_empty(pool: PgPool) {
|
|
let db = Db::from_pool(pool);
|
|
let mut tx = db.pool().begin().await.unwrap();
|
|
let v = vocab::create_vocabulary(&mut tx, AuditActor::System, "material")
|
|
.await
|
|
.unwrap();
|
|
tx.commit().await.unwrap();
|
|
|
|
let mut tx = db.pool().begin().await.unwrap();
|
|
let term_id = vocab::add_term(
|
|
&mut tx,
|
|
AuditActor::System,
|
|
&NewTerm {
|
|
vocabulary_id: v.id,
|
|
external_uri: None,
|
|
labels: vec![],
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
tx.commit().await.unwrap();
|
|
|
|
let term = vocab::term_by_id(db.pool(), term_id)
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
assert!(term.labels.is_empty());
|
|
}
|
|
|
|
#[sqlx::test]
|
|
async fn duplicate_vocabulary_key_is_rejected(pool: PgPool) {
|
|
let db = Db::from_pool(pool);
|
|
let mut tx = db.pool().begin().await.unwrap();
|
|
vocab::create_vocabulary(&mut tx, AuditActor::System, "material")
|
|
.await
|
|
.unwrap();
|
|
tx.commit().await.unwrap();
|
|
|
|
let mut tx = db.pool().begin().await.unwrap();
|
|
let err = vocab::create_vocabulary(&mut tx, AuditActor::System, "material")
|
|
.await
|
|
.unwrap_err();
|
|
assert!(
|
|
matches!(err, sqlx::Error::Database(_)),
|
|
"expected a unique-violation DB error, got: {err:?}"
|
|
);
|
|
}
|
|
|
|
#[sqlx::test]
|
|
async fn resolve_term_checks_vocabulary_membership(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();
|
|
let technique = vocab::create_vocabulary(&mut tx, AuditActor::System, "technique")
|
|
.await
|
|
.unwrap();
|
|
tx.commit().await.unwrap();
|
|
|
|
let mut tx = db.pool().begin().await.unwrap();
|
|
let term_id = vocab::add_term(
|
|
&mut tx,
|
|
AuditActor::System,
|
|
&NewTerm {
|
|
vocabulary_id: material.id,
|
|
external_uri: None,
|
|
labels: vec![LocalizedLabel {
|
|
lang: "en".into(),
|
|
label: "wood".into(),
|
|
}],
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
tx.commit().await.unwrap();
|
|
|
|
assert!(
|
|
vocab::resolve_term(db.pool(), material.id, term_id)
|
|
.await
|
|
.unwrap()
|
|
.is_some()
|
|
);
|
|
assert!(
|
|
vocab::resolve_term(db.pool(), technique.id, term_id)
|
|
.await
|
|
.unwrap()
|
|
.is_none()
|
|
);
|
|
}
|
|
|
|
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,
|
|
}
|
|
}
|
|
|
|
#[sqlx::test(migrations = "../db/migrations")]
|
|
async fn update_term_changes_labels_and_uri(pool: PgPool) {
|
|
let db = Db::from_pool(pool);
|
|
let mut tx = db.pool().begin().await.unwrap();
|
|
let vocab = vocab::create_vocabulary(&mut tx, AuditActor::System, "material")
|
|
.await
|
|
.unwrap();
|
|
let term_id = vocab::add_term(
|
|
&mut tx,
|
|
AuditActor::System,
|
|
&NewTerm {
|
|
vocabulary_id: vocab.id,
|
|
external_uri: None,
|
|
labels: vec![LocalizedLabel {
|
|
lang: "sv".into(),
|
|
label: "Trä".into(),
|
|
}],
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let existed = vocab::update_term(
|
|
&mut tx,
|
|
AuditActor::System,
|
|
vocab.id,
|
|
term_id,
|
|
Some("https://example.org/wood"),
|
|
&[LocalizedLabel {
|
|
lang: "sv".into(),
|
|
label: "Träslag".into(),
|
|
}],
|
|
)
|
|
.await
|
|
.unwrap();
|
|
assert!(existed);
|
|
tx.commit().await.unwrap();
|
|
|
|
let term = vocab::term_by_id(db.pool(), term_id)
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
assert_eq!(
|
|
term.external_uri.as_deref(),
|
|
Some("https://example.org/wood")
|
|
);
|
|
assert_eq!(term.labels.len(), 1);
|
|
assert_eq!(term.labels[0].label, "Träslag");
|
|
}
|
|
|
|
#[sqlx::test(migrations = "../db/migrations")]
|
|
async fn delete_term_blocks_when_referenced_then_succeeds(pool: PgPool) {
|
|
use db::DeleteOutcome;
|
|
|
|
let db = Db::from_pool(pool);
|
|
let mut tx = db.pool().begin().await.unwrap();
|
|
let vocab = vocab::create_vocabulary(&mut tx, AuditActor::System, "material")
|
|
.await
|
|
.unwrap();
|
|
let term_id = vocab::add_term(
|
|
&mut tx,
|
|
AuditActor::System,
|
|
&NewTerm {
|
|
vocabulary_id: vocab.id,
|
|
external_uri: None,
|
|
labels: vec![LocalizedLabel {
|
|
lang: "sv".into(),
|
|
label: "Trä".into(),
|
|
}],
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
fields::create_field_definition(
|
|
&mut tx,
|
|
&NewFieldDefinition {
|
|
key: "material".into(),
|
|
field_type: FieldType::Term {
|
|
vocabulary_id: vocab.id,
|
|
},
|
|
required: false,
|
|
group_key: None,
|
|
labels: vec![LocalizedLabel {
|
|
lang: "sv".into(),
|
|
label: "Material".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(
|
|
"material".into(),
|
|
serde_json::Value::String(term_id.to_string()),
|
|
);
|
|
catalog::set_object_fields(&mut tx, AuditActor::System, obj, &map)
|
|
.await
|
|
.unwrap();
|
|
|
|
let blocked = vocab::delete_term(&mut tx, AuditActor::System, vocab.id, term_id)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(blocked, DeleteOutcome::InUse { count: 1 });
|
|
|
|
catalog::set_object_fields(&mut tx, AuditActor::System, obj, &serde_json::Map::new())
|
|
.await
|
|
.unwrap();
|
|
let ok = vocab::delete_term(&mut tx, AuditActor::System, vocab.id, term_id)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(ok, DeleteOutcome::Deleted);
|
|
assert!(
|
|
vocab::term_by_id(&mut *tx, term_id)
|
|
.await
|
|
.unwrap()
|
|
.is_none()
|
|
);
|
|
|
|
let gone = vocab::delete_term(&mut tx, AuditActor::System, vocab.id, term_id)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(gone, DeleteOutcome::NotFound);
|
|
}
|