use helios_sof::{ContentType, SofBundle, SofViewDefinition, run_view_definition};
fn create_test_bundle(
resources: &[serde_json::Value],
) -> Result<SofBundle, Box<dyn std::error::Error>> {
let mut bundle_json = serde_json::json!({
"resourceType": "Bundle",
"id": "test-bundle",
"type": "collection",
"entry": []
});
if let Some(entry_array) = bundle_json["entry"].as_array_mut() {
for resource in resources {
entry_array.push(serde_json::json!({ "resource": resource }));
}
}
let bundle: helios_fhir::r4::Bundle = serde_json::from_value(bundle_json)?;
Ok(SofBundle::R4(bundle))
}
fn parse_view_definition(
view_json: &serde_json::Value,
) -> Result<SofViewDefinition, Box<dyn std::error::Error>> {
let mut view_def = view_json.clone();
if let Some(obj) = view_def.as_object_mut() {
obj.insert(
"resourceType".to_string(),
serde_json::Value::String("ViewDefinition".to_string()),
);
obj.insert(
"status".to_string(),
serde_json::Value::String("active".to_string()),
);
}
let view_definition: helios_fhir::r4::ViewDefinition = serde_json::from_value(view_def)?;
Ok(SofViewDefinition::R4(view_definition))
}
fn run(view: &serde_json::Value, bundle: SofBundle) -> Vec<serde_json::Value> {
let view_definition = parse_view_definition(view).expect("Failed to parse ViewDefinition");
let result = run_view_definition(view_definition, bundle, ContentType::Json)
.expect("Failed to run ViewDefinition");
serde_json::from_slice(&result).expect("Failed to parse result as JSON")
}
#[test]
fn resolve_basic_cross_resource() {
let bundle = create_test_bundle(&[
serde_json::json!({
"resourceType": "Patient",
"id": "pat-1",
"name": [{ "family": "Sibling", "given": ["Bundle"] }]
}),
serde_json::json!({
"resourceType": "Encounter",
"id": "enc-1",
"status": "finished",
"subject": { "reference": "Patient/pat-1" }
}),
])
.expect("bundle");
let view = serde_json::json!({
"resource": "Encounter",
"select": [
{ "column": [{ "name": "encounter_id", "path": "id" }] },
{
"forEach": "subject.resolve()",
"column": [
{ "name": "patient_id", "path": "id" },
{ "name": "patient_family", "path": "name.family" }
]
}
]
});
let rows = run(&view, bundle);
assert_eq!(rows.len(), 1, "expected one row, got {rows:#?}");
assert_eq!(rows[0]["encounter_id"], serde_json::json!("enc-1"));
assert_eq!(
rows[0]["patient_id"],
serde_json::json!("pat-1"),
"subject.resolve() must dereference the sibling Patient, not a stub"
);
assert_eq!(rows[0]["patient_family"], serde_json::json!("Sibling"));
}
#[test]
fn resolve_missing_ref_yields_nulls() {
let bundle = create_test_bundle(&[serde_json::json!({
"resourceType": "Encounter",
"id": "enc-1",
"status": "finished",
"subject": { "reference": "Patient/does-not-exist" }
})])
.expect("bundle");
let view = serde_json::json!({
"resource": "Encounter",
"select": [
{ "column": [{ "name": "encounter_id", "path": "id" }] },
{
"forEach": "subject.resolve()",
"column": [{ "name": "patient_family", "path": "name.family" }]
}
]
});
let rows = run(&view, bundle);
assert_eq!(rows.len(), 1, "expected one row, got {rows:#?}");
assert_eq!(rows[0]["encounter_id"], serde_json::json!("enc-1"));
assert!(
rows[0]["patient_family"].is_null(),
"missing reference must produce a null column, got {:#?}",
rows[0]["patient_family"]
);
}
#[test]
fn resolve_contained_still_works() {
let bundle = create_test_bundle(&[serde_json::json!({
"resourceType": "Encounter",
"id": "enc-1",
"status": "finished",
"contained": [{
"resourceType": "Patient",
"id": "pat-1",
"name": [{ "family": "Contained" }]
}],
"subject": { "reference": "#pat-1" }
})])
.expect("bundle");
let view = serde_json::json!({
"resource": "Encounter",
"select": [
{ "column": [{ "name": "encounter_id", "path": "id" }] },
{
"forEach": "subject.resolve()",
"column": [
{ "name": "patient_id", "path": "id" },
{ "name": "patient_family", "path": "name.family" }
]
}
]
});
let rows = run(&view, bundle);
assert_eq!(rows.len(), 1, "expected one row, got {rows:#?}");
assert_eq!(rows[0]["patient_id"], serde_json::json!("pat-1"));
assert_eq!(rows[0]["patient_family"], serde_json::json!("Contained"));
}
#[test]
fn resolve_full_bundle_scope() {
let bundle = create_test_bundle(&[
serde_json::json!({
"resourceType": "Patient",
"id": "pat-1",
"name": [{ "family": "Alpha" }]
}),
serde_json::json!({
"resourceType": "Patient",
"id": "pat-2",
"name": [{ "family": "Beta" }]
}),
serde_json::json!({
"resourceType": "Encounter",
"id": "enc-1",
"status": "finished",
"subject": { "reference": "Patient/pat-1" }
}),
serde_json::json!({
"resourceType": "Encounter",
"id": "enc-2",
"status": "finished",
"subject": { "reference": "Patient/pat-2" }
}),
])
.expect("bundle");
let view = serde_json::json!({
"resource": "Encounter",
"select": [
{ "column": [{ "name": "encounter_id", "path": "id" }] },
{
"forEach": "subject.resolve()",
"column": [{ "name": "patient_family", "path": "name.family" }]
}
]
});
let mut rows = run(&view, bundle);
assert_eq!(rows.len(), 2, "expected two rows, got {rows:#?}");
rows.sort_by(|a, b| a["encounter_id"].as_str().cmp(&b["encounter_id"].as_str()));
assert_eq!(rows[0]["encounter_id"], serde_json::json!("enc-1"));
assert_eq!(rows[0]["patient_family"], serde_json::json!("Alpha"));
assert_eq!(rows[1]["encounter_id"], serde_json::json!("enc-2"));
assert_eq!(rows[1]["patient_family"], serde_json::json!("Beta"));
}
#[test]
fn resolve_absolute_url() {
let bundle = create_test_bundle(&[
serde_json::json!({
"resourceType": "Patient",
"id": "pat-1",
"name": [{ "family": "Absolute" }]
}),
serde_json::json!({
"resourceType": "Encounter",
"id": "enc-1",
"status": "finished",
"subject": { "reference": "http://example.org/fhir/Patient/pat-1" }
}),
])
.expect("bundle");
let view = serde_json::json!({
"resource": "Encounter",
"select": [{
"forEach": "subject.resolve()",
"column": [{ "name": "patient_family", "path": "name.family" }]
}]
});
let rows = run(&view, bundle);
assert_eq!(rows.len(), 1, "expected one row, got {rows:#?}");
assert_eq!(rows[0]["patient_family"], serde_json::json!("Absolute"));
}
#[test]
fn resolve_in_where_clause() {
let bundle = create_test_bundle(&[
serde_json::json!({
"resourceType": "Patient",
"id": "pat-1",
"gender": "female"
}),
serde_json::json!({
"resourceType": "Encounter",
"id": "enc-1",
"status": "finished",
"subject": { "reference": "Patient/pat-1" }
}),
serde_json::json!({
"resourceType": "Patient",
"id": "pat-2",
"gender": "male"
}),
serde_json::json!({
"resourceType": "Encounter",
"id": "enc-2",
"status": "finished",
"subject": { "reference": "Patient/pat-2" }
}),
])
.expect("bundle");
let view = serde_json::json!({
"resource": "Encounter",
"where": [{ "path": "subject.resolve().gender = 'female'" }],
"select": [{ "column": [{ "name": "encounter_id", "path": "id" }] }]
});
let rows = run(&view, bundle);
assert_eq!(
rows.len(),
1,
"only the Encounter whose Patient is female should match, got {rows:#?}"
);
assert_eq!(rows[0]["encounter_id"], serde_json::json!("enc-1"));
}